Implement playable Mac client and rendering validation

This commit is contained in:
shen
2026-09-11 14:58:22 +08:00
parent 1ab68bd06e
commit 374d4165d8
233 changed files with 6546 additions and 284 deletions
+33 -11
View File
@@ -67,7 +67,19 @@ void decode_dxt5_alpha(const uint8_t* blk, uint8_t out[16][4]) {
}
}
enum Fmt { F_NONE, F_DXT1, F_DXT3, F_DXT5, F_BGRA8 };
enum Fmt { F_NONE, F_DXT1, F_DXT3, F_DXT5, F_RGB };
bool valid_mask(uint32_t mask, uint32_t bits) {
if (!mask || (bits < 32 && (mask >> bits))) return false;
while (!(mask & 1)) mask >>= 1;
return (mask & (mask + 1)) == 0; // contiguous channel bits
}
uint8_t channel(uint32_t pixel, uint32_t mask) {
if (!mask) return 255;
while (!(mask & 1)) { mask >>= 1; pixel >>= 1; }
return uint8_t((uint64_t(pixel & mask) * 255 + mask / 2) / mask);
}
Image decode(const uint8_t* d, size_t len) {
Image img;
@@ -85,8 +97,8 @@ Image decode(const uint8_t* d, size_t len) {
if (!std::memcmp(fourcc, "DXT1", 4)) fmt = F_DXT1;
else if (!std::memcmp(fourcc, "DXT3", 4)) fmt = F_DXT3;
else if (!std::memcmp(fourcc, "DXT5", 4)) fmt = F_DXT5;
} else if ((pf_flags & 0x40) && rgb_bitcount == 32) { // DDPF_RGB, 32bpp
fmt = F_BGRA8; // Metin2 的非压缩 DDS 一般是 B8G8R8A8
} else if ((pf_flags & 0x40) && (rgb_bitcount == 16 || rgb_bitcount == 24 || rgb_bitcount == 32)) {
fmt = F_RGB;
}
if (fmt == F_NONE || w == 0 || h == 0 || w > 8192 || h > 8192) return img;
@@ -96,15 +108,25 @@ Image decode(const uint8_t* d, size_t len) {
img.h = uint16_t(h);
img.rgba.assign(size_t(w) * h * 4, 0);
if (fmt == F_BGRA8) {
if (avail < size_t(w) * h * 4) { img = Image{}; return img; }
for (size_t i = 0; i < size_t(w) * h; ++i) {
img.rgba[i * 4 + 0] = src[i * 4 + 2];
img.rgba[i * 4 + 1] = src[i * 4 + 1];
img.rgba[i * 4 + 2] = src[i * 4 + 0];
img.rgba[i * 4 + 3] = src[i * 4 + 3];
if (fmt == F_RGB) {
const uint32_t r = rd_le32(d + 92), g = rd_le32(d + 96), b = rd_le32(d + 100);
const uint32_t a = (pf_flags & 1) ? rd_le32(d + 104) : 0;
if (!valid_mask(r, rgb_bitcount) || !valid_mask(g, rgb_bitcount) || !valid_mask(b, rgb_bitcount) ||
(a && !valid_mask(a, rgb_bitcount)) || ((pf_flags & 1) && !a) ||
(r & g) || (r & b) || (g & b) || (a & (r | g | b))) return {};
const size_t bytes = rgb_bitcount / 8, row_bytes = size_t(w) * bytes;
const size_t pitch = (rd_le32(d + 8) & 8) ? rd_le32(d + 20) : row_bytes;
if (pitch < row_bytes || avail < row_bytes || pitch > avail || (h - 1) > (avail - row_bytes) / pitch) return {};
for (size_t y = 0; y < h; ++y) {
for (size_t x = 0; x < w; ++x) {
uint32_t pixel = 0;
for (size_t k = 0; k < bytes; ++k) pixel |= uint32_t(src[y * pitch + x * bytes + k]) << (8 * k);
auto* out = &img.rgba[(y * w + x) * 4];
out[0] = channel(pixel, r); out[1] = channel(pixel, g);
out[2] = channel(pixel, b); out[3] = channel(pixel, a);
}
}
img.format = "BGRA8";
img.format = rgb_bitcount == 32 && r == 0xff0000 && b == 0xff ? "BGRA8" : "RGB_MASKED";
return img;
}