PNG CRC32 修复工具 #
这是一个用于修复 PNG 图片 CRC32 校验的工具,主要通过调整图片的宽度和高度来实现。
功能特性 #
-
CRC32 检测
- 自动检测 PNG 文件的 IHDR 块 CRC32 值
- 对比实际 CRC32 和期望值是否匹配
-
自动修复
- 支持三种修复模式:
- 仅修复高度
- 仅修复宽度
- 同时修复高度和宽度
- 自动生成修复后的文件(文件名后缀为 _fix.png)
- 支持三种修复模式:
-
可视化输出
- 使用彩色输出显示处理过程
- 显示当前和修复后的图片尺寸
- 显示详细的 CRC32 信息
使用方法 #
python break.py <png_file_path>
例如:
python break.py test.png
输出说明 #
- 绿色: 成功信息
- 红色: 错误信息
- 黄色: 预期值
- 青色: 信息提示
技术细节 #
-
IHDR 类
- 处理 PNG 文件的 IHDR 块
- 提供宽度/高度的读取和修改方法
- 计算 CRC32 校验值
-
修复策略
- 优先尝试仅修改高度(最快)
- 其次尝试仅修改宽度
- 最后尝试同时修改高度和宽度(最慢)
-
文件处理
- 支持二进制文件读写
- 自动备份原始文件
- 错误处理和异常捕获
注意事项 #
- 输入文件必须是有效的 PNG 文件
- 工具会在原文件同目录下创建修复后的文件
- 修复过程可能需要一定时间,特别是在尝试同时修复宽度和高度时
# -*- coding: utf-8 -*-
import binascii
import struct
import sys
from pathlib import Path
from typing import Optional, Tuple
# ANSI escape codes for colors
class Colors:
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
CYAN = "\033[96m"
RESET = "\033[0m"
class IHDR:
def __init__(self, data: bytes):
self.raw_data = data
self._header = data[:4] # IHDR chunk type
self._height_bytes = data[8:12]
self._width_bytes = data[4:8]
self._ends = data[12:] # bit depth, color type, etc
@property
def width(self) -> int:
return int.from_bytes(self._width_bytes, "big")
@property
def height(self) -> int:
return int.from_bytes(self._height_bytes, "big")
def get_data(self, width=None, height=None) -> bytes:
return (
bytes(self._header)
+ (bytes(self._width_bytes) if width is None else struct.pack(">i", width))
+ (
bytes(self._height_bytes)
if height is None
else struct.pack(">i", height)
)
+ bytes(self._ends)
)
def crc32(self, width=None, height=None) -> int:
return binascii.crc32(self.get_data(width, height)) & 0xFFFFFFFF
@staticmethod
def from_png(png_data: bytes) -> Tuple["IHDR", int]:
"""Extract IHDR from PNG file data and return (IHDR, expected_crc32)"""
ihdr_data = png_data[12:29]
expected_crc32 = int.from_bytes(png_data[29:33], "big")
return IHDR(ihdr_data), expected_crc32
def try_fix_height(ihdr: IHDR, expected_crc32: int) -> Optional[int]:
for height in range(0, 65535):
if ihdr.crc32(height=height) == expected_crc32:
return height
return None
def try_fix_width(ihdr: IHDR, expected_crc32: int) -> Optional[int]:
for width in range(0, 65535):
if ihdr.crc32(width=width) == expected_crc32:
return width
return None
def try_fix_both(
ihdr: IHDR, expected_crc32: int
) -> Tuple[Optional[int], Optional[int]]:
for height in range(0, 65535):
for width in range(0, 65535):
if ihdr.crc32(width=width, height=height) == expected_crc32:
return height, width
return None, None
def save_fixed_png(
original_data: bytes,
width: Optional[int] = None,
height: Optional[int] = None,
output_path: Path | None = None,
) -> None:
"""Save a new PNG with fixed dimensions"""
if output_path is None:
output_path = Path(str(png_path).replace(".png", "_fix.png"))
# Only modify if we have new dimensions
if width is None and height is None:
return
# Copy original data
new_data = bytearray(original_data)
# Update width if provided (at offset 16)
if width is not None:
new_data[16:20] = struct.pack(">i", width)
# Update height if provided (at offset 20)
if height is not None:
new_data[20:24] = struct.pack(">i", height)
# Calculate new CRC32 for IHDR chunk
new_crc = binascii.crc32(new_data[12:29]) & 0xFFFFFFFF
new_data[29:33] = struct.pack(">I", new_crc)
# Save new file
with open(output_path, "wb") as f:
f.write(new_data)
print(f"{Colors.GREEN}Saved fixed PNG to: {output_path}{Colors.RESET}")
def try_fix_crc32(img_data: bytes, png_path: Path) -> None:
ihdr, expected_crc32 = IHDR.from_png(img_data)
real_crc32 = ihdr.crc32()
if expected_crc32 == real_crc32:
print(f"{Colors.GREEN}CRC32 is correct!{Colors.RESET}")
return
print(f"{Colors.CYAN}IHDR data:{Colors.RESET} {ihdr.get_data()}")
print(f"{Colors.YELLOW}Expected CRC32: 0x{expected_crc32:X}")
print(f"{Colors.RED}Current CRC32: 0x{real_crc32:X}{Colors.RESET}")
print(f"{Colors.CYAN}Current dimensions: {ihdr.width}x{ihdr.height}{Colors.RESET}")
# Try fixing height
height = try_fix_height(ihdr, expected_crc32)
if height is not None:
print(f"{Colors.GREEN}Found height fix: {height} (0x{height:X})")
print(f"Real dimensions: {ihdr.width}x{height}{Colors.RESET}")
save_fixed_png(
img_data,
height=height,
output_path=png_path.parent / f"{png_path.stem}_fix.png",
)
return
# Try fixing width
width = try_fix_width(ihdr, expected_crc32)
if width is not None:
print(f"{Colors.GREEN}Found width fix: {width} (0x{width:X})")
print(f"Real dimensions: {width}x{ihdr.height}{Colors.RESET}")
save_fixed_png(
img_data,
width=width,
output_path=png_path.parent / f"{png_path.stem}_fix.png",
)
return
# Try fixing both
height, width = try_fix_both(ihdr, expected_crc32)
if height is not None and width is not None:
print(
f"{Colors.GREEN}Found height/width fix: {height}x{width} (0x{height:X}x0x{width:X}){Colors.RESET}"
)
save_fixed_png(
img_data,
width=width,
height=height,
output_path=png_path.parent / f"{png_path.stem}_fix.png",
)
return
print(f"{Colors.RED}No fix found{Colors.RESET}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"{Colors.RED}Usage: python {sys.argv[0]} <png_file_path>{Colors.RESET}")
sys.exit(1)
png_path = Path(sys.argv[1])
if not png_path.exists():
print(f"{Colors.RED}Error: File {png_path} does not exist{Colors.RESET}")
sys.exit(1)
if not png_path.is_file():
print(f"{Colors.RED}Error: {png_path} is not a file{Colors.RESET}")
sys.exit(1)
try:
with open(png_path, "rb") as f:
img_data = f.read()
try_fix_crc32(img_data, png_path)
except Exception as e:
print(f"{Colors.RED}Error: {str(e)}{Colors.RESET}")
sys.exit(1)
f