Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1182 lines
28 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. #include <windows.h>
  9. #include <stdio.h>
  10. #include <string.h>
  11. #include <stdlib.h>
  12. #include <errno.h>
  13. #include <conio.h>
  14. #include <ctype.h>
  15. #include <sys/types.h>
  16. #include <sys/stat.h>
  17. #include <io.h>
  18. #include <direct.h>
  19. #include <stdarg.h>
  20. #include "goldsrc_standin.h"
  21. #include "wadlib.h"
  22. #include "goldsrc_bspfile.h"
  23. extern FILE *wadhandle;
  24. #pragma pack( 1 )
  25. struct TGAHeader_t
  26. {
  27. unsigned char id_length;
  28. unsigned char colormap_type;
  29. unsigned char image_type;
  30. unsigned short colormap_index;
  31. unsigned short colormap_length;
  32. unsigned char colormap_size;
  33. unsigned short x_origin;
  34. unsigned short y_origin;
  35. unsigned short width;
  36. unsigned short height;
  37. unsigned char pixel_size;
  38. unsigned char attributes;
  39. };
  40. #pragma pack()
  41. typedef enum {ST_SYNC=0, ST_RAND } synctype_t;
  42. typedef enum { SPR_SINGLE=0, SPR_GROUP } spriteframetype_t;
  43. typedef struct {
  44. int ident;
  45. int version;
  46. int type;
  47. int texFormat;
  48. float boundingradius;
  49. int width;
  50. int height;
  51. int numframes;
  52. float beamlength;
  53. synctype_t synctype;
  54. } dsprite_t;
  55. typedef struct {
  56. int origin[2];
  57. int width;
  58. int height;
  59. } dspriteframe_t;
  60. class RGBAColor
  61. {
  62. public:
  63. unsigned char r,g,b,a;
  64. };
  65. const char *g_pDefaultShader = "LightmappedGeneric";
  66. const char *g_pShader = g_pDefaultShader;
  67. bool g_bBMPAllowTranslucent = false;
  68. bool g_bDecal = false;
  69. bool g_bQuiet = false;
  70. #define MAX_VMT_PARAMS 16
  71. struct VTexVMTParam_t
  72. {
  73. const char *m_szParam;
  74. const char *m_szValue;
  75. };
  76. static VTexVMTParam_t g_VMTParams[MAX_VMT_PARAMS];
  77. static int g_NumVMTParams = 0;
  78. void PrintExitStuff()
  79. {
  80. if ( !g_bQuiet )
  81. {
  82. printf( "Press a key to quit.\n" );
  83. getch();
  84. }
  85. }
  86. RGBAColor* ConvertToRGBUpsideDown( byte *pBits, int width, int height, byte *pPalette, bool *bTranslucent )
  87. {
  88. RGBAColor *pRet = new RGBAColor[width*height];
  89. byte newPalette[256][3];
  90. for ( int i=0; i < 256; i++ )
  91. {
  92. newPalette[i][0] = pPalette[i*3+2];
  93. newPalette[i][1] = pPalette[i*3+1];
  94. newPalette[i][2] = pPalette[i*3+0];
  95. }
  96. // Write the lines upside-down.
  97. for ( int y=0; y < height; y++ )
  98. {
  99. byte *pLine = &pBits[(height-y-1)*width];
  100. for ( int x=0; x < width; x++ )
  101. {
  102. pRet[y*width+x].r = newPalette[pLine[x]][0];
  103. pRet[y*width+x].g = newPalette[pLine[x]][1];
  104. pRet[y*width+x].b = newPalette[pLine[x]][2];
  105. if ( pLine[x] == 255 )
  106. {
  107. *bTranslucent = true;
  108. pRet[y*width+x].a = 0;
  109. }
  110. else
  111. {
  112. pRet[y*width+x].a = 255;
  113. }
  114. }
  115. }
  116. return pRet;
  117. }
  118. void FloodSolidPixels( RGBAColor *pTexels, int width, int height )
  119. {
  120. byte *pAlphaMap = new byte[width*height];
  121. byte *pNewAlphaMap = new byte[width*height];
  122. for ( int y=0; y < height; y++ )
  123. for ( int x=0; x < width; x++ )
  124. pAlphaMap[y*width+x] = pTexels[y*width+x].a;
  125. bool bHappy = false;
  126. while ( !bHappy )
  127. {
  128. bHappy = true;
  129. memcpy( pNewAlphaMap, pAlphaMap, width * height );
  130. for ( int y=0; y < height; y++ )
  131. {
  132. RGBAColor *pLine = &pTexels[y*width];
  133. for ( int x=0; x < width; x++ )
  134. {
  135. if ( pAlphaMap[y*width+x] == 0 )
  136. {
  137. int nNeighbors = 0;
  138. int neighborTotal[3] = {0,0,0};
  139. // Blend all the neighboring solid pixels.
  140. for ( int offsetX=-1; offsetX <= 1; offsetX++ )
  141. {
  142. for ( int offsetY=-1; offsetY <= 1; offsetY++ )
  143. {
  144. int testX = x + offsetX;
  145. int testY = y + offsetY;
  146. if ( testX >= 0 && testY >= 0 && testX < width && testY < height )
  147. {
  148. if ( pAlphaMap[testY*width+testX] )
  149. {
  150. RGBAColor *pNeighbor = &pTexels[testY*width+testX];
  151. ++nNeighbors;
  152. neighborTotal[0] += pNeighbor->r;
  153. neighborTotal[1] += pNeighbor->g;
  154. neighborTotal[2] += pNeighbor->b;
  155. }
  156. }
  157. }
  158. }
  159. if ( nNeighbors )
  160. {
  161. pNewAlphaMap[y*width+x] = 255;
  162. bHappy = false;
  163. pLine[x].r = (byte)( neighborTotal[0] / nNeighbors );
  164. pLine[x].g = (byte)( neighborTotal[1] / nNeighbors );
  165. pLine[x].b = (byte)( neighborTotal[2] / nNeighbors );
  166. }
  167. }
  168. }
  169. }
  170. memcpy( pAlphaMap, pNewAlphaMap, width * height );
  171. }
  172. delete [] pAlphaMap;
  173. delete [] pNewAlphaMap;
  174. }
  175. RGBAColor* ResampleImage( RGBAColor *pRGB, int width, int height, int newWidth, int newHeight )
  176. {
  177. RGBAColor *pResampled = new RGBAColor[newWidth * newHeight];
  178. for ( int y=0; y < newHeight; y++ )
  179. {
  180. float yPercent = (float)y / (newHeight - 1);
  181. float flSrcY = yPercent * (height - 1.00001f);
  182. int iSrcY = (int)flSrcY;
  183. float flYFrac = flSrcY - iSrcY;
  184. for ( int x=0; x < newWidth; x++ )
  185. {
  186. float xPercent = (float)x / (newWidth - 1);
  187. float flSrcX = xPercent * (width - 1.00001f);
  188. int iSrcX = (int)flSrcX;
  189. float flXFrac = flSrcX - iSrcX;
  190. byte *pSrc0 = ((byte*)&pRGB[iSrcY*width+iSrcX]);
  191. byte *pSrc1 = ((byte*)&pRGB[iSrcY*width+iSrcX+1]);
  192. byte *pSrc2 = ((byte*)&pRGB[(iSrcY+1)*width+iSrcX]);
  193. byte *pSrc3 = ((byte*)&pRGB[(iSrcY+1)*width+iSrcX+1]);
  194. byte *pDest = (byte*)&pResampled[y*newWidth+x];
  195. // Now blend the nearest 4 source pixels.
  196. for ( int i=0; i < 4; i++ )
  197. {
  198. //pDest[i] = pSrc0[i];
  199. float topColor = ( pSrc0[i]*(1-flXFrac) + pSrc1[i]*flXFrac );
  200. float bottomColor = ( pSrc2[i]*(1-flXFrac) + pSrc3[i]*flXFrac );
  201. pDest[i] = (byte)( topColor*(1-flYFrac) + bottomColor*flYFrac );
  202. }
  203. }
  204. }
  205. return pResampled;
  206. }
  207. bool WriteTGAFile(
  208. const char *pFilename,
  209. bool bAllowTranslucent,
  210. byte *pBits,
  211. int width,
  212. int height,
  213. byte *pPalette,
  214. bool bPowerOf2,
  215. bool *bTranslucent,
  216. bool *bResized
  217. )
  218. {
  219. *bResized = *bTranslucent = false;
  220. RGBAColor *pRGB = ConvertToRGBUpsideDown( pBits, width, height, pPalette, bTranslucent );
  221. // Unless the filename starts with '{', we don't allow translucency.
  222. if ( !bAllowTranslucent )
  223. *bTranslucent = false;
  224. // Flood the solid texel colors into the transparent texels.
  225. // Since we turn on point sampling for these textures, this only matters if we're resizing the texture.
  226. if ( *bTranslucent )
  227. {
  228. FloodSolidPixels( pRGB, width, height );
  229. }
  230. if ( bPowerOf2 )
  231. {
  232. // Is it not a power of 2?
  233. if ( (width & (width - 1)) || (height & (height - 1)) )
  234. {
  235. // Ok, resize it to the next highest power of 2.
  236. int newWidth = width;
  237. while ( (newWidth & (newWidth - 1)) )
  238. ++newWidth;
  239. int newHeight = height;
  240. while ( (newHeight & (newHeight - 1)) )
  241. ++newHeight;
  242. printf( "\t(%dx%d) -> (%dx%d)", width, height, newWidth, newHeight );
  243. RGBAColor *pResampled = ResampleImage( pRGB, width, height, newWidth, newHeight );
  244. delete [] pRGB;
  245. pRGB = pResampled;
  246. width = newWidth;
  247. height = newHeight;
  248. *bResized = true;
  249. }
  250. }
  251. // Write it..
  252. TGAHeader_t hdr;
  253. memset( &hdr, 0, sizeof( hdr ) );
  254. hdr.width = width;
  255. hdr.height = height;
  256. hdr.colormap_type = 0; // no, no colormap please
  257. hdr.image_type = 2; // uncompressed, true-color
  258. hdr.pixel_size = 32; // 32 bits per pixel
  259. FILE *fp = fopen( pFilename, "wb" );
  260. if ( !fp )
  261. return false;
  262. SafeWrite( fp, &hdr, sizeof( hdr ) );
  263. SafeWrite( fp, pRGB, sizeof( RGBAColor ) * width * height );
  264. fclose( fp );
  265. delete [] pRGB;
  266. return true;
  267. }
  268. int PrintUsage( const char *pExtra, ... )
  269. {
  270. va_list marker;
  271. va_start( marker, pExtra );
  272. vprintf( pExtra, marker );
  273. va_end( marker );
  274. printf(
  275. "%s \n"
  276. "\t[-AutoDir]\n"
  277. "\t\tAutomatically detects -basedir and -wadfile or -bmpfile based\n"
  278. "\t\ton the last parameter (it must be a WAD file or a BMP file.\n"
  279. "\t[-decal]\n"
  280. "\t\tCreates VMTs for decals and creates VMTs for model decals.\n"
  281. "\t[-onlytex <tex name>]\n"
  282. "\t[-shader <shader name>]\n"
  283. "\t\tSpecify the shader to use in the VMT file (default\n"
  284. "\t\tis LightmappedGeneric.\n"
  285. "\t[-vtex]\n"
  286. "\t\tIf -vtex is specified, then it calls vtex on each newly-created\n"
  287. "\t\t.TGA file.\n"
  288. "\t[-vmtparam <ParamName> <ParamValue>]\n"
  289. "\t\tif -vtex was specified, passes the parameters to that process.\n"
  290. "\t\tUsed to add parameters to the generated .vmt file\n"
  291. "\t-BaseDir <basedir>\n"
  292. "\t-game <basedir>\n"
  293. "\t\tSpecifies where the root mod directory is.\n"
  294. "\t-WadFile <wildcard>\n"
  295. "\t\t-wadfile will make (power-of-2) TGAs, VTFs, and VMTs for each\n"
  296. "\t\ttexture in the WAD. It will also place them under a directory\n"
  297. "\t\twith the name of the WAD. In addition, it will create\n"
  298. "\t\t.resizeinfo files in the materials directory if it has to\n"
  299. "\t\tresize the texture. Then Hammer's File->WAD To Material\n"
  300. "\t\tcommand will use them to rescale texture coordinates.\n"
  301. "\t-bmpfile <wildcard>\n"
  302. "\t\t-bmpfile acts like -WadFile but for BMP files, and it'll place\n"
  303. "\t\tthem in the root materials directory.\n"
  304. "\t-sprfile <wildcard>\n"
  305. "\t\tActs like -bmpfile, but ports a sprite.\n"
  306. "\t-Transparent (BMP files only)\n"
  307. "\t\tIf this is set, then it will treat palette index 255 as a\n"
  308. "\t\ttransparent pixel."
  309. "\t-SubDir <subdirectory>\n"
  310. "\t\t-SubDir tells it what directory under materials to place the\n"
  311. "\t\tfinal art. If using a WAD file, then it will automatically\n"
  312. "\t\tuse the WAD filename if no -SubDir is specified.\n"
  313. "\t-Quiet\n"
  314. "\t\tDon't print out anything or wait for a keypress on exit.\n"
  315. "\n"
  316. , __argv[0] );
  317. printf( "ex: %s -vtex -BaseDir c:\\hl2\\dod -WadFile c:\\hl1\\dod\\*.wad\n", __argv[0] );
  318. printf( "ex: %s -vtex -BaseDir c:\\hl2\\dod -bmpfile test.bmp -SubDir models\\props\n", __argv[0] );
  319. printf( "ex: %s -vtex -vmtparam $ignorez 1 -BaseDir c:\\hl2\\dod -sprfile test.spr -SubDir sprites\\props\n", __argv[0] );
  320. PrintExitStuff();
  321. return 1;
  322. }
  323. // Take something like "c:/a/b/c/filename.ext" and return "filename".
  324. void GetBaseFilename( const char *pWadFilename, char wadBaseName[512] )
  325. {
  326. const char *pBase = strrchr( pWadFilename, '\\' );
  327. if ( strrchr( pWadFilename, '/' ) > pBase )
  328. pBase = strrchr( pWadFilename, '/' );
  329. if ( pBase )
  330. strcpy( wadBaseName, pBase+1 );
  331. else
  332. strcpy( wadBaseName, pWadFilename );
  333. char *pDot = strchr( wadBaseName, '.' );
  334. if ( pDot )
  335. *pDot = 0;
  336. }
  337. const char *LastSlash( const char *pSrc )
  338. {
  339. const char *pRet = strrchr( pSrc, '/' );
  340. const char *pRet2 = strrchr( pSrc, '\\' );
  341. return (pRet > pRet2) ? pRet : pRet2;
  342. }
  343. void EnsureDirExists( const char *pDir )
  344. {
  345. if ( _access( pDir, 0 ) != 0 )
  346. {
  347. // We use the shell's "md" command here instead of the _mkdir() function because
  348. // md will create all the subdirectories leading up to the bottom one and _mkdir() won't.
  349. char cmd[1024];
  350. _snprintf( cmd, sizeof( cmd ), "md \"%s\"", pDir );
  351. system( cmd );
  352. if ( _access( pDir, 0 ) != 0 )
  353. Error( "\tCan't create directory: %s.\n", pDir );
  354. }
  355. }
  356. void WriteVMTFile( const char *pBaseDir, const char *pSubDir, const char *pName, bool bTranslucent )
  357. {
  358. char vmtFilename[512];
  359. sprintf( vmtFilename, "%s\\materials\\%s\\%s.vmt", pBaseDir, pSubDir, pName );
  360. FILE *fp = fopen( vmtFilename, "wt" );
  361. if ( !fp )
  362. {
  363. Error( "\tWriteVMTFile failed to open %s for writing.\n", vmtFilename );
  364. return;
  365. }
  366. fprintf( fp, "\"%s\"\n{\n", g_pShader );
  367. fprintf( fp, "\t\"$basetexture\"\t\"%s\\%s\"\n", pSubDir, pName );
  368. if ( bTranslucent || g_bDecal )
  369. {
  370. fprintf( fp, "\t\"$alphatest\"\t\"1\"\n" );
  371. fprintf( fp, "\t\"$ALPHATESTREFERENCE\"\t\"0.5\"\n" );
  372. }
  373. if ( g_bDecal )
  374. {
  375. fprintf( fp, "\t\"$decal\"\t\t\"1\"\n" );
  376. }
  377. int i;
  378. for( i=0;i<g_NumVMTParams;i++ )
  379. {
  380. fprintf( fp, "\t\"%s\" \"%s\"\n", g_VMTParams[i].m_szParam, g_VMTParams[i].m_szValue );
  381. }
  382. fprintf( fp, "}" );
  383. fclose( fp );
  384. }
  385. void WriteTXTFile( const char *pBaseDir, const char *pSubDir, const char *pName )
  386. {
  387. char filename[512];
  388. sprintf( filename, "%s\\materialsrc\\%s\\%s.txt", pBaseDir, pSubDir, pName );
  389. FILE *fp = fopen( filename, "wt" );
  390. if ( !fp )
  391. Error( "\tWriteTXTFile: can't open %s for writing.\n", filename );
  392. fprintf( fp, "\"pointsample\" \"1\"\n" );
  393. fclose( fp );
  394. }
  395. void WriteResizeInfoFile( const char *pBaseDir, const char *pSubDir, const char *pName, int width, int height )
  396. {
  397. char filename[512];
  398. sprintf( filename, "%s\\materials\\%s\\%s.resizeinfo", pBaseDir, pSubDir, pName );
  399. FILE *fp = fopen( filename, "wt" );
  400. if ( !fp )
  401. {
  402. Error( "\tWriteResizeInfoFile failed to open %s for writing.\n", filename );
  403. return;
  404. }
  405. fprintf( fp, "%d %d", width, height );
  406. fclose( fp );
  407. }
  408. void RunVTexOnFile( const char *pBaseDir, const char *pFilename )
  409. {
  410. char executableDir[MAX_PATH];
  411. GetModuleFileName( NULL, executableDir, sizeof( executableDir ) );
  412. char *pLastSlash = max( strrchr( executableDir, '/' ), strrchr( executableDir, '\\' ) );
  413. if ( !pLastSlash )
  414. Error( "Can't find filename in '%s'.\n", executableDir );
  415. *pLastSlash = 0;
  416. // Set the vproject environment variable (vtex doesn't allow game yet).
  417. char envStr[MAX_PATH];
  418. _snprintf( envStr, sizeof( envStr ), "vproject=%s", pBaseDir );
  419. putenv( envStr );
  420. // Call vtex on this texture now.
  421. char vtexCommand[1024];
  422. sprintf( vtexCommand, "%s\\vtex.exe -quiet -nopause \"%s\"", executableDir, pFilename );
  423. if ( system( vtexCommand ) != 0 )
  424. {
  425. Error( "\tCommand '%s' failed!\n", vtexCommand );
  426. }
  427. }
  428. void WriteOutputFiles(
  429. const char *pBaseDir,
  430. const char *pSubDir,
  431. const char *pName,
  432. bool bAllowTranslucent,
  433. byte *buffer,
  434. int width,
  435. int height,
  436. byte *pPalette,
  437. bool bVTex
  438. )
  439. {
  440. bool bTranslucent, bResized;
  441. bool bPowerOf2 = true;
  442. char tgaFilename[1024];
  443. sprintf( tgaFilename, "%s\\materialsrc\\%s\\%s.tga", pBaseDir, pSubDir, pName );
  444. if ( !WriteTGAFile(
  445. tgaFilename,
  446. bAllowTranslucent,
  447. buffer,
  448. width,
  449. height,
  450. pPalette,
  451. bPowerOf2,
  452. &bTranslucent,
  453. &bResized ) )
  454. {
  455. Error( "\tError writing %s.\n", tgaFilename );
  456. }
  457. // Write its .VMT file.
  458. WriteVMTFile( pBaseDir, pSubDir, pName, bTranslucent );
  459. // Write a text file for it if it's translucent so we can enable pointsample for vtex.
  460. // if ( bTranslucent )
  461. // WriteTXTFile( pBaseDir, pSubDir, pName );
  462. // Write its .resizeinfo file.
  463. if ( bResized )
  464. {
  465. WriteResizeInfoFile( pBaseDir, pSubDir, pName, width, height );
  466. }
  467. if ( bVTex )
  468. {
  469. RunVTexOnFile( pBaseDir, tgaFilename );
  470. }
  471. }
  472. void EnsureDirectoriesExist( const char *pBaseDir, const char *pSubDir )
  473. {
  474. char materialsrcDir[512], materialsDir[512];
  475. sprintf( materialsrcDir, "%s\\materialsrc\\%s", pBaseDir, pSubDir );
  476. sprintf( materialsDir, "%s\\materials\\%s", pBaseDir, pSubDir );
  477. EnsureDirExists( materialsrcDir );
  478. EnsureDirExists( materialsDir );
  479. }
  480. void ProcessWadFile( const char *pWadFilename, const char *pBaseDir, const char *pSubDir, const char *pOnlyTex, bool bVTex )
  481. {
  482. if ( !g_bQuiet )
  483. printf( "\n\n[WADFILE %s]\n\n", pWadFilename );
  484. // If no -subdir was specified, then figure it out from the wad filename.
  485. char wadBaseName[512];
  486. if ( !pSubDir )
  487. {
  488. // Get the base wad filename.
  489. GetBaseFilename( pWadFilename, wadBaseName );
  490. pSubDir = wadBaseName;
  491. }
  492. EnsureDirectoriesExist( pBaseDir, pSubDir );
  493. // Now process all the images in the wad.
  494. W_OpenWad( pWadFilename );
  495. #define MAXLUMP (640*480*85/64)
  496. byte inbuffer[MAXLUMP];
  497. for (int i = 0; i < numlumps; i++)
  498. {
  499. if ( pOnlyTex && stricmp( pOnlyTex, lumpinfo[i].name ) != 0 )
  500. continue;
  501. fseek( wadhandle, lumpinfo[i].filepos, SEEK_SET );
  502. SafeRead ( wadhandle, inbuffer, lumpinfo[i].size );
  503. miptex_t *qtex = (miptex_t *)inbuffer;
  504. int width = LittleLong(qtex->width);
  505. int height = LittleLong(qtex->height);
  506. if ( width <= 0 || height <= 0 || width > 5000 || height > 5000 )
  507. {
  508. if ( !g_bQuiet )
  509. printf("\tskipping %s @ %d size %d (not an image?)\n", lumpinfo[i].name, lumpinfo[i].filepos, lumpinfo[i].size );
  510. continue;
  511. }
  512. else
  513. {
  514. if ( !g_bQuiet )
  515. printf( "\t%s", lumpinfo[i].name );
  516. }
  517. byte *pPalette = inbuffer + LittleLong( qtex->offsets[3] ) + width * height / 64 + 2;
  518. byte *psrc, *pdest;
  519. byte outbuffer[(640+320)*480];
  520. // The old xwad put the mipmaps in there too, but we don't want that now (usually).
  521. // copy in 0 image
  522. psrc = inbuffer + LittleLong( qtex->offsets[0] );
  523. pdest = outbuffer;
  524. for (int t = 0; t < height; t++)
  525. {
  526. memcpy( pdest + t * width, psrc + t * width, width );
  527. }
  528. WriteOutputFiles(
  529. pBaseDir, // base directory
  530. pSubDir, // subdir under materials
  531. qtex->name, // filename (w/o extension)
  532. qtex->name[0] == '{', // allow transparency?
  533. outbuffer,
  534. width,
  535. height,
  536. pPalette,
  537. bVTex
  538. );
  539. if ( !g_bQuiet )
  540. printf( "\n" );
  541. }
  542. }
  543. void ProcessBMPFile( const char *pBaseDir, const char *pSubDir, const char *pFilename, bool bVTex )
  544. {
  545. if ( !g_bQuiet )
  546. printf( "[%s]\n", pFilename );
  547. if ( !pSubDir )
  548. pSubDir = ".";
  549. // First make directories under materialsrc and materials if they don't exist.
  550. EnsureDirectoriesExist( pBaseDir, pSubDir );
  551. // Read in the 8-bit BMP file.
  552. FILE *fp = fopen( pFilename, "rb" );
  553. if ( !fp )
  554. Error( "ProcessBMPFile( %s ) can't open the file for reading.\n", pFilename );
  555. BITMAPFILEHEADER bfh;
  556. BITMAPINFOHEADER bih;
  557. unsigned char pixelData[512*512];
  558. SafeRead( fp, &bfh, sizeof( bfh ) );
  559. SafeRead( fp, &bih, sizeof( bih ) );
  560. // Make sure it's an 8-bit one like we want.
  561. if ( bih.biSize != sizeof( bih ) ||
  562. bih.biPlanes != 1 ||
  563. bih.biBitCount != 8 ||
  564. bih.biCompression != BI_RGB ||
  565. bih.biHeight < 0 ||
  566. bih.biWidth * bih.biHeight > sizeof( pixelData ) )
  567. {
  568. Error( "ProcessBMPFile( %s ) - invalid format.\n", pFilename );
  569. }
  570. int nColorsUsed = 256;
  571. if ( bih.biClrUsed != 0 )
  572. {
  573. nColorsUsed = bih.biClrUsed;
  574. if ( nColorsUsed > 256 )
  575. Error( "ProcessBMPFile( %s ) - bih.biClrUsed = %d.\n", pFilename, bih.biClrUsed );
  576. }
  577. RGBQUAD quadPalette[256];
  578. SafeRead( fp, quadPalette, sizeof( quadPalette[0] ) * nColorsUsed );
  579. // Usually, bfOffBits is the same place we are at now, but sometimes it's a little different.
  580. fseek( fp, bfh.bfOffBits, SEEK_SET );
  581. // Now read the bitmap data.
  582. SafeRead( fp, pixelData, bih.biWidth * bih.biHeight );
  583. fclose( fp );
  584. // Convert the palette.
  585. byte palette[256][3];
  586. for ( int i=0; i < 256; i++ )
  587. {
  588. palette[i][0] = quadPalette[i].rgbRed;
  589. palette[i][1] = quadPalette[i].rgbGreen;
  590. palette[i][2] = quadPalette[i].rgbBlue;
  591. }
  592. // Unflip the pixel data.
  593. for ( int y=0; y < bih.biHeight / 2; y++ )
  594. {
  595. byte tempLine[1024];
  596. memcpy( tempLine, &pixelData[y*bih.biWidth], bih.biWidth );
  597. memcpy( &pixelData[y*bih.biWidth], &pixelData[(bih.biHeight-y-1)*bih.biWidth], bih.biWidth );
  598. memcpy( &pixelData[(bih.biHeight-y-1)*bih.biWidth], tempLine, bih.biWidth );
  599. }
  600. char baseFilename[512];
  601. GetBaseFilename( pFilename, baseFilename );
  602. // Save it out.
  603. WriteOutputFiles(
  604. pBaseDir, // base directory
  605. pSubDir, // subdir under materials
  606. baseFilename, // filename (w/o extension)
  607. g_bBMPAllowTranslucent, // allow transparency
  608. pixelData,
  609. bih.biWidth,
  610. bih.biHeight,
  611. (byte*)palette,
  612. bVTex
  613. );
  614. }
  615. void ProcessSPRFile( const char *pBaseDir, const char *pSubDir, const char *pFilename, bool bVTex )
  616. {
  617. if ( !g_bQuiet )
  618. printf( "[%s]\n", pFilename );
  619. if ( !pSubDir )
  620. pSubDir = ".";
  621. char baseFilename[512];
  622. GetBaseFilename( pFilename, baseFilename );
  623. // First make directories under materialsrc and materials if they don't exist.
  624. EnsureDirectoriesExist( pBaseDir, pSubDir );
  625. // Read in the SPR file.
  626. FILE *fp = fopen( pFilename, "rb" );
  627. if ( !fp )
  628. Error( "ProcessSPRFile( %s ) can't open the file for reading.\n", pFilename );
  629. dsprite_t header;
  630. SafeRead( fp, &header, sizeof( header ) );
  631. // Make sure it's a sprite file.
  632. if ( ((header.ident>>0) & 0xFF) != 'I' ||
  633. ((header.ident>>8) & 0xFF) != 'D' ||
  634. ((header.ident>>16) & 0xFF) != 'S' ||
  635. ((header.ident>>24) & 0xFF) != 'P' )
  636. {
  637. Warning( "WARNING: sprite %s is not a sprite file. Skipping.\n", pFilename );
  638. fclose( fp );
  639. return;
  640. }
  641. if ( header.version != 2 )
  642. {
  643. Warning( "WARNING: sprite %s is not a version 2 sprite file. Skipping.\n", pFilename );
  644. fclose( fp );
  645. return;
  646. }
  647. // Read the palette.
  648. short cnt;
  649. byte palette[768];
  650. SafeRead( fp, &cnt, sizeof( cnt ) );
  651. SafeRead( fp, palette, sizeof( palette ) );
  652. // Read the frames.
  653. int i;
  654. for ( i=0; i < header.numframes; i++ )
  655. {
  656. spriteframetype_t type;
  657. SafeRead( fp, &type, sizeof( type ) );
  658. if ( type == SPR_SINGLE )
  659. {
  660. dspriteframe_t frame;
  661. SafeRead( fp, &frame, sizeof( frame ) );
  662. if ( frame.width > 5000 || frame.height > 5000 || frame.width < 1 || frame.height < 1 )
  663. {
  664. Warning( "WARNING: sprite %s has an invalid frame size (%d x %d) for frame %d.\n", pFilename, frame.width, frame.height, i );
  665. fclose( fp );
  666. return;
  667. }
  668. byte *frameData = new byte[frame.width * frame.height];
  669. SafeRead( fp, frameData, frame.width * frame.height );
  670. Msg( "\tFrame %d ", i );
  671. // Write the TGA file for this frame.
  672. bool bTranslucent, bResized;
  673. char frameFilename[512];
  674. _snprintf( frameFilename, sizeof( frameFilename ), "%s\\materialsrc\\%s\\%s%03d.tga", pBaseDir, pSubDir, baseFilename, i );
  675. if ( !WriteTGAFile(
  676. frameFilename,
  677. g_bBMPAllowTranslucent,
  678. frameData,
  679. frame.width,
  680. frame.height,
  681. palette,
  682. true, // allow power-of-2
  683. &bTranslucent,
  684. &bResized ) )
  685. {
  686. Error( "\tError writing %s.\n", frameFilename );
  687. }
  688. if ( !g_bQuiet )
  689. printf( "\n" );
  690. delete [] frameData;
  691. }
  692. else if ( type == SPR_GROUP )
  693. {
  694. Error( "Sprite %s uses type SPR_GROUP. Get a programmer to add support for it.\n", pFilename );
  695. }
  696. else
  697. {
  698. Warning( "WARNING: sprite %s has an invalid frame type (%d) for frame %d.\n", pFilename, type, i );
  699. fclose( fp );
  700. return;
  701. }
  702. }
  703. fclose( fp );
  704. //
  705. // Generate a .txt file for the sprite.
  706. //
  707. char txtFilename[512];
  708. sprintf( txtFilename, "%s\\materialsrc\\%s\\%s.txt", pBaseDir, pSubDir, baseFilename );
  709. fp = fopen( txtFilename, "wt" );
  710. if ( !fp )
  711. Error( "\tProcessSPRFile: can't open %s for writing.\n", txtFilename );
  712. fprintf( fp, "\"startframe\" \"0\"\n" );
  713. fprintf( fp, "\"endframe\" \"%d\"\n", header.numframes-1 );
  714. fprintf( fp, "\"nomip\" \"1\"\n" );
  715. fprintf( fp, "\"nolod\" \"1\"\n" );
  716. fclose( fp );
  717. //
  718. // Run VTEX on the .txt file?
  719. //
  720. if ( bVTex )
  721. {
  722. RunVTexOnFile( pBaseDir, txtFilename );
  723. }
  724. //
  725. // Generate a .vmt file.
  726. //
  727. char vmtFilename[512];
  728. _snprintf( vmtFilename, sizeof( vmtFilename ), "%s\\materials\\%s\\%s.vmt", pBaseDir, pSubDir, baseFilename );
  729. fp = fopen( vmtFilename, "wt" );
  730. if ( !fp )
  731. Error( "\tProcessSPRFile: can't open %s for writing.\n", vmtFilename );
  732. if ( g_pShader == g_pDefaultShader )
  733. fprintf( fp, "\"UnlitGeneric\"\n" );
  734. else
  735. fprintf( fp, "\"%s\"\n", g_pShader );
  736. fprintf( fp, "{\n" );
  737. fprintf( fp, "\t\"$spriteorientation\" \"vp_parallel\"\n" );
  738. fprintf( fp, "\t\"$spriteorigin\" \"[ 0.50 0.50 ]\"\n" );
  739. fprintf( fp, "\t\"$basetexture\" \"%s/%s\"\n", pSubDir, baseFilename );
  740. for( i=0;i<g_NumVMTParams;i++ )
  741. {
  742. fprintf( fp, "\t\"%s\" \"%s\"\n", g_VMTParams[i].m_szParam, g_VMTParams[i].m_szValue );
  743. }
  744. fprintf( fp, "}" );
  745. fclose( fp );
  746. }
  747. void ExtractDirectory( const char *pFilename, char *prefix )
  748. {
  749. const char *pSlash = strrchr( pFilename, '/' );
  750. if ( strrchr( pFilename, '\\' ) > pSlash )
  751. pSlash = strrchr( pFilename, '\\' );
  752. if ( pSlash )
  753. {
  754. memcpy( prefix, pFilename, pSlash - pFilename );
  755. prefix[ pSlash - pFilename ] = 0;
  756. }
  757. else
  758. {
  759. strcpy( prefix, ".\\" );
  760. }
  761. }
  762. // This allows them to have a WAD or BMP under their materialsrc directory and it'll try to figure out
  763. // all the other parameters for them.
  764. bool DragAndDropCheck(
  765. const char **pBaseDir,
  766. const char **pSubDir,
  767. const char **pWadFilenames,
  768. const char **pBMPFilenames,
  769. const char **pSPRFilenames,
  770. bool *bVTex )
  771. {
  772. const char *pLastParam = __argv[__argc-1];
  773. // Get the first argument in upper case.
  774. char arg1[512];
  775. strncpy( arg1, pLastParam, sizeof( arg1 ) );
  776. strupr( arg1 );
  777. // Only handle it if there's a full path (with a colon).
  778. if ( !strchr( arg1, ':' ) )
  779. return false;
  780. if ( strstr( arg1, ".WAD" ) )
  781. *pWadFilenames = pLastParam;
  782. else if ( strstr( arg1, ".BMP" ) )
  783. *pBMPFilenames = pLastParam;
  784. else if ( strstr( arg1, ".SPR" ) )
  785. *pSPRFilenames = pLastParam;
  786. else
  787. return false;
  788. // Ok, we know that argv[1] has a valid filename. Is it under materialsrc?
  789. char *pMatSrc = strstr( arg1, "MATERIALSRC" );
  790. if ( !pMatSrc || pMatSrc == arg1 )
  791. return false;
  792. // The base directory is everything before materialsrc.
  793. static char baseDir[512];
  794. *pBaseDir = baseDir;
  795. memcpy( baseDir, arg1, pMatSrc-arg1 );
  796. baseDir[pMatSrc-arg1-1] = 0;
  797. // The subdirectory is everything after materialsrc, minus the filename.
  798. char *pSubDirSrc = pMatSrc + strlen( "MATERIALSRC" ) + 1;
  799. char *pEnd = strrchr( pSubDirSrc, '\\' );
  800. if ( strrchr( pSubDirSrc, '/' ) > pEnd )
  801. pEnd = strrchr( pSubDirSrc, '/' );
  802. if ( !pEnd )
  803. return false;
  804. static char subDir[512];
  805. *pSubDir = subDir;
  806. memcpy( subDir, pSubDirSrc, pEnd - pSubDirSrc );
  807. subDir[pEnd-pSubDirSrc] = 0;
  808. // Always use vtex in drag-and-drop mode.
  809. *bVTex = true;
  810. return true;
  811. }
  812. int main (int argc, char **argv)
  813. {
  814. if (argc < 2)
  815. {
  816. return PrintUsage( "" );
  817. }
  818. bool bWriteTGA = true;
  819. bool bWriteBMP = false;
  820. bool bPowerOf2 = true;
  821. bool bAutoDir = false;
  822. bool bVTex = false;
  823. const char *pBaseDir = NULL;
  824. const char *pWadFilenames = NULL;
  825. const char *pBMPFilenames = NULL;
  826. const char *pSPRFilenames = NULL;
  827. const char *pSubDir = NULL;
  828. const char *pOnlyTex = NULL;
  829. // Scan for options.
  830. for ( int i=1; i < argc; i++ )
  831. {
  832. if ( (i+2) < argc )
  833. {
  834. if ( stricmp( argv[i], "-vmtparam" ) == 0 && g_NumVMTParams < MAX_VMT_PARAMS )
  835. {
  836. g_VMTParams[g_NumVMTParams].m_szParam = argv[i+1];
  837. g_VMTParams[g_NumVMTParams].m_szValue = argv[i+2];
  838. if( !g_bQuiet )
  839. {
  840. fprintf( stderr, "Adding .vmt parameter: \"%s\"\t\"%s\"\n",
  841. g_VMTParams[g_NumVMTParams].m_szParam,
  842. g_VMTParams[g_NumVMTParams].m_szValue );
  843. }
  844. g_NumVMTParams++;
  845. i += 2;
  846. }
  847. }
  848. if ( (i+1) < argc )
  849. {
  850. if ( stricmp( argv[i], "-basedir" ) == 0 )
  851. {
  852. pBaseDir = argv[i+1];
  853. ++i;
  854. }
  855. else if ( stricmp( argv[i], "-wadfile" ) == 0 )
  856. {
  857. pWadFilenames = argv[i+1];
  858. ++i;
  859. }
  860. else if ( stricmp( argv[i], "-bmpfile" ) == 0 )
  861. {
  862. pBMPFilenames = argv[i+1];
  863. ++i;
  864. }
  865. else if ( stricmp( argv[i], "-sprfile" ) == 0 )
  866. {
  867. pSPRFilenames = argv[i+1];
  868. ++i;
  869. }
  870. else if ( stricmp( argv[i], "-OnlyTex" ) == 0 )
  871. {
  872. pOnlyTex = argv[i+1];
  873. ++i;
  874. }
  875. else if ( stricmp( argv[i], "-SubDir" ) == 0 )
  876. {
  877. pSubDir = argv[i+1];
  878. ++i;
  879. }
  880. else if ( stricmp( argv[i], "-shader" ) == 0 )
  881. {
  882. g_pShader = argv[i+1];
  883. ++i;
  884. }
  885. }
  886. if ( stricmp( argv[i], "-AutoDir" ) == 0 )
  887. {
  888. bAutoDir = true;
  889. }
  890. else if ( stricmp( argv[i], "-Transparent" ) == 0 )
  891. {
  892. g_bBMPAllowTranslucent = true;
  893. }
  894. else if ( stricmp( argv[i], "-Decal" ) == 0 )
  895. {
  896. g_bDecal = true;
  897. if ( g_pShader == g_pDefaultShader )
  898. g_pShader = "DecalModulate";
  899. }
  900. else if ( stricmp( argv[i], "-quiet" ) == 0 )
  901. {
  902. g_bQuiet = true;
  903. }
  904. else if ( stricmp( argv[i], "-vtex" ) == 0 )
  905. {
  906. bVTex = true;
  907. }
  908. }
  909. if ( bAutoDir )
  910. {
  911. if ( !DragAndDropCheck( &pBaseDir, &pSubDir, &pWadFilenames, &pBMPFilenames, &pSPRFilenames, &bVTex ) )
  912. return PrintUsage( "-AutoDir failed to setup directories." );
  913. }
  914. if ( !pBaseDir || (!pWadFilenames && !pBMPFilenames && !pSPRFilenames) )
  915. return PrintUsage( "Missing a parameter.\n" );
  916. char prefix[512];
  917. // Scan through each wadfile.
  918. if ( pWadFilenames )
  919. {
  920. ExtractDirectory( pWadFilenames, prefix );
  921. _finddata_t findData;
  922. long handle = _findfirst( pWadFilenames, &findData );
  923. if ( handle != -1 )
  924. {
  925. do
  926. {
  927. if ( !(findData.attrib & _A_SUBDIR) )
  928. {
  929. char fullFilename[512];
  930. sprintf( fullFilename, "%s\\%s", prefix, findData.name );
  931. ProcessWadFile( fullFilename, pBaseDir, pSubDir, pOnlyTex, bVTex );
  932. }
  933. } while( _findnext( handle, &findData ) == 0 );
  934. _findclose( handle );
  935. }
  936. }
  937. // Process BMP files.
  938. if ( pBMPFilenames )
  939. {
  940. ExtractDirectory( pBMPFilenames, prefix );
  941. _finddata_t findData;
  942. long handle = _findfirst( pBMPFilenames, &findData );
  943. if ( handle != -1 )
  944. {
  945. do
  946. {
  947. if ( !(findData.attrib & _A_SUBDIR) )
  948. {
  949. char fullFilename[512];
  950. sprintf( fullFilename, "%s\\%s", prefix, findData.name );
  951. ProcessBMPFile( pBaseDir, pSubDir, fullFilename, bVTex );
  952. }
  953. } while( _findnext( handle, &findData ) == 0 );
  954. _findclose( handle );
  955. }
  956. }
  957. if ( pSPRFilenames )
  958. {
  959. ExtractDirectory( pSPRFilenames, prefix );
  960. _finddata_t findData;
  961. long handle = _findfirst( pSPRFilenames, &findData );
  962. if ( handle != -1 )
  963. {
  964. do
  965. {
  966. if ( !(findData.attrib & _A_SUBDIR) )
  967. {
  968. char fullFilename[512];
  969. sprintf( fullFilename, "%s\\%s", prefix, findData.name );
  970. ProcessSPRFile( pBaseDir, pSubDir, fullFilename, bVTex );
  971. }
  972. } while( _findnext( handle, &findData ) == 0 );
  973. _findclose( handle );
  974. }
  975. }
  976. PrintExitStuff();
  977. return 0;
  978. }