//-------------------------------------------------------------------------------------- // File: tgadump.cpp // // TGA file content examination utility // // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. //-------------------------------------------------------------------------------------- #include #include #include namespace { enum TGAImageType { TGA_NO_IMAGE = 0, TGA_COLOR_MAPPED = 1, TGA_TRUECOLOR = 2, TGA_BLACK_AND_WHITE = 3, TGA_COLOR_MAPPED_RLE = 9, TGA_TRUECOLOR_RLE = 10, TGA_BLACKAND_WHITE_RLE = 11, }; enum TGADescriptorFlags { TGA_FLAGS_INVERTX = 0x10, TGA_FLAGS_INVERTY = 0x20, TGA_FLAGS_INTERLEAVED_2WAY = 0x40, // Deprecated TGA_FLAGS_INTERLEAVED_4WAY = 0x80, // Deprecated }; const char* g_TGA20_Signature = "TRUEVISION-XFILE."; #pragma pack(push,1) struct TGA_HEADER { BYTE bIDLength; BYTE bColorMapType; BYTE bImageType; WORD wColorMapFirst; WORD wColorMapLength; BYTE bColorMapSize; WORD wXOrigin; WORD wYOrigin; WORD wWidth; WORD wHeight; BYTE bBitsPerPixel; BYTE bDescriptor; }; static_assert(sizeof(TGA_HEADER) == 18, "TGA 2.0 size mismatch"); struct TGA_FOOTER { DWORD dwExtensionOffset; DWORD dwDeveloperOffset; CHAR Signature[18]; }; static_assert(sizeof(TGA_FOOTER) == 26, "TGA 2.0 size mismatch"); struct TGA_EXTENSION { WORD wSize; CHAR szAuthorName[41]; CHAR szAuthorComment[324]; WORD wStampMonth; WORD wStampDay; WORD wStampYear; WORD wStampHour; WORD wStampMinute; WORD wStampSecond; CHAR szJobName[41]; WORD wJobHour; WORD wJobMinute; WORD wJobSecond; CHAR szSoftwareId[41]; WORD wVersionNumber; BYTE bVersionLetter; DWORD dwKeyColor; WORD wPixelNumerator; WORD wPixelDenominator; WORD wGammaNumerator; WORD wGammaDenominator; DWORD dwColorOffset; DWORD dwStampOffset; DWORD dwScanOffset; BYTE bAttributesType; }; static_assert(sizeof(TGA_EXTENSION) == 495, "TGA 2.0 size mismatch"); #pragma pack(pop) //--------------------------------------------------------------------------------- struct handle_closer { void operator()(HANDLE h) noexcept { if (h) CloseHandle(h); } }; using ScopedHandle = std::unique_ptr; inline HANDLE safe_handle(HANDLE h) noexcept { return (h == INVALID_HANDLE_VALUE) ? nullptr : h; } //-------------------------------------------------------------------------------------- HRESULT LoadTextureDataFromFile(_In_z_ const wchar_t* fileName, std::unique_ptr& tgaData, const TGA_HEADER** ppHeader, const CHAR** ppId, const TGA_FOOTER** ppFooter, const TGA_EXTENSION** ppExt) { // open the file #if (_WIN32_WINNT >= 0x0602 /*_WIN32_WINNT_WIN8*/) ScopedHandle hFile(safe_handle(CreateFile2(fileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr))); #else ScopedHandle hFile(safe_handle(CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr))); #endif if (!hFile) return HRESULT_FROM_WIN32(GetLastError()); // Get the file size FILE_STANDARD_INFO fileInfo; if (!GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo))) { return HRESULT_FROM_WIN32(GetLastError()); } // File is too big for 32-bit allocation, so reject read if (fileInfo.EndOfFile.HighPart > 0) { return E_FAIL; } // Need at least enough data to fill the header and magic number to be a valid DDS if (fileInfo.EndOfFile.LowPart < (sizeof(TGA_HEADER))) { return E_FAIL; } // create enough space for the file data tgaData.reset(new uint8_t[fileInfo.EndOfFile.LowPart]); if (!tgaData) { return E_OUTOFMEMORY; } // read the data in DWORD BytesRead = 0; if (!ReadFile(hFile.get(), tgaData.get(), fileInfo.EndOfFile.LowPart, &BytesRead, nullptr)) { return HRESULT_FROM_WIN32(GetLastError()); } if (BytesRead < fileInfo.EndOfFile.LowPart) { return E_FAIL; } auto pHeader = reinterpret_cast(tgaData.get()); if (pHeader->bIDLength != 0) { if (fileInfo.EndOfFile.LowPart < (sizeof(TGA_HEADER) + pHeader->bIDLength)) { return E_FAIL; } } const TGA_FOOTER* pFooter = nullptr; const TGA_EXTENSION* pExtension = nullptr; if (fileInfo.EndOfFile.LowPart >= (sizeof(TGA_HEADER) + pHeader->bIDLength + sizeof(TGA_FOOTER))) { pFooter = reinterpret_cast(tgaData.get() + fileInfo.EndOfFile.LowPart - sizeof(TGA_FOOTER)); if (strncmp(pFooter->Signature, g_TGA20_Signature, 18) != 0) { pFooter = nullptr; } else if (pFooter->dwExtensionOffset != 0) { if (pFooter->dwExtensionOffset + sizeof(TGA_EXTENSION) < fileInfo.EndOfFile.LowPart) { pExtension = reinterpret_cast(tgaData.get() + pFooter->dwExtensionOffset); if (pExtension->wSize != sizeof(TGA_EXTENSION)) { pExtension = nullptr; } } } } // setup the pointers in the process request *ppHeader = pHeader; *ppFooter = pFooter; *ppExt = pExtension; if (pHeader->bIDLength != 0) { *ppId = reinterpret_cast(tgaData.get() + sizeof(TGA_HEADER)); } return S_OK; } #define ENUM_CASE(x) case x: wprintf( L## #x ); break; #define DESCRIPTOR(x) if ( header->bDescriptor & (x)) { wprintf( L"\t" L## #x ); } void OutputHeader(const TGA_HEADER* header, const wchar_t* fname, const CHAR* pId, const TGA_FOOTER* footer, const TGA_EXTENSION* ext) { wprintf(L"TGA %ls\n", fname); if (header->bIDLength != 0 && pId) { char buff[256] = {}; strncpy_s(buff, pId, header->bIDLength); buff[header->bIDLength] = 0; wprintf(L"id = \"%hs\" (%u)\n", buff, header->bIDLength); } wprintf(L"ColorMap Type = %d\n", header->bColorMapType); wprintf(L"ImageType = %d\t", header->bImageType); switch (header->bImageType) { ENUM_CASE(TGA_NO_IMAGE) ENUM_CASE(TGA_COLOR_MAPPED) ENUM_CASE(TGA_TRUECOLOR) ENUM_CASE(TGA_BLACK_AND_WHITE) ENUM_CASE(TGA_COLOR_MAPPED_RLE) ENUM_CASE(TGA_TRUECOLOR_RLE) ENUM_CASE(TGA_BLACKAND_WHITE_RLE) } wprintf(L"\n"); wprintf(L"ColorMap First = %d\n", header->wColorMapFirst); wprintf(L"ColorMap Length = %d\n", header->wColorMapLength); wprintf(L"ColorMap Entry Size = %d\n", header->bColorMapSize); wprintf(L"Origin %d, %d\n", header->wXOrigin, header->wYOrigin); wprintf(L"Size: %d by %d\n", header->wWidth, header->wHeight); wprintf(L"Bits Per Pixel %d\n", header->bBitsPerPixel); wprintf(L"Image descriptor %02X\n", header->bDescriptor); if (header->bDescriptor & 0xF) { wprintf(L"\tAttributes %d\n", (header->bDescriptor & 0xF)); } DESCRIPTOR(TGA_FLAGS_INVERTX) DESCRIPTOR(TGA_FLAGS_INVERTY) DESCRIPTOR(TGA_FLAGS_INTERLEAVED_2WAY) DESCRIPTOR(TGA_FLAGS_INTERLEAVED_4WAY) if (footer) { wprintf(L"ExtensionOffset = %lu\n", footer->dwExtensionOffset); wprintf(L"DeveloperOffset = %lu\n", footer->dwDeveloperOffset); wprintf(L"Signature= %hs\n", footer->Signature); } if (ext) { wprintf(L"Author Name = \"%hs\"\n", ext->szAuthorName); wprintf(L"Author Comment = \"%hs\"\n", ext->szAuthorComment); wprintf(L"Timestamp = %d/%d/%d %02d:%02d:%02d\n", ext->wStampMonth, ext->wStampDay, ext->wStampYear, ext->wStampHour, ext->wStampMinute, ext->wStampSecond); wprintf(L"Job Name = \"%hs\"\n", ext->szJobName); wprintf(L"Job %02d:%02d:%02d\n", ext->wJobHour, ext->wJobMinute, ext->wJobSecond); wprintf(L"Software Id = %hs (Version %d, '%c')\n", ext->szSoftwareId, ext->wVersionNumber, ext->bVersionLetter); wprintf(L"Key Color = %08lX\n", ext->dwKeyColor); wprintf(L"Pixel Aspect Ratio = %d/%d\n", ext->wPixelNumerator, ext->wPixelDenominator); if (ext->wGammaDenominator != 0) { wprintf(L"Gamma = %d/%d (%.1f)\n", ext->wGammaNumerator, ext->wGammaDenominator, double(ext->wGammaNumerator) / double(ext->wGammaDenominator)); } else { wprintf(L"Gamma = %d/%d\n", ext->wGammaNumerator, ext->wGammaDenominator); } wprintf(L"Color Offset = %lu\n", ext->dwColorOffset); wprintf(L"Stamp Offset = %lu\n", ext->dwStampOffset); wprintf(L"Scan-line Offset = %lu\n", ext->dwScanOffset); const wchar_t* attrType = L"*Unknown*"; switch (ext->bAttributesType) { case 0: attrType = L"no alpha data included"; break; case 1: attrType = L"undefined data, can be ignored"; break; case 2: attrType = L"udefined data, should be retained"; break; case 3: attrType = L"useful alpha channel data"; break; case 4: attrType = L"pre-multiplied alpha"; break; } wprintf(L"Attributes Type = %02X: %ls\n", ext->bAttributesType, attrType); } wprintf(L"\n"); } } int __cdecl wmain(int argc, wchar_t* argv[]) { if (argc != 2) { wprintf(L"Usage: %ls [filename]\n", argv[0]); return -1; } const TGA_HEADER* pHeader = nullptr; const TGA_FOOTER* pFooter = nullptr; const TGA_EXTENSION* pExtension = nullptr; const CHAR* pId = nullptr; std::unique_ptr tgaData; HRESULT hr = LoadTextureDataFromFile(argv[1], tgaData, &pHeader, &pId, &pFooter, &pExtension); if (FAILED(hr)) { wprintf(L"ERROR: failed to load %ls\n", argv[1]); return -1; } OutputHeader(pHeader, argv[1], pId, pFooter, pExtension); return 0; }