diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/exe2ico.c | 170 | ||||
-rw-r--r-- | tools/ico2png.c | 251 |
2 files changed, 421 insertions, 0 deletions
diff --git a/tools/exe2ico.c b/tools/exe2ico.c new file mode 100644 index 0000000..5cf0483 --- /dev/null +++ b/tools/exe2ico.c @@ -0,0 +1,170 @@ +/* Copyright (C) 2022 June McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <SDL_rwops.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static void errx(int eval, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + putc('\n', stderr); + exit(eval); +} +static void err(int eval, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", SDL_GetError()); + exit(eval); +} + +int main(int argc, char *argv[]) { + if (argc < 2) return 1; + const char *exePath = argv[1]; + const char *name = strrchr(exePath, '/'); + name = (name ? &name[1] : exePath); + char icoPath[256]; + snprintf( + icoPath, sizeof(icoPath), "%.*s.ICO", (int)strcspn(name, "."), name + ); + + SDL_RWops *exe = SDL_RWFromFile(exePath, "rb"); + if (!exe) err(1, "%s", exePath); + + // Read the EXE header + if (SDL_ReadU8(exe) != 'M' || SDL_ReadU8(exe) != 'Z') { + errx(1, "%s: invalid MZ signature", exePath); + } + SDL_RWseek(exe, 0x3C, RW_SEEK_SET); + Uint16 neOffset = SDL_ReadLE16(exe); + SDL_RWseek(exe, neOffset, RW_SEEK_SET); + Uint8 sig[2] = { SDL_ReadU8(exe), SDL_ReadU8(exe) }; + if (sig[0] == 'P' && sig[1] == 'E') { + errx(1, "%s: not implemented for PE", exePath); + } + if (sig[0] != 'N' || sig[1] != 'E') { + errx(1, "%s: invalid NE/PE signature", exePath); + } + + // Read the NE header + SDL_RWseek(exe, neOffset + 0x24, RW_SEEK_SET); + Uint16 resourceTableOffset = neOffset + SDL_ReadLE16(exe); + + // Find the RT_GROUP_ICON resource + SDL_RWseek(exe, resourceTableOffset, RW_SEEK_SET); + Uint16 alignmentShift = SDL_ReadLE16(exe); + for (;;) { + Uint16 typeID = SDL_ReadLE16(exe); + Uint16 count = SDL_ReadLE16(exe); + SDL_ReadLE32(exe); // reserved + if (!typeID) { + errx(1, "%s: no group icon resource", exePath); + } + if (typeID == 0x800E) break; + SDL_RWseek(exe, 0x0C * count, RW_SEEK_CUR); + } + + SDL_RWops *ico = SDL_RWFromFile(icoPath, "wb"); + if (!ico) err(1, "%s", icoPath); + + // Copy the ICONHEADER + // https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513 + SDL_RWseek(exe, (Uint32)SDL_ReadLE16(exe) << alignmentShift, RW_SEEK_SET); + SDL_WriteLE16(ico, SDL_ReadLE16(exe)); // reserved + SDL_WriteLE16(ico, SDL_ReadLE16(exe)); // type + Uint16 iconDirCount = SDL_ReadLE16(exe); + SDL_WriteLE16(ico, iconDirCount); + + // Copy ICONDIRENTRY's. The entries in the RT_GROUP_ICON resource differ + // from those in ICO in their last field. In ICO, it is a 32-bit offset + // of the icon data in the ICO file. In the resource, it is a 16-bit + // resource ID of an RT_ICON resource. Here we assume the resources are + // in the same order as the entries referencing them. We translate the + // resource IDs into data offsets by simply tallying up the data lengths + // so far. The data offsets need to be aligned to alignmentShift to match + // up with the lengths of the icon resources. + Uint32 iconDirLength = 0x06 + 0x10 * iconDirCount; + Uint32 dataOffset = iconDirLength; + dataOffset += -dataOffset & ((1 << alignmentShift) - 1); + for (Uint16 i = 0; i < iconDirCount; ++i) { + SDL_WriteU8(ico, SDL_ReadU8(exe)); // width + SDL_WriteU8(ico, SDL_ReadU8(exe)); // height + SDL_WriteU8(ico, SDL_ReadU8(exe)); // colors + SDL_WriteU8(ico, SDL_ReadU8(exe)); // reserved + SDL_WriteLE16(ico, SDL_ReadLE16(exe)); // color planes + SDL_WriteLE16(ico, SDL_ReadLE16(exe)); // bits per pixel + Uint32 dataLength = SDL_ReadLE32(exe); + SDL_ReadLE16(exe); // resource ID, assumed sequential + SDL_WriteLE32(ico, dataLength); + SDL_WriteLE32(ico, dataOffset); + dataOffset += dataLength; + dataOffset += -dataOffset & ((1 << alignmentShift) - 1); + } + // Padding before the first aligned data offset + for ( + Uint32 i = 0; + i < (-iconDirLength & ((1 << alignmentShift) - 1)); + ++i + ) { + SDL_WriteU8(ico, 0); + } + + // Find the RT_ICON resources + SDL_RWseek(exe, resourceTableOffset + 0x02, RW_SEEK_SET); + Uint16 resourceCount; + for (;;) { + Uint16 typeID = SDL_ReadLE16(exe); + resourceCount = SDL_ReadLE16(exe); + SDL_ReadLE32(exe); // reserved + if (!typeID) { + errx(1, "%s: no icon resources", exePath); + } + if (typeID == 0x8003) break; + SDL_RWseek(exe, 0x0C * resourceCount, RW_SEEK_CUR); + } + + // Copy all icon data sequentially + Uint8 *data = NULL; + for (Uint16 i = 0; i < resourceCount; ++i) { + Uint32 dataOffset = (Uint32)SDL_ReadLE16(exe) << alignmentShift; + Uint32 dataLength = (Uint32)SDL_ReadLE16(exe) << alignmentShift; + SDL_ReadLE16(exe); // flags + SDL_ReadLE16(exe); // resource ID + SDL_ReadLE32(exe); // reserved + Sint64 nextResource = SDL_RWtell(exe); + if (nextResource < 0) err(1, "%s", exePath); + + SDL_RWseek(exe, dataOffset, RW_SEEK_SET); + data = realloc(data, dataLength); + if (!data) errx(1, "malloc failed"); + size_t num = SDL_RWread(exe, data, dataLength, 1); + if (!num) err(1, "%s", exePath); + num = SDL_RWwrite(ico, data, dataLength, 1); + if (!num) err(1, "%s", icoPath); + + SDL_RWseek(exe, nextResource, RW_SEEK_SET); + } + free(data); + + SDL_RWclose(ico); + SDL_RWclose(exe); +} diff --git a/tools/ico2png.c b/tools/ico2png.c new file mode 100644 index 0000000..9065f6f --- /dev/null +++ b/tools/ico2png.c @@ -0,0 +1,251 @@ +/* Copyright (C) 2022 June McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <SDL_rwops.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +static void errx(int eval, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + putc('\n', stderr); + exit(eval); +} +static void err(int eval, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", SDL_GetError()); + exit(eval); +} + +static Uint32 crcTable(Uint8 n) { + static Uint32 table[256]; + if (table[1]) return table[n]; + for (int i = 0; i < 256; ++i) { + table[i] = i; + for (int j = 0; j < 8; ++j) { + table[i] = (table[i] >> 1) ^ (table[i] & 1 ? 0xEDB88320 : 0); + } + } + return table[n]; +} + +static Uint32 pngCRC; +static void pngWrite(SDL_RWops *rw, const Uint8 *ptr, Uint32 len) { + if (!SDL_RWwrite(rw, ptr, len, 1)) err(1, "pngWrite"); + for (Uint32 i = 0; i < len; ++i) { + pngCRC = crcTable(pngCRC ^ ptr[i]) ^ (pngCRC >> 8); + } +} +static void pngUint32(SDL_RWops *rw, Uint32 n) { + Uint8 bytes[4] = { n >> 24, n >> 16, n >> 8, n }; + pngWrite(rw, bytes, sizeof(bytes)); +} +static void pngChunk(SDL_RWops *rw, const char *type, Uint32 len) { + pngUint32(rw, len); + pngCRC = ~0; + pngWrite(rw, (const Uint8 *)type, 4); +} + +// Write out image data as DEFLATE literal blocks +static void pngData(SDL_RWops *rw, const Uint8 *data, Uint32 len) { + Uint32 adler1 = 1, adler2 = 0; + for (Uint32 i = 0; i < len; ++i) { + adler1 = (adler1 + data[i]) % 65521; + adler2 = (adler1 + adler2) % 65521; + } + Uint32 zlen = 2 + 5 * ((len + 0xFFFE) / 0xFFFF) + len + 4; + pngChunk(rw, "IDAT", zlen); + Uint8 zlib[2] = { 0x08, 0x1D }; + pngWrite(rw, zlib, sizeof(zlib)); + for (; len > 0xFFFF; data += 0xFFFF, len -= 0xFFFF) { + Uint8 block[5] = { 0x00, 0xFF, 0xFF, 0x00, 0x00 }; + pngWrite(rw, block, sizeof(block)); + pngWrite(rw, data, 0xFFFF); + } + Uint8 block[5] = { 0x01, len, len >> 8, ~len, ~len >> 8 }; + pngWrite(rw, block, sizeof(block)); + pngWrite(rw, data, len); + pngUint32(rw, adler2 << 16 | adler1); + pngUint32(rw, ~pngCRC); +} + +int main(int argc, char *argv[]) { + if (argc < 4) return 1; + const char *icoPath = argv[1]; + const char *pngPath = argv[3]; + Uint32 scale = strtoul(argv[2], NULL, 10); + if (!scale) return 1; + + SDL_RWops *ico = SDL_RWFromFile(icoPath, "rb"); + if (!ico) err(1, "%s", icoPath); + + // Read the ICONHEADER + // https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513 + SDL_ReadLE16(ico); // reserved + if (SDL_ReadLE16(ico) != 0x0001) { + errx(1, "%s: not an ICO", icoPath); + } + Uint16 count = SDL_ReadLE16(ico); + + // Find the best icon + Uint32 bestScore = 0; + struct Icon { + Uint8 width; + Uint8 height; + Uint8 colors; + Uint16 planes; + Uint16 bpp; + Uint32 length; + Uint32 offset; + } bestIcon = {0}; + for (Uint16 i = 0; i < count; ++i) { + struct Icon icon = {0}; + icon.width = SDL_ReadU8(ico); + icon.height = SDL_ReadU8(ico); + icon.colors = SDL_ReadU8(ico); + SDL_ReadU8(ico); // reserved + icon.planes = SDL_ReadLE16(ico); + icon.bpp = SDL_ReadLE16(ico); // some ICOs lie about this... + icon.length = SDL_ReadLE32(ico); + icon.offset = SDL_ReadLE32(ico); + Uint32 score = icon.width * icon.height * icon.colors; + if (score > bestScore) { + bestScore = score; + bestIcon = icon; + } + } + if (!bestScore) errx(1, "%s: no icons found", icoPath); + if (bestIcon.planes != 1) { + errx(1, "%s: non-indexed bitmap not implemented", icoPath); + } + + SDL_RWops *png = SDL_RWFromFile(pngPath, "wb"); + if (!png) err(1, "%s", pngPath); + + // Write the PNG header + SDL_RWwrite(png, "\x89PNG\r\n\x1A\n", 8, 1); + pngChunk(png, "IHDR", 13); + pngUint32(png, bestIcon.width * scale); + pngUint32(png, bestIcon.height * scale); + Uint8 depth = 8; + Uint8 color = 3; // indexed + Uint8 zero[3] = {0}; + pngWrite(png, &depth, 1); + pngWrite(png, &color, 1); + pngWrite(png, zero, 3); + pngUint32(png, ~pngCRC); + + // Read the real bpp and palette count from the DIB header + SDL_RWseek(ico, bestIcon.offset, RW_SEEK_SET); + Uint32 dibHeaderLength = SDL_ReadLE32(ico); + if (dibHeaderLength != 0x28) { + errx( + 1, "%s: unrecognized DIB header length %u", + icoPath, dibHeaderLength + ); + } + SDL_RWseek(ico, 0x0A, RW_SEEK_CUR); + bestIcon.bpp = SDL_ReadLE16(ico); // the truth + SDL_RWseek(ico, 0x10, RW_SEEK_CUR); + Uint32 paletteCount = SDL_ReadLE32(ico); + if (!paletteCount) paletteCount = 1 << bestIcon.bpp; + + // Copy the palette, reserving index 0 for transparency. We assume at most + // 255 colors will be used by the icon. + Uint8 *palette = calloc(1 + paletteCount, 3); + if (!palette) errx(1, "calloc failed"); + SDL_RWseek(ico, bestIcon.offset + dibHeaderLength, RW_SEEK_SET); + for (Uint32 i = 0; i < paletteCount; ++i) { + palette[3 * (i + 1) + 2] = SDL_ReadU8(ico); + palette[3 * (i + 1) + 1] = SDL_ReadU8(ico); + palette[3 * (i + 1) + 0] = SDL_ReadU8(ico); + SDL_ReadU8(ico); + } + pngChunk(png, "PLTE", 3 * (1 + paletteCount)); + pngWrite(png, palette, 3 * (1 + paletteCount)); + pngUint32(png, ~pngCRC); + pngChunk(png, "tRNS", 1); + pngWrite(png, zero, 1); + pngUint32(png, ~pngCRC); + free(palette); + + Uint32 pngStride = 1 + bestIcon.width; + Uint8 *lines = calloc(bestIcon.height, pngStride); + if (!lines) errx(1, "calloc failed"); + + // Copy and expand the icon pixels to 8 bpp, adding 1 for the transparency + // index. + Uint32 icoPPByte = 8 / bestIcon.bpp; + Uint32 icoStride = bestIcon.width / icoPPByte; + icoStride += -icoStride & 0x03; + for (Uint32 y = bestIcon.height - 1; y < bestIcon.height; --y) { + Uint8 *ptr = &lines[y * pngStride + 1]; + Uint8 *end = &lines[(y + 1) * pngStride]; + for (Uint32 x = 0; x < icoStride; ++x) { + Uint8 byte = SDL_ReadU8(ico); + for (Uint8 i = icoPPByte - 1; i < icoPPByte; --i) { + if (ptr == end) break; + *ptr = (byte >> (i * bestIcon.bpp)) & ((1 << bestIcon.bpp) - 1); + *ptr++ += 1; + } + } + } + + // Read the 1 bpp transparency mask, setting PNG pixels to index 0. + // https://devblogs.microsoft.com/oldnewthing/20101019-00/?p=12503 + icoPPByte = 8; + icoStride = bestIcon.width / icoPPByte; + icoStride += -icoStride & 0x03; + for (Uint32 y = bestIcon.height - 1; y < bestIcon.height; --y) { + Uint8 *ptr = &lines[y * pngStride + 1]; + Uint8 *end = &lines[(y + 1) * pngStride]; + for (Uint32 x = 0; x < icoStride; ++x) { + Uint8 byte = SDL_ReadU8(ico); + for (Uint8 i = icoPPByte - 1; i < icoPPByte; --i) { + if (ptr == end) break; + if ((byte >> i) & 1) *ptr = 0; + ptr++; + } + } + } + + // Upscale with nearest neighbor + Uint32 scaledWidth = bestIcon.width * scale; + Uint32 scaledHeight = bestIcon.height * scale; + Uint32 scaledStride = 1 + scaledWidth; + Uint8 *scaled = calloc(scaledHeight, scaledStride); + for (Uint32 y = 0; y < scaledHeight; ++y) { + for (Uint32 x = 0; x < scaledWidth; ++x) { + scaled[y * scaledStride + 1 + x] = + lines[(y / scale) * pngStride + 1 + (x / scale)]; + } + } + pngData(png, scaled, scaledHeight * scaledStride); + pngChunk(png, "IEND", 0); + pngUint32(png, ~pngCRC); + + free(scaled); + free(lines); + SDL_RWclose(png); + SDL_RWclose(ico); +} |