游戏内ab文件看起来是压缩文件,文件头是NHPKD1。解压后部分文件能正常打开,但有些文件头是KTX11,打不开。
有没有大佬知道如何处理
Vibe Coding
Ktx2Png
python -m pip install pillow texture2ddecoder
import os
import sys
import struct
from PIL import Image
import texture2ddecoder
# KTX文件标识符
KTX_ID = b'\xABKTX 11\xBB\r\n\x1A\n'
# ASTC internalFormat -> (block_width, block_height)
# 参考 GL_KHR_texture_compression_astc_ldr 规范
ASTC_FORMATS = {
0x93B0: (4, 4), 0x93B1: (5, 4), 0x93B2: (5, 5), 0x93B3: (6, 5),
0x93B4: (6, 6), 0x93B5: (8, 5), 0x93B6: (8, 6), 0x93B7: (8, 8),
0x93B8: (10, 5), 0x93B9: (10, 6), 0x93BA: (10, 8), 0x93BB: (10, 10),
0x93BC: (12, 10), 0x93BD: (12, 12),
}
def parse_ktx_header(filepath):
"""解析 KTX 1.1 头部,返回关键字段字典"""
with open(filepath, 'rb') as f:
header = f.read(64)
if header[:12] != KTX_ID:
raise ValueError("无效的 KTX 1.1 文件头")
return {
'endianness': struct.unpack_from('<I', header, 12)[0],
'gl_type': struct.unpack_from('<I', header, 16)[0],
'gl_format': struct.unpack_from('<I', header, 24)[0],
'gl_internal_format': struct.unpack_from('<I', header, 28)[0],
'width': struct.unpack_from('<I', header, 36)[0],
'height': struct.unpack_from('<I', header, 40)[0],
'kv_bytes': struct.unpack_from('<I', header, 60)[0],
}
def decode_compressed(internal_fmt, data, width, height):
"""
根据 glInternalFormat 调用 texture2ddecoder 解码
返回 BGRA bytes 或 None(表示不支持)
"""
# --- BC / DXT (S3TC) ---
if internal_fmt in (0x83F0, 0x83F1): # BC1 / DXT1
return texture2ddecoder.decode_bc1(data, width, height)
elif internal_fmt == 0x83F2: # BC2 / DXT3
return texture2ddecoder.decode_bc2(data, width, height)
elif internal_fmt == 0x83F3: # BC3 / DXT5
return texture2ddecoder.decode_bc3(data, width, height)
elif internal_fmt == 0x8C48: # BC4 / ATI1
return texture2ddecoder.decode_bc4(data, width, height)
elif internal_fmt == 0x8C4A: # BC5 / ATI2
return texture2ddecoder.decode_bc5(data, width, height)
elif internal_fmt in (0x8E8C, 0x8E8D): # BC6H
return texture2ddecoder.decode_bc6h(data, width, height)
elif internal_fmt in (0x8E8F, 0x8E90): # BC7
return texture2ddecoder.decode_bc7(data, width, height)
# --- ETC ---
elif internal_fmt == 0x8D64: # ETC1
return texture2ddecoder.decode_etc1(data, width, height)
elif internal_fmt in (0x9274, 0x9275): # ETC2 RGB
return texture2ddecoder.decode_etc2_rgb(data, width, height)
elif internal_fmt in (0x9276, 0x9277): # ETC2 EAC RGBA (planar)
return texture2ddecoder.decode_etc2_rgba(data, width, height)
elif internal_fmt in (0x9278, 0x9279): # ETC2 EAC RGBA (interleaved)
return texture2ddecoder.decode_etc2_rgba(data, width, height)
# --- ASTC ---
elif internal_fmt in ASTC_FORMATS:
bw, bh = ASTC_FORMATS[internal_fmt]
return texture2ddecoder.decode_astc(data, width, height, bw, bh)
# --- PVRTC (可选,部分平台可能有问题) ---
# elif internal_fmt in (0x8C00, 0x8C01, 0x8C02, 0x8C03):
# return texture2ddecoder.decode_pvrtc(data, width, height, ...)
return None
def convert_ktx(ktx_path, png_path):
"""将单个 KTX 文件转换为 PNG"""
hdr = parse_ktx_header(ktx_path)
with open(ktx_path, 'rb') as f:
f.seek(64 + hdr['kv_bytes'])
img_size = struct.unpack('<I', f.read(4))[0]
img_data = f.read(img_size)
w, h = hdr['width'], hdr['height']
# ---- 未压缩纹理:直接用 Pillow ----
if hdr['gl_type'] != 0:
gl_fmt = hdr['gl_format']
if gl_fmt == 6408: # GL_RGBA
mode, bpp = 'RGBA', 4
elif gl_fmt == 6407: # GL_RGB
mode, bpp = 'RGB', 3
else:
raise ValueError(f"不支持的未压缩 glFormat: {gl_fmt}")
stride = (w * bpp + 3) & ~3 # KTX 行对齐到 4 字节
img = Image.frombytes(mode, (w, h), img_data, 'raw', mode, stride)
img.save(png_path)
return
# ---- 压缩纹理:texture2ddecoder ----
decoded = decode_compressed(hdr['gl_internal_format'], img_data, w, h)
if decoded is None:
raise ValueError(
f"不支持的压缩格式 (internalFormat=0x{hdr['gl_internal_format']:X}),"
"请检查 texture2ddecoder 是否支持该格式"
)
# texture2ddecoder 统一输出 BGRA 原始字节 -> 用 Pillow 转存
img = Image.frombytes('RGBA', (w, h), decoded, 'raw', 'BGRA')
img.save(png_path)
def process(path):
"""处理单个文件或目录下所有 .ktx"""
if os.path.isfile(path) and path.lower().endswith('.ktx'):
out = os.path.splitext(path)[0] + '.png'
print(f"[转换] {os.path.basename(path)} -> {os.path.basename(out)}")
try:
convert_ktx(path, out)
print(" 成功")
except Exception as e:
print(f" 失败: {e}")
elif os.path.isdir(path):
for f in os.listdir(path):
if f.lower().endswith('.ktx'):
process(os.path.join(path, f))
def main():
script_dir = os.path.dirname(os.path.abspath(__file__))
if len(sys.argv) > 1:
target = sys.argv[1]
else:
target = script_dir
print(f"未指定参数,使用脚本目录: {script_dir}")
if not os.path.exists(target):
print("路径不存在")
return
process(target)
if __name__ == '__main__':
main()
杂项
1.下载:
B服:
源序空间 - bilibili游戏中心
Apk_from_bilibili
https://pkg.biligame.com/games/yxkj_1.10.3500_4_20260414_115435_09687.apk
https://hsdownload.densicraft.com/yxkj/20260922/PD_2026_9_22_V1.10.4000_encrypted_hnym.apk
2.停运公告:
3.引擎——LayaAir、资源管理——LayaDCC
4.动画——DragonBones、Spine
5.NHPK容器结构(草稿)
6.关于还原文件名及目录结构后的
*.zpm文件(刚刚弄的时候,操作不当整个丢失了
大佬们辛苦了,想请教一下。com.hnym.yxkjgame\LayaCache目录下的文件疑似加密,直接按KTX解析会失败(文件头像 WSTX0,但内部能搜到KTX11)。请问该如何提取?


