金庸群俠傳是智冠科技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)); }..................