/* Copyright (C) 2019 C. McEnroe * * 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 . */ #include #include #include #include "cards.h" static SDL_Surface * dibSurface(SDL_RWops *rw, Uint32 offset, Uint32 length) { Uint32 bitmapLength = 0x0E + length; Uint8 *buffer = malloc(bitmapLength); if (!buffer) { SDL_SetError("malloc error: %s", strerror(errno)); return NULL; } SDL_RWops *bitmap = SDL_RWFromMem(buffer, bitmapLength); if (!bitmap) { free(buffer); return NULL; } if (SDL_RWseek(rw, offset, RW_SEEK_SET) < 0) goto fail; Uint32 dibHeaderLength = SDL_ReadLE32(rw); Uint32 bitmapDataOffset = 0x0E + dibHeaderLength; if (dibHeaderLength == 0x0C) { if (SDL_RWseek(rw, 0x06, RW_SEEK_CUR) < 0) goto fail; Uint16 bitsPerPixel = SDL_ReadLE16(rw); bitmapDataOffset += 3 * (1 << bitsPerPixel); } else if (dibHeaderLength == 0x28) { if (SDL_RWseek(rw, 0x0A, RW_SEEK_CUR) < 0) goto fail; Uint16 bitsPerPixel = SDL_ReadLE16(rw); if (SDL_RWseek(rw, 0x10, RW_SEEK_CUR) < 0) goto fail; Uint32 paletteLength = SDL_ReadLE32(rw); if (!paletteLength && bitsPerPixel < 16) { paletteLength = 1 << bitsPerPixel; } bitmapDataOffset += 4 * paletteLength; } else { SDL_SetError("unrecognized DIB header length %u", dibHeaderLength); goto fail; } SDL_WriteU8(bitmap, 'B'); SDL_WriteU8(bitmap, 'M'); SDL_WriteLE32(bitmap, bitmapLength); SDL_WriteLE16(bitmap, 0); // reserved SDL_WriteLE16(bitmap, 0); // reserved SDL_WriteLE32(bitmap, bitmapDataOffset); if (SDL_RWseek(rw, offset, RW_SEEK_SET) < 0) goto fail; if (SDL_RWread(rw, &buffer[SDL_RWtell(bitmap)], length, 1) < 1) goto fail; SDL_RWseek(bitmap, 0, RW_SEEK_SET); SDL_Surface *surface = SDL_LoadBMP_RW(bitmap, 1); free(buffer); return surface; fail: SDL_RWclose(bitmap); free(buffer); return NULL; } static int setColorKey(struct Cards *cards) { int i = Cards_Empty; if (SDL_SetColorKey(cards->surfaces[i], SDL_TRUE, 1) < 0) return -1; for (i = Cards_X; i <= Cards_O; ++i) { if (SDL_SetColorKey(cards->surfaces[i], SDL_TRUE, 12) < 0) return -1; } return 0; } static int setAlphaCorners(struct Cards *cards) { SDL_Surface *alpha = NULL; for (int i = 0; i < Cards_Count; ++i) { if (!cards->surfaces[i]) continue; alpha = SDL_ConvertSurfaceFormat( cards->surfaces[i], SDL_PIXELFORMAT_RGBA32, 0 ); if (!alpha) return -1; if (SDL_SetSurfaceBlendMode(alpha, SDL_BLENDMODE_BLEND) < 0) goto fail; SDL_Rect rects[8] = { { 0, 0, 2, 1 }, { 0, 1, 1, 1 }, { Cards_Width - 2, 0, 2, 1 }, { Cards_Width - 1, 1, 1, 1 }, { 0, Cards_Height - 1, 2, 1 }, { 0, Cards_Height - 2, 1, 1 }, { Cards_Width - 2, Cards_Height - 1, 2, 1 }, { Cards_Width - 1, Cards_Height - 2, 1, 1 }, }; Uint32 trans = SDL_MapRGBA(alpha->format, 0x00, 0x00, 0x00, 0x00); if (SDL_FillRects(alpha, rects, 8, trans) < 0) goto fail; SDL_FreeSurface(cards->surfaces[i]); cards->surfaces[i] = alpha; } return 0; fail: SDL_FreeSurface(alpha); return -1; } static int setBlackBorders(struct Cards *cards) { for (int i = Cards_Diamond + Cards_A; i <= Cards_Heart + Cards_K; ++i) { if (!cards->surfaces[i]) continue; SDL_Rect rects[8] = { { 2, 0, Cards_Width - 4, 1 }, { 2, Cards_Height - 1, Cards_Width - 4, 1 }, { 0, 2, 1, Cards_Height - 4 }, { Cards_Width - 1, 2, 1, Cards_Height - 4 }, { 1, 1, 1, 1 }, { Cards_Width - 2, 1, 1, 1 }, { 1, Cards_Height - 2, 1, 1 }, { Cards_Width - 2, Cards_Height - 2, 1, 1 }, }; Uint32 black = SDL_MapRGB(cards->surfaces[i]->format, 0x00, 0x00, 0x00); if (SDL_FillRects(cards->surfaces[i], rects, 8, black) < 0) return -1; } return 0; } // exefmt.txt static int loadNE(SDL_Surface **surfaces, size_t count, SDL_RWops *rw, Uint16 neOffset) { if (SDL_RWseek(rw, neOffset + 0x24, RW_SEEK_SET) < 0) return -1; Uint16 resourceTableOffset = neOffset + SDL_ReadLE16(rw); if (SDL_RWseek(rw, resourceTableOffset, RW_SEEK_SET) < 0) return -1; Uint16 alignmentShift = SDL_ReadLE16(rw); Uint16 resourceCount; for (;;) { Uint16 typeID = SDL_ReadLE16(rw); resourceCount = SDL_ReadLE16(rw); SDL_ReadLE32(rw); // reserved if (!typeID) { SDL_SetError("no bitmap resources"); return -1; } if (typeID == 0x8002) break; if (SDL_RWseek(rw, 0x0C * resourceCount, RW_SEEK_CUR) < 0) return -1; } for (Uint16 i = 0; i < resourceCount; ++i) { Uint16 dataOffset = SDL_ReadLE16(rw); Uint16 dataLength = SDL_ReadLE16(rw); /* Uint16 flags = */ SDL_ReadLE16(rw); Uint16 resourceID = SDL_ReadLE16(rw); SDL_ReadLE32(rw); // reserved resourceID &= 0x7FFF; if (resourceID >= count) continue; Sint64 nextResource = SDL_RWtell(rw); if (nextResource < 0) return -1; surfaces[resourceID] = dibSurface( rw, (Uint32)dataOffset << alignmentShift, (Uint32)dataLength << alignmentShift ); if (!surfaces[resourceID]) return -1; if (SDL_RWseek(rw, nextResource, RW_SEEK_SET) < 0) return -1; } return 0; } // static int loadPE(SDL_Surface **surfaces, size_t count, SDL_RWops *rw, Uint16 peOffset) { if (SDL_RWseek(rw, peOffset + 0x04 + 0x02, RW_SEEK_SET) < 0) return -1; Uint16 sectionCount = SDL_ReadLE16(rw); if (SDL_RWseek(rw, peOffset + 0x04 + 0x10, RW_SEEK_SET) < 0) return -1; Uint16 optionalHeaderLength = SDL_ReadLE16(rw); Uint16 sectionTableOffset = peOffset + 0x04 + 0x14 + optionalHeaderLength; Uint32 resourceTableOffset = 0; Uint32 resourceTableVirtual = 0; for (Uint16 i = 0; i < sectionCount; ++i) { Uint16 headerOffset = sectionTableOffset + 0x28 * i; if (SDL_RWseek(rw, headerOffset, RW_SEEK_SET) < 0) return -1; char name[8]; if (!SDL_RWread(rw, name, sizeof(name), 1)) return -1; if (strncmp(".rsrc", name, sizeof(name))) continue; if (SDL_RWseek(rw, headerOffset + 0x0C, RW_SEEK_SET) < 0) return -1; resourceTableVirtual = SDL_ReadLE32(rw); if (SDL_RWseek(rw, headerOffset + 0x14, RW_SEEK_SET) < 0) return -1; resourceTableOffset = SDL_ReadLE32(rw); break; } if (!resourceTableOffset) { SDL_SetError("no resource table"); return -1; } if (SDL_RWseek(rw, resourceTableOffset + 0x0C, RW_SEEK_SET) < 0) return -1; Uint16 typeNameCount = SDL_ReadLE16(rw); Uint16 typeIDCount = SDL_ReadLE16(rw); if (SDL_RWseek(rw, 0x08 * typeNameCount, RW_SEEK_CUR) < 0) return -1; Uint32 bitmapTableOffset = 0; for (Uint16 i = 0; i < typeIDCount; ++i) { Uint32 typeID = SDL_ReadLE32(rw); Uint32 subdirOffset = SDL_ReadLE32(rw); if (typeID != 0x02) continue; if (!(subdirOffset & (1 << 31))) { SDL_SetError("bitmap type entry does not point to table"); return -1; } bitmapTableOffset = resourceTableOffset + (subdirOffset & ~(1 << 31)); break; } if (SDL_RWseek(rw, bitmapTableOffset + 0x0C, RW_SEEK_SET) < 0) return -1; Uint16 nameNameCount = SDL_ReadLE16(rw); Uint16 nameIDCount = SDL_ReadLE16(rw); if (SDL_RWseek(rw, 0x08 * nameNameCount, RW_SEEK_CUR) < 0) return -1; for (Uint16 i = 0; i < nameIDCount; ++i) { Uint32 nameID = SDL_ReadLE32(rw); Uint32 subdirOffset = SDL_ReadLE32(rw); if (nameID >= count) continue; if (!(subdirOffset & (1 << 31))) { SDL_SetError("bitmap name entry does not point to table"); return -1; } Sint64 nextName = SDL_RWtell(rw); if (nextName < 0) return -1; Uint32 langTableOffset = resourceTableOffset + (subdirOffset & ~(1 << 31)); if (SDL_RWseek(rw, langTableOffset + 0x0C, RW_SEEK_SET) < 0) return -1; Uint16 langNameCount = SDL_ReadLE16(rw); Uint16 langIDCount = SDL_ReadLE16(rw); if (langNameCount != 0 || langIDCount != 1) { SDL_SetError("language table contains more than one entry"); return -1; } /* Uint32 langID = */ SDL_ReadLE32(rw); Uint32 dataEntryOffset = SDL_ReadLE32(rw); if (dataEntryOffset & (1 << 31)) { SDL_SetError("language entry does not point to data"); return -1; } dataEntryOffset += resourceTableOffset; if (SDL_RWseek(rw, dataEntryOffset, RW_SEEK_SET) < 0) return -1; Uint32 dataVirtual = SDL_ReadLE32(rw); Uint32 dataLength = SDL_ReadLE32(rw); Uint32 dataOffset = dataVirtual - (resourceTableVirtual - resourceTableOffset); surfaces[nameID] = dibSurface(rw, dataOffset, dataLength); if (!surfaces[nameID]) return -1; if (SDL_RWseek(rw, nextName, RW_SEEK_SET) < 0) return -1; } return 0; } static int loadEXE(SDL_Surface **surfaces, size_t count, SDL_RWops *rw) { if (SDL_RWseek(rw, 0, RW_SEEK_SET) < 0) return -1; if (SDL_ReadU8(rw) != 'M' || SDL_ReadU8(rw) != 'Z') { SDL_SetError("invalid MZ signature"); return -1; } if (SDL_RWseek(rw, 0x3C, RW_SEEK_SET) < 0) return -1; Uint16 offset = SDL_ReadLE16(rw); if (SDL_RWseek(rw, offset, RW_SEEK_SET) < 0) return -1; Uint8 sig[2] = { SDL_ReadU8(rw), SDL_ReadU8(rw) }; if (sig[0] == 'N' && sig[1] == 'E') { return loadNE(surfaces, count, rw, offset); } else if (sig[0] == 'P' && sig[1] == 'E') { return loadPE(surfaces, count, rw, offset); } else { SDL_SetError("invalid NE/PE signature"); return -1; } } struct Cards *Cards_Load(SDL_RWops *rw, enum Cards_Flags flags) { struct Cards *cards = calloc(1, sizeof(*cards)); if (!cards) { SDL_SetError("calloc error: %s", strerror(errno)); return NULL; } if (loadEXE(cards->surfaces, Cards_Count, rw) < 0) goto fail; for (int i = Cards_Club + Cards_A; i <= Cards_Spade + Cards_K; ++i) { if (cards->surfaces[i]) continue; SDL_SetError("missing resource %d", i); goto fail; } for (int i = Cards_Empty; i <= Cards_Back12; ++i) { if (cards->surfaces[i]) continue; SDL_SetError("missing resource %d", i); goto fail; } for (int i = Cards_X; i <= Cards_O; ++i) { if (cards->surfaces[i]) continue; SDL_SetError("missing resource %d", i); goto fail; } if (flags & Cards_ColorKey) { if (setColorKey(cards) < 0) goto fail; } if (flags & Cards_AlphaCorners) { if (setAlphaCorners(cards) < 0) goto fail; } if (flags & Cards_BlackBorders) { if (setBlackBorders(cards) < 0) goto fail; } return cards; fail: Cards_Free(cards); return NULL; } int invertPalette(SDL_Surface *surface) { const SDL_Palette *palette = surface->format->palette; SDL_Palette *invert = SDL_AllocPalette(palette->ncolors); if (!invert) return -1; for (int i = 0; i < invert->ncolors; ++i) { invert->colors[i].r = ~palette->colors[i].r; invert->colors[i].g = ~palette->colors[i].g; invert->colors[i].b = ~palette->colors[i].b; invert->colors[i].a = palette->colors[i].a; } if (SDL_SetSurfacePalette(surface, invert) < 0) return -1; SDL_FreePalette(invert); return 0; } int invertPixels(SDL_Surface *surface) { if (SDL_LockSurface(surface) < 0) return -1; Uint8 *pixels = surface->pixels; for (int y = 0; y < surface->h; ++y) { Uint32 *row = (Uint32 *)&pixels[y * surface->pitch]; for (int x = 0; x < surface->w; ++x) { Uint32 color = ~row[x] & ~surface->format->Amask; Uint32 alpha = row[x] & surface->format->Amask; row[x] = color | alpha; } } SDL_UnlockSurface(surface); return 0; } int Cards_Invert(struct Cards *cards) { for (int i = 0; i < Cards_Count; ++i) { if (!cards->surfaces[i]) continue; if (cards->surfaces[i]->format->palette) { if (invertPalette(cards->surfaces[i]) < 0) return -1; } else if (cards->surfaces[i]->format->BytesPerPixel == 4) { if (invertPixels(cards->surfaces[i]) < 0) return -1; } else { SDL_SetError("unexpected surface format"); return -1; } } return 0; } void Cards_Free(struct Cards *cards) { for (int i = 0; i < Cards_Count; ++i) { if (!cards->surfaces[i]) continue; SDL_FreeSurface(cards->surfaces[i]); } free(cards); }