diff --git a/Source/functions.c b/Source/functions.c index e6723d6..575d57e 100644 --- a/Source/functions.c +++ b/Source/functions.c @@ -2,6 +2,8 @@ #include #include #include +#include "functions.h" + char* readFile(char *inputFile){ // This function reads a file into memory, and returns a pointer printf("Reading binary file %s...\n", inputFile); @@ -22,6 +24,86 @@ char* readFile(char *inputFile){ fclose(levelFile); // Closes file return bytelist; } + +// This function requires a 24-bit bitmap with no compression +// The caller of the function is responsible for freeing the returned BYTE* memory +// Returns the pixel array in 3-byte RGB pixels, with rows bottom-to-top +BYTE* readBitmapFile(char* inputFilename) +{ + char* image = readFile(inputFilename); + + // Check bitmap integrity + BYTE bmpIntegrityA; + BYTE bmpIntegrityB; + memcpy(&bmpIntegrityA, image + 0, 1); + memcpy(&bmpIntegrityB, image + 1, 1); + //printf("Bitmap integrity: %c%c\n", bmpIntegrityA, bmpIntegrityB); + if (!(bmpIntegrityA == 'B' && bmpIntegrityB == 'M')) + { + printf("ERROR: File must be BM for Windows 3.1x, 95, NT, etc.\n"); + free(image); + return NULL; + } + + // Verify that the bitmap is 24 bits per pixel + unsigned short bitsPerPixel; + memcpy(&bitsPerPixel, image + 28, 2); + //printf("Bitmap bpp: %d", bitsPerPixel); + if (!bitsPerPixel == 24) + { + printf("ERROR: Bitmap must be 24 bits per pixel.\n"); + free(image); + return NULL; + } + + // Verify image is uncompressed + signed int compression; + memcpy(&compression, image + 30, 4); + //printf("Compression method: %d", compression); + if (compression != 0) + { + printf("ERROR: Bitmap must be uncompressed (found compression method %d, must be 0 a.k.a. BI_RGB).\n", compression); + free(image); + return NULL; + } + + // Read width and height + signed int width; + signed int height; + memcpy(&width, image + 18, 4); + memcpy(&height, image + 22, 4); + //printf("Width x height: %d x %d\n", width, height); + + // Read image size + signed int imageSize; + memcpy(&imageSize, image + 34, 4); + if (imageSize == 0) + { + // For BI_RGB, a dummy 0 may indicate bpp x w x h + imageSize = bitsPerPixel * width * height; + } + printf("Image size: %d\n", imageSize); + + // Allocate memory for result + BYTE* result = malloc(imageSize * sizeof(BYTE)); + if (result == 0) + { + printf("ERROR: Could not allocate memory to read bmp! (error code: %d)\n", errno); + return NULL; + } + + // Determine the offset of the pixel array + signed int offset; + memcpy(&offset, image + 10, 4); + //printf("Bitmap pixel array offset: %d", offset); + + memcpy(result, image + offset, imageSize); + + free(image); + + return result; +} + void writeFile(char *outputFile, char *bytelist, unsigned fsize){ // This function writes bytelist to the level file. FILE *File = fopen(outputFile,"wb"); @@ -84,6 +166,9 @@ unsigned removerange(char *bytelist, unsigned offset, unsigned length, unsigned return (fsize-length); } +// This function writes a 24-bit bitmap (R8 G8 B8) +// In TR1, full black (0, 0, 0) represents transparent +// In TR2, full magenta (255, 0, 255) represents transparent void writeBitmap(char* img, char* filename, const unsigned w, const unsigned h) { FILE* f; @@ -118,3 +203,89 @@ void writeBitmap(char* img, char* filename, const unsigned w, const unsigned h) fclose(f); } + +void writeBitmap32(char* img, char* filename, const unsigned w, const unsigned h) +{ + FILE* f; + int bmpFileSize = 54 + 4 * w * h; + + unsigned char bmpfileheader[14] = { 'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0 }; + unsigned char bmpinfoheader[40] = { 40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 32,0 }; + + bmpfileheader[2] = (unsigned char)(bmpFileSize); + bmpfileheader[3] = (unsigned char)(bmpFileSize >> 8); + bmpfileheader[4] = (unsigned char)(bmpFileSize >> 16); + bmpfileheader[5] = (unsigned char)(bmpFileSize >> 24); + + bmpinfoheader[4] = (unsigned char)(w); + bmpinfoheader[5] = (unsigned char)(w >> 8); + bmpinfoheader[6] = (unsigned char)(w >> 16); + bmpinfoheader[7] = (unsigned char)(w >> 24); + bmpinfoheader[8] = (unsigned char)(h); + bmpinfoheader[9] = (unsigned char)(h >> 8); + bmpinfoheader[10] = (unsigned char)(h >> 16); + bmpinfoheader[11] = (unsigned char)(h >> 24); + + f = fopen(filename, "wb"); + fwrite(bmpfileheader, 1, 14, f); + fwrite(bmpinfoheader, 1, 40, f); + for (int i = 0; i < h; i++) + { + fwrite(img + (w * (h - i - 1) * 4), 4, w, f); + } + + fclose(f); +} + +BYTE findPixelKey(BYTE* palette, BYTE r, BYTE g, BYTE b) +{ + BYTE pr, pg, pb; + BYTE result; + short closestMatch = 255 * 3; + short diff; + BYTE isExactMatch = 0; + BYTE rpr, rpg, rpb; + for (int i = 0; i < 256; i++) + { + short curpos = i * 3; + memcpy(&pr, palette + curpos + 2, 1); + memcpy(&pg, palette + curpos + 1, 1); + memcpy(&pb, palette + curpos + 0, 1); + + if (pr == r && + pg == g && + pb == b) + { + result = i; + isExactMatch = 1; + break; + } + + diff = abs(pr - r) + + abs(pg - g) + + abs(pb - b); + + if (diff < closestMatch) + { + closestMatch = diff; + result = i; + rpr = pr; + rpg = pg; + rpb = pb; + isExactMatch = 0; + } + } + + /* + if (isExactMatch == 1) + { + printf("Exact match: %d, %d, %d matches palette %d\n", r, g, b, result); + } + else + { + printf("No match: %d, %d, %d is closest to palette %d (%d, %d, %d)\n", r, g, b, result, rpr, rpg, rpb); + } + */ + + return result; +} diff --git a/Source/functions.h b/Source/functions.h index ca2f3ac..871e932 100644 --- a/Source/functions.h +++ b/Source/functions.h @@ -1,7 +1,10 @@ #ifndef FUNCTIONS_H_INCLUDED #define FUNCTIONS_H_INCLUDED +typedef unsigned char BYTE; + char* readFile(char *inputFile); +BYTE* readBitmapFile(char* inputFilename); void writeFile(char *outputFile, char *bytelist, unsigned fsize); unsigned filesize(char *inputFile); char* substring(char *string, unsigned offset, unsigned length); @@ -10,5 +13,6 @@ unsigned countchar(char *instring, char countChar); char* insertrange(char *bytelist, unsigned offset, unsigned length, char *insertme, unsigned fsize); unsigned removerange(char *bytelist, unsigned offset, unsigned length, unsigned fsize); void writeBitmap(char* img, char* filename, const unsigned w, const unsigned h); +BYTE findPixelKey(BYTE* palette, BYTE r, BYTE g, BYTE b); #endif \ No newline at end of file diff --git a/Source/tr1pc.c b/Source/tr1pc.c index d7871ee..ea83356 100644 --- a/Source/tr1pc.c +++ b/Source/tr1pc.c @@ -250,6 +250,14 @@ void tr1pc_main(int argc, char *args[], char *bytelist, unsigned fsize){ if (!_strnicmp(args[2],"get",3)&&!_strnicmp(args[3],"offset",6)) tr1pc_get_offset(bytelist, args, argc, p_NumVertices, p_NumSprites, p_NumLights, p_NumZSector, p_NumStaticMeshes, p_AlternateRoom, p_NumFloorData, p_NumItems, p_NumSpriteSequences, p_NumCameras, p_NumDemoData, p_NumBoxes, p_NumOverlaps, p_NumSoundDetails, p_NumSamples, p_NumSampleIndices, p_NumSoundSources, fsize); if (!_strnicmp(args[2],"extract",7)&&!strnicmp(args[3],"textile",7)) tr1pc_extract_textile(bytelist, args, numTexTiles, p_TexTiles, palette, !strnicmp(args[4],"all",3) ? -1 : atoi(args[4])); + if (!_strnicmp(args[2], "replace", 7)) + { + if (!strnicmp(args[3], "textile", 7)) + { + tr1pc_replace_textile(bytelist, args, numTexTiles, p_TexTiles, palette, fsize); + } + } + free(p_TexTiles); free(roomX); free(roomY); @@ -3152,8 +3160,9 @@ void tr1pc_get_offset(char *bytelist, char *args[], unsigned argc, unsigned p_Nu if (!_strnicmp(args[4],"numsampleindices",16)) printf("%X\n",p_NumSampleIndices); } +// This function extracts a texture tile from the level into a bitmap file. The syntax is "trmod [FILE] EXTRACT TEXTILE [#/All]". // int selection // -1 for all, 0 - (numTexTiles-1) for an individual texture tile -void tr1pc_extract_textile(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles, unsigned char* palette, int selection) +void tr1pc_extract_textile(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles, BYTE* palette, int selection) { if (selection < -1 || selection >= (int)numTexTiles) { @@ -3162,7 +3171,6 @@ void tr1pc_extract_textile(char* bytelist, char* args[], unsigned numTexTiles, u } printf("Extracting textile %d...\n", selection); - // This function extracts a texture tile from the level into a bitmap file. The syntax is "trmod [FILE] EXTRACT TEXTILE [#/All]". printf("Writing \"palette.bmp\"...\n"); writeBitmap(palette, "palette.bmp", 16, 16); @@ -3170,9 +3178,9 @@ void tr1pc_extract_textile(char* bytelist, char* args[], unsigned numTexTiles, u const unsigned w = 256; const unsigned h = 256; - for (int t = (selection == -1 ? 0 : selection); t <= (selection == -1 ? numTexTiles-1 : selection); ++t) + for (int t = (selection == -1 ? 0 : selection); t <= (selection == -1 ? numTexTiles - 1 : selection); ++t) { - unsigned char* img = (char*)malloc(3 * w * h); + BYTE* img = malloc(3 * w * h); memset(img, 0, 3 * w * h); // Fill the whole image with 0s for (int j = 0; j < h; j++) @@ -3215,4 +3223,61 @@ void tr1pc_extract_textile(char* bytelist, char* args[], unsigned numTexTiles, u free(img); free(filename); } +} + +// This function replaces a texture tile in the level from a bitmap file. The syntax is "trmod [FILE] REPLACE TEXTILE [#] [FILENAME.BMP]". +void tr1pc_replace_textile(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles, BYTE * palette, unsigned fsize) +{ + unsigned int selection = atoi(args[4]); + char* filename = args[5]; + + if (selection < 0 || selection >= numTexTiles) + { + printf("ERROR: Textile selection must be between \"0\" and \"%d\", argument was \"%d\"", numTexTiles - 1, selection); + return 0; + } + + printf("Replacing textile %d...\n", selection); + + const short w = 256; + const short h = 256; + + BYTE* bitmapImage = readBitmapFile(filename); + if (bitmapImage == NULL) + { + printf("ERROR: Could not read bitmap.\n"); + return 0; + } + + for (int j = 0; j < h; j++) + { + for (int i = 0; i < w; i++) + { + unsigned int curpos = j * w + i; + BYTE r; + BYTE g; + BYTE b; + BYTE pixelKey; + + memcpy(&r, bitmapImage + curpos * 3 + 2, 1); + memcpy(&g, bitmapImage + curpos * 3 + 1, 1); + memcpy(&b, bitmapImage + curpos * 3 + 0, 1); + + if (r == 0 && g == 0 && b == 0) + { + pixelKey = 0; // Reserved for pure-black aka transparent + } + else + { + pixelKey = findPixelKey(palette, r, g, b); + } + + unsigned int curposFlip = ((h - 1 - j) * w) + i; // Bitmap rows are stored bottom-up + memcpy(bytelist + p_TexTiles[selection] + curposFlip, &pixelKey, 1); + } + } + + writeFile(args[1], bytelist, fsize); + + free(bitmapImage); } \ No newline at end of file diff --git a/Source/tr1pc.h b/Source/tr1pc.h index fde23aa..fe761a1 100644 --- a/Source/tr1pc.h +++ b/Source/tr1pc.h @@ -1,6 +1,8 @@ #ifndef TR1PC_H_INCLUDED #define TR1PC_H_INCLUDED +typedef unsigned char BYTE; + void tr1pc_main(int argc, char *args[], char *bytelist, unsigned fsize); void tr1pc_list(char *bytelist, char *args[], unsigned roomX[], int roomY[], unsigned roomZ[], unsigned p_NumVertices[], unsigned p_NumSprites[], unsigned p_NumLights[], unsigned p_NumZSector[], unsigned p_NumStaticMeshes[], unsigned p_AlternateRoom[], unsigned p_NumFloorData, unsigned p_NumSpriteSequences, unsigned p_NumCameras, unsigned p_NumBoxes, unsigned p_NumSoundSources, unsigned p_NumItems, unsigned short numRooms, unsigned fsize); void tr1pc_add_item(char *bytelist, char *args[], unsigned roomX[], int roomY[], unsigned roomZ[], unsigned p_NumItems, unsigned fsize); @@ -59,6 +61,7 @@ void tr1pc_replace_zone(char *bytelist, char *args[], unsigned p_NumBoxes, unsig void tr1pc_add_overlap(char *bytelist, char *args[], unsigned argc, unsigned p_NumOverlaps, unsigned fsize); void tr1pc_overwrite_overlap(char *bytelist, char *args[], unsigned argc, unsigned p_NumOverlaps, unsigned fsize); void tr1pc_get_offset(char *bytelist, char *args[], unsigned argc, unsigned p_NumVertices[], unsigned p_NumSprites[], unsigned p_NumLights[], unsigned p_NumZSector[], unsigned p_NumStaticMeshes[], unsigned p_AlternateRoom[], unsigned p_NumFloorData, unsigned p_NumItems, unsigned p_NumSpriteSequences, unsigned p_NumCameras, unsigned p_NumDemoData, unsigned p_NumBoxes, unsigned p_NumOverlaps, unsigned p_NumSoundDetails, unsigned p_NumSamples, unsigned p_NumSampleIndices, unsigned p_NumSoundSources, unsigned fsize); -void tr1pc_extract_textile(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles, unsigned char* palette, int selection); +void tr1pc_extract_textile(char *bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles, BYTE* palette, int selection); +void tr1pc_replace_textile(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles, BYTE* palette, unsigned fsize); #endif diff --git a/Source/tr1psx.c b/Source/tr1psx.c index 545020c..1c3d032 100644 --- a/Source/tr1psx.c +++ b/Source/tr1psx.c @@ -178,6 +178,14 @@ void tr1psx_main(int argc, char *args[], char *bytelist, unsigned fsize){ if (!_strnicmp(args[2],"get",3)&&!_strnicmp(args[3],"offset",6)) tr1psx_get_offset(bytelist, args, argc, p_NumVertices, p_NumSprites, p_NumLights, p_NumZSector, p_NumStaticMeshes, p_AlternateRoom, p_NumFloorData, p_NumItems, p_NumSpriteSequences, p_NumCameras, p_NumBoxes, p_NumOverlaps, p_NumSoundSources, fsize); if (!_strnicmp(args[2], "extract", 7) && !strnicmp(args[3], "textile", 7)) printf("EXTRACT TEXTILE is not supported for PSX file format."); + if (!_strnicmp(args[2], "replace", 7)) + { + if (!strnicmp(args[3], "textile", 7)) + { + printf("REPLACE TEXTILE is not supported for PSX file format."); + } + } + free(roomX); free(roomY); free(roomZ); diff --git a/Source/tr2pc.c b/Source/tr2pc.c index 8b54515..806333d 100644 --- a/Source/tr2pc.c +++ b/Source/tr2pc.c @@ -49,6 +49,7 @@ void tr2pc_main(int argc, char *args[], char *bytelist, unsigned fsize){ // 16-bit palette // NOTE: There is no reason for us to store the unused byte, so we'll discard it for a smaller memory footprint. // tr_colour4 Palette16[256]; // 16-bit palette (1024 bytes) + // The 16-bit palette is used for identifying the colors of solid polygons, *not* textile colors. // struct tr_colour4 // 4 bytes // { // uint8_t Red; @@ -69,18 +70,23 @@ void tr2pc_main(int argc, char *args[], char *bytelist, unsigned fsize){ // Texture tiles // uint32_t NumTextiles; // number of texture tiles (4 bytes) - // tr2_textile8 Textile8[NumTextiles]; // 8-bit (palettized) textiles (NumTextiles * 65536 bytes) - // tr2_textile16 Textile16[NumTextiles]; // 16-bit (ARGB) textiles (NumTextiles * 131072 bytes) memcpy(&freeuint1, bytelist + curpos, 4); // Number of Texture Tiles numTexTiles = freeuint1; curpos += 4; + // tr2_textile8 Textile8[NumTextiles]; // 8-bit (palettized) textiles (NumTextiles * 65536 bytes) p_TexTiles8 = malloc(numTexTiles * sizeof(unsigned)); // Offsets of 8-bit TexTiles for (int i = 0; i < numTexTiles; i++) { p_TexTiles8[i] = curpos; curpos += (1 << 16); // Each texture is 8 bits in a 256 x 256 grid a.k.a. 2^16 } + + // tr2_textile16 Textile16[NumTextiles]; // 16-bit (ARGB) textiles (NumTextiles * 131072 bytes) + // The 16-bit textile array, which contains[tr_textile16] structures, specifies colours using 16 - bit ARGB, + // where the highest bit (0x8000) is a crude alpha channel (really just a simple transparency: 0==transparent, 1==opaque). + // The next 5 bits(0x7C00) specify the red channel, the next 5 bits(0x03E0) specify the green channel, + // and the last 5 bits(0x001F) specify the blue channel, each on a scale from 0..31. p_TexTiles16 = malloc(numTexTiles * sizeof(unsigned)); // Offsets of 16-bit TexTiles for (int i = 0; i < numTexTiles; i++) { @@ -248,7 +254,32 @@ void tr2pc_main(int argc, char *args[], char *bytelist, unsigned fsize){ if (!_strnicmp(args[2],"add",3)&&!_strnicmp(args[3],"overlap",7)) tr1pc_add_overlap(bytelist, args, argc, p_NumOverlaps, fsize); if (!_strnicmp(args[2],"overwrite",7)&&!_strnicmp(args[3],"overlap",7)) tr1pc_overwrite_overlap(bytelist, args, argc, p_NumOverlaps, fsize); if (!_strnicmp(args[2],"get",3)&&!_strnicmp(args[3],"offset",6)) tr2pc_get_offset(bytelist, args, argc, p_NumVertices, p_NumSprites, p_NumLights, p_NumZSector, p_NumStaticMeshes, p_AlternateRoom, p_NumFloorData, p_NumItems, p_NumSpriteSequences, p_NumCameras, p_NumDemoData, p_NumBoxes, p_NumOverlaps, p_NumSoundSources, fsize); - if (!_strnicmp(args[2], "extract", 7) && !strnicmp(args[3], "textile", 7)) tr2pc_extract_textile(bytelist, args, numTexTiles, p_TexTiles8, palette8, !strnicmp(args[4], "all", 3) ? -1 : atoi(args[4])); + + if (!_strnicmp(args[2], "extract", 7)) + { + if (!strnicmp(args[3], "textile", 9)) + { + tr2pc_extract_textile(bytelist, args, numTexTiles, p_TexTiles16, !strnicmp(args[4], "all", 3) ? -1 : atoi(args[4])); + } + else if (!strnicmp(args[3], "textile8", 7)) + { + printf("WARNING: 8-bit textures are a legacy feature, and will probably not render on a modern PC.\n"); + tr2pc_extract_textile8(bytelist, args, numTexTiles, p_TexTiles8, palette8, !strnicmp(args[4], "all", 3) ? -1 : atoi(args[4])); + } + } + + if (!_strnicmp(args[2], "replace", 7)) + { + if (!strnicmp(args[3], "textile", 9)) + { + tr2pc_replace_textile(bytelist, args, numTexTiles, p_TexTiles16, fsize); + } + else if (!strnicmp(args[3], "textile8", 7)) + { + printf("WARNING: 8-bit textures are a legacy feature, and will probably not render on a modern PC.\n"); + tr2pc_replace_textile8(bytelist, args, numTexTiles, p_TexTiles8, palette8, fsize); + } + } free(palette8); free(palette16); @@ -2901,7 +2932,7 @@ void tr2pc_get_offset(char *bytelist, char *args[], unsigned argc, unsigned p_Nu } // int selection // -1 for all, 0 - (numTexTiles-1) for an individual texture tile -void tr2pc_extract_textile(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles8, BYTE* palette8, int selection) +void tr2pc_extract_textile8(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles8, BYTE* palette8, int selection) { if (selection < -1 || selection >= (int)numTexTiles) { @@ -2964,3 +2995,206 @@ void tr2pc_extract_textile(char* bytelist, char* args[], unsigned numTexTiles, u free(filename); } } + +// This function replaces a texture tile in the level from a bitmap file. The syntax is "trmod [FILE] REPLACE TEXTILE [#] [FILENAME.BMP]". +void tr2pc_replace_textile8(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles, BYTE* palette8, unsigned fsize) +{ + unsigned int selection = atoi(args[4]); + char* filename = args[5]; + + if (selection < 0 || selection >= numTexTiles) + { + printf("ERROR: Textile selection must be between \"0\" and \"%d\", argument was \"%d\"", numTexTiles - 1, selection); + return 0; + } + + printf("Replacing 8-bit textile %d...\n", selection); + + const short w = 256; + const short h = 256; + unsigned memorySize = 3 * w * h; + + BYTE* bitmapImage = readBitmapFile(filename); + if (bitmapImage == NULL) + { + printf("ERROR: Could not read bitmap.\n"); + return 0; + } + + for (int j = 0; j < h; j++) + { + for (int i = 0; i < w; i++) + { + unsigned int curpos = j * w + i; + BYTE r; + BYTE g; + BYTE b; + BYTE pixelKey; + + memcpy(&r, bitmapImage + curpos * 3 + 2, 1); + memcpy(&g, bitmapImage + curpos * 3 + 1, 1); + memcpy(&b, bitmapImage + curpos * 3 + 0, 1); + + if (r == 255 && g == 0 && b == 255) + { + pixelKey = 0; // Reserved for pure-magenta aka transparent + } + else + { + pixelKey = findPixelKey(palette8, r, g, b); + } + + unsigned int curposFlip = ((h - 1 - j) * w) + i; // Bitmap rows are stored bottom-up + memcpy(bytelist + p_TexTiles[selection] + curposFlip, &pixelKey, 1); + } + } + + writeFile(args[1], bytelist, fsize); + + free(bitmapImage); +} + +// tr2_textile16 Textile16[NumTextiles]; // 16-bit (ARGB) textiles (NumTextiles * 131072 bytes) +// The 16-bit textile array, which contains[tr_textile16] structures, specifies colours using 16 - bit ARGB, +// where the highest bit (0x8000) is a crude alpha channel (really just a simple transparency: 0==transparent, 1==opaque). +// The next 5 bits(0x7C00) specify the red channel, the next 5 bits(0x03E0) specify the green channel, +// and the last 5 bits(0x001F) specify the blue channel, each on a scale from 0..31. +void tr2pc_extract_textile(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles16, int selection) +{ + if (selection < -1 || selection >= (int)numTexTiles) + { + printf("ERROR: Textile selection must be between \"0\" and \"%d\" or \"all\", argument was \"%d\"", numTexTiles - 1, selection); + return 0; + } + + if (selection == -1) + { + printf("Extracting all 16-bit textiles to 24-bit bitmap (BMP)..."); + } + { + printf("Extracting 16-bit textile %d to 24-bit bitmap (BMP)...\n", selection); + } + + // This function extracts a texture tile from the level into a bitmap file. The syntax is "trmod [FILE] EXTRACT TEXTILE [#/All]". + + const unsigned short w = 256; + const unsigned short h = 256; + + for (int t = (selection == -1 ? 0 : selection); t <= (selection == -1 ? numTexTiles - 1 : selection); ++t) + { + BYTE* img = malloc(3 * w * h); + + for (int j = 0; j < h; j++) + { + for (int i = 0; i < w; i++) + { + unsigned int curpos = (j * w + i) * 2; + unsigned short pixel16; + BYTE a; + BYTE r; + BYTE g; + BYTE b; + + memcpy(&pixel16, bytelist + p_TexTiles16[t] + curpos, 2); + + // Crack it + a = pixel16 >> 15; + r = (pixel16 >> 10) & 0b11111; // 5-bit red channel + g = (pixel16 >> 5) & 0b11111; // 5-bit green channel + b = pixel16 & 0b11111; // 5-bit blue channel + + if (a == 0) + { + // TR2 transparency - full magenta + img[i * 3 + j * w * 3 + 2] = 255; // 8-bit red + img[i * 3 + j * w * 3 + 1] = 0; // 8-bit green + img[i * 3 + j * w * 3 + 0] = 255; // 8-bit blue + } + else + { + // Full color / full opaque + img[i * 3 + j * w * 3 + 2] = r * 255 / 31; // Convert to 8-bit red + img[i * 3 + j * w * 3 + 1] = g * 255 / 31; // Convert to 8-bit green + img[i * 3 + j * w * 3 + 0] = b * 255 / 31; // Convert to 8-bit blue + } + } + } + + unsigned fnameLen = 12 + (t < 10 ? 1 : 2); + char* filename = malloc(fnameLen); + snprintf(filename, fnameLen, "textile%u.bmp", t); + printf("Writing \"%s\"...\n", filename); + writeBitmap(img, filename, 256, 256); + + free(img); + free(filename); + } +} + +// This function replaces a texture tile in the level from a bitmap file. The syntax is "trmod [FILE] REPLACE TEXTILE [#] [FILENAME.BMP]". +// The 16-bit textile array, which contains[tr_textile16] structures, specifies colours using 16 - bit ARGB, +// where the highest bit (0x8000) is a crude alpha channel (really just a simple transparency: 0==transparent, 1==opaque). +// The next 5 bits(0x7C00) specify the red channel, the next 5 bits(0x03E0) specify the green channel, +// and the last 5 bits(0x001F) specify the blue channel, each on a scale from 0..31. +void tr2pc_replace_textile(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles, unsigned fsize) +{ + unsigned int selection = atoi(args[4]); + char* filename = args[5]; + + if (selection < 0 || selection >= numTexTiles) + { + printf("ERROR: Textile selection must be between \"0\" and \"%d\", argument was \"%d\"", numTexTiles - 1, selection); + return 0; + } + + printf("Replacing 16-bit textile %d with 24-bit bitmap (BMP)...\n", selection); + + const short w = 256; + const short h = 256; + + BYTE* bitmapImage = readBitmapFile(filename); + if (bitmapImage == NULL) + { + printf("ERROR: Could not read 24-bit bitmap \"%s\".\n", filename); + return 0; + } + + for (int j = 0; j < h; j++) + { + for (int i = 0; i < w; i++) + { + unsigned int curpos = j * w + i; + BYTE a; + BYTE r; + BYTE g; + BYTE b; + unsigned short pixel16; + + memcpy(&r, bitmapImage + curpos * 3 + 2, 1); + memcpy(&g, bitmapImage + curpos * 3 + 1, 1); + memcpy(&b, bitmapImage + curpos * 3 + 0, 1); + + if (r == 255 && g == 0 && b == 255) + { + pixel16 = 0; // Reserved for pure-magenta aka transparent + } + else + { + a = 1; // Full color / full opaque + r = r * 31 / 255; // Convert to 5-bit red + g = g * 31 / 255; // Convert to 5-bit green + b = b * 31 / 255; // Convert to 5-bit blue + + // Pack it + pixel16 = (a << 15) | (r << 10) | (g << 5) | b; + } + + unsigned int curposFlip = (((h - 1 - j) * w) + i) * 2; // Bitmap rows are stored bottom-up + memcpy(bytelist + p_TexTiles[selection] + curposFlip, &pixel16, 2); + } + } + + writeFile(args[1], bytelist, fsize); + + free(bitmapImage); +} diff --git a/Source/tr2pc.h b/Source/tr2pc.h index 67bd27f..2f207ed 100644 --- a/Source/tr2pc.h +++ b/Source/tr2pc.h @@ -39,6 +39,9 @@ void tr2pc_get_zones(char *bytelist, char *args[], unsigned p_NumBoxes, unsigned void tr2pc_add_zone(char *bytelist, char *args[], unsigned p_NumBoxes, unsigned p_NumOverlaps, unsigned fsize); void tr2pc_replace_zone(char *bytelist, char *args[], unsigned p_NumBoxes, unsigned p_NumOverlaps, unsigned fsize); void tr2pc_get_offset(char *bytelist, char *args[], unsigned argc, unsigned p_NumVertices[], unsigned p_NumSprites[], unsigned p_NumLights[], unsigned p_NumZSector[], unsigned p_NumStaticMeshes[], unsigned p_AlternateRoom[], unsigned p_NumFloorData, unsigned p_NumItems, unsigned p_NumSpriteSequences, unsigned p_NumCameras, unsigned p_NumDemoData, unsigned p_NumBoxes, unsigned p_NumOverlaps, unsigned p_NumSoundSources, unsigned fsize); -void tr2pc_extract_textile(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles, BYTE* palette8, int selection); +void tr2pc_extract_textile8(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles, BYTE* palette8, int selection); +void tr2pc_extract_textile(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles16, int selection); +void tr2pc_replace_textile8(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles, BYTE* palette8, unsigned fsize); +void tr2pc_replace_textile(char* bytelist, char* args[], unsigned numTexTiles, unsigned* p_TexTiles, unsigned fsize); #endif