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