金庸群侠传是智冠科技90年代出品的精品DOS游戏,
其资源压缩包格式紧凑而科学,这里我们一起学习一下其数据结构。并且编写一个能够读取解析它的程序,
以下是我对 Hdgrp资源文件解包的运行结果展示
下面我们看一下资源类型:
其资源包括 idx和grp文件,idx记录了各个资源的索引、grp(group pictures?)存储了具体数据。
另外以Mmap.col作为整体的调色板,存储各种颜色数据。
idx文件每4个byte为一个section,每个section记录一个资源图片的endoffset,即又是下一个资源图片的startoffset。
----------
int32 | endoffset
grp文件,以idx文件为基准,按offset区间划分存储图片,每个图片资源:
---------
int16 | w
int16 | h
int16 | x (不知道有什么用)
int16 | y(不知道有什么用)
以下h行,每行支持多个section,每个section数据结构
----------
int8 | 该行字节数
[section]
int8 | t 透明像素点个数
int8 | nt 非透明像素点个数
nt * int8 | 该点对应调色板数据,如1,则对应调色板第一个颜色值
调色板文件Mmap.col
一共256*3 byte,256色,每个颜色按rgb 除以4存放,按顺序存放
----------
byte | r
byte | g
byte | b
这样就可以解每个资源文件了。下面是部分核心的C#代码,我使用WPF的bitmapimage进行渲染。
class GameResource { #region 单例 static public GameResource Instance { get { if(_instance==null){ _instance = new GameResource(); } return _instance; } } static GameResource _instance = null; private GameResource() { ImageFiles = new Dictionary<string, List<Image>>(); } #endregion public Dictionary<string, List<Image>> ImageFiles; } .............
/// <summary> /// 初始化调色板 /// </summary> public void InitColors() { FileStream f = new FileStream("data/Mmap.col", FileMode.Open); BinaryReader reader = new BinaryReader(f); for (int i = 0; i < 256; ++i) { byte[] color = reader.ReadBytes(3); for (int j = 0; j < color.Length; ++j) { color[j] = (byte)((int)color[j] * 4); } colorMap[i] = color; } reader.Close(); f.Close(); } Dictionary<int, byte[]> colorMap = new Dictionary<int, byte[]>(); private BitmapImage ReadImage(BinaryReader reader, int length) { int w = reader.ReadInt16(); int h = reader.ReadInt16(); int x = reader.ReadInt16(); int y = reader.ReadInt16(); List<System.Windows.Media.Color> colors = new List<System.Windows.Media.Color>(); colors.Add(System.Windows.Media.Colors.Blue); colors.Add(System.Windows.Media.Colors.Green); colors.Add(System.Windows.Media.Colors.Red); BitmapPalette palette = new BitmapPalette(colors); PixelFormat pf = PixelFormats.Bgra32; int stride = (w * pf.BitsPerPixel + 7) / 8; byte[] pixels = new byte[h * stride]; for (int i = 0; i < pixels.Length; ++i) { pixels[i] = 0x00; } int p = 0; try { for (int i = 0; i < h; ++i) //H行 { p = i * w * pf.BitsPerPixel / 8; int count = reader.ReadByte(); //该行字节数 int offset = 0; while (offset < count) { int transparentPixs = reader.ReadByte(); //透明像素个数 offset++; p += transparentPixs * pf.BitsPerPixel / 8; int nonTransPix = reader.ReadByte(); //非透明像素个数 offset++; for (int j = 0; j < nonTransPix; ++j) { int colorKey = reader.ReadByte(); pixels[p] = colorMap[colorKey][2]; //b p++; pixels[p] = colorMap[colorKey][1]; //g p++; pixels[p] = colorMap[colorKey][0]; //r p++; pixels[p] = 0xFF; //a p++; offset++; } } } } catch (Exception e) { MessageBox.Show(p.ToString()); } BitmapSource image = BitmapSource.Create( w, h, 96, 96, pf, palette, pixels, stride); PngBitmapEncoder encoder = new PngBitmapEncoder(); MemoryStream memoryStream = new MemoryStream(); BitmapImage bImg = new BitmapImage(); encoder.Frames.Add(BitmapFrame.Create(image)); encoder.Save(memoryStream); bImg.BeginInit(); bImg.StreamSource = new MemoryStream(memoryStream.ToArray()); bImg.EndInit(); memoryStream.Close(); return bImg; } public List<Image> LoadImages(string filename) { List<Image> rst = new List<Image>(); FileStream f = new FileStream(filename + ".idx", FileMode.Open); BinaryReader reader = new BinaryReader(f); FileStream gf = new FileStream(filename + ".grp", FileMode.Open); BinaryReader greader = new BinaryReader(gf); try { int startOffset = 0; while (f.CanRead) { int endOffset = reader.ReadInt32(); Image image = new Image(); image.Source = ReadImage(greader, endOffset-startOffset); rst.Add(image); startOffset = endOffset; } } catch (Exception e) { } greader.Close(); gf.Close(); reader.Close(); f.Close(); return rst; } public void LoadResource(string filename) { GameResource.Instance.ImageFiles.Add(filename, LoadImages(filename)); } ..................