解密RimWorld环世界地形数据

这是来自粉丝的求助,其实这个问题应该求助那些参与多语言翻译和模组制作的人。

随着国力的不断强大,很多跨国的产品不管游戏、电影、机器还是软件系统,都越来越多的有中国人参与,这个也不例外。

我从来不喜欢直接告诉你那是什么,我更乐于告诉你我是怎么知道的,时间紧迫,本文将把几个小时的解密笔记搬上来了事~

首先,打开存档(RimWorld.Ideology.v1.3.3066.rev1166

C:\Users\Administrator\AppData\LocalLow\Ludeon Studios\RimWorld by Ludeon Studios\Saves

这是个xml文件,文件较大,可能需要一些有内存映射技术的专业文本编辑器来提高速度;

接着是英语水平了,搜索地形的英文:terrain

最后反复F3几次“查找下一个”锁定terrainGrid节点,因为前后有roof屋顶设置

解密RimWorld环世界地形数据

这个topGridDeflate节点应该就是地表数据了,从“乱码”风格看,应该就是base64编码

开始敲C++代码进行解码,代码略,解密出来看不出什么,为什么?

因为地形经常一大块地方是一样的,这里看不到大量重复的数值~

所以,要么是加密,要么是压缩(压缩其实也是加密),从节点名称Deflate猜测:是压缩

开发者模式看到大量的C#堆栈调用信息,开发语言应该就是C#,因此用C#解压缩


        private void button1_Click(object sender, EventArgs e)
        {
            byte[] buf = Convert.FromBase64String(this.textBox1.Text);
            using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
            {
                ms.Write(buf, 0, buf.Length);
                ms.Position = 0;
                using (System.IO.Compression.DeflateStream ds = new System.IO.Compression.DeflateStream(ms, System.IO.Compression.CompressionMode.Decompress))
                {
                    ds.Flush();
                    byte[] data = new byte[65536 * 2];
                    int len = ds.Read(data, 0, data.Length);
                    ds.Close();
                    // view key point
                    int size = (int)System.Math.Sqrt((double)(len / 2));
                    for (int y = 8; y <= 11; y++)
                    {
                        for (int x = 33; x < 36; x++)
                        {
                    int idx = GetByteOffset(x, y, size, 2);
                    ushort w_10_10 = (ushort)(data[idx] + data[idx + 1] * 256);
                            System.Diagnostics.Debug.Print("(" + x.ToString() + "," + y.ToString() + ")=" + w_10_10.ToString());
                        }
                    }
                    // display
                    this.textBox2.Text = System.Text.Encoding.UTF8.GetString(data, 0, len);
                }
            }
        }

最后解出来101250字节,地图是225x225=50625个格子,因此是每个格子2个字节数据

(代码后面加上转为2字节16位整数查看)

用开发者模式修改左上角一个格子的数据,存档,再次解密,在对比:

只有100800和100801两字节改变,说明x是水平向右,而y是从下向上

继续开发者模式,连续设置不同的地形,列出对应的16位数值:

左下角连续土地块:0x86A1=34465(-31071)
下一行第一格土地:0x86A1=34465(-31071)已确认
                        沼泽:0x8606=34310(-31226)
                        浅水:0x2CB5=11445
                    浅海水:0xFF89=65417(-119)
            流动的浅水:0x66D4=26324
                        深水:0x798C=31116
                    深海水:0xA790=42896(-22640)
            流动的深水:0x8015=32789(-32747)
    粗糙的砂岩地面:0x?=?

数值上看不出高度或类型,猜测为地形定义的ID值,不管是哪种,只要这个值一样即可

至少对当前存档有效,新建一个地图,再次试验:

确定该值对不同存档有效(相同游戏版本)

那么解析一个图片,然后根据图像颜色生成地形数据的代码,很自然的就有了

        private void button2_Click(object sender, EventArgs e)
        {
            ushort[] w_vectorTab = new ushort[8];
            w_vectorTab[0] = 0x86A1;    // 土地
            w_vectorTab[1] = 0x8606;    // 沼泽
            w_vectorTab[2] = 0x2CB5;    // 浅水
            w_vectorTab[3] = 0xFF89;    // 浅海水
            w_vectorTab[4] = 0x66D4;    // 流动的浅水
            w_vectorTab[5] = 0x798C;    // 深水
            w_vectorTab[6] = 0xA790;    // 深海水
            w_vectorTab[7] = 0x8015;    // 流动的深水
            System.Drawing.Color[] s_colorTab = new Color[8];
            s_colorTab[0] = Color.FromArgb(255, 255, 255);      // 白色=土地
            s_colorTab[1] = Color.FromArgb(  0, 128,   0);      // 墨绿=沼泽
            s_colorTab[2] = Color.FromArgb(  0, 255,   0);      // 绿色=浅水
            s_colorTab[3] = Color.FromArgb(255,   0, 255);      // 紫色=浅海水
            s_colorTab[4] = Color.FromArgb(  0, 255, 255);      // 青色=流动的浅水
            s_colorTab[5] = Color.FromArgb(  0,   0,   0);      // 黑色=深水()
            s_colorTab[6] = Color.FromArgb(128,   0, 128);      // 深蓝=深海水()
            s_colorTab[7] = Color.FromArgb(255, 255,   0);      // 黄色=流动的深水
            System.Drawing.Bitmap bmp = new Bitmap("C:\\Users\\Administrator\\Desktop\\terrain.bmp");
            //ushort[] data = new ushort[bmp.Width * bmp.Width];
            byte[] data = new byte[bmp.Width * bmp.Width * 2];
            // try
            for (int y = 0; y < bmp.Height; y++)
            {
                for (int x = 0; x < bmp.Width; x++)
                {
                    System.Drawing.Color c = bmp.GetPixel(x, y);
                    // 0 if not found
                    int k = 0;
                    // find
                    for (int i = 0; i < s_colorTab.Length; i++)
                    {
                        if (s_colorTab[i] == c)
                        {
                            k = i;
                            break;
                        }
                    }
                    //System.Diagnostics.Debug.Assert(k >= 0);
                    int idx = (bmp.Height - 1 - y) * bmp.Width + x;
                    // x86 little endian
                    ushort key = w_vectorTab[k];
                    data[idx * 2] = (byte)(key & 0x00FF);
                    data[idx * 2 + 1] = (byte)((key >> 8) & 0x00FF);
                }
            }
            // bmp will auto-dispose
            using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
            {
                using (System.IO.Compression.DeflateStream ds = new System.IO.Compression.DeflateStream(ms, System.IO.Compression.CompressionMode.Compress, true))
                {
                    ds.Write(data, 0, data.Length);
                    ds.Close();
                    byte[] buf = ms.ToArray();
                    ms.Close();
                    string str = Convert.ToBase64String(buf);
                    this.textBox1.Text = InsertCrLf(str, 100, "\r\n");
                }
            }
            // ret
        }

 读取的文件固定放在桌面的terrain.bmp位图,兼顾原有100字节换行风格

粘贴到存档位置覆盖,保存,进去重新加载即可

同样的,屋顶的解析也是类似:

    无:0
山岩顶:0x2A44=10820

 那么根据图片来生成山岩顶的代码,依小姐姐的葫芦曲线画瓢


        private void button3_Click(object sender, EventArgs e)
        {
            ushort[] w_vectorTab = new ushort[3];
            w_vectorTab[0] = 0x0000;    // 无
            w_vectorTab[1] = 0;    // 建筑屋顶
            w_vectorTab[2] = 0x2A44;    // 山岩顶
            System.Drawing.Color[] s_colorTab = new Color[8];
            s_colorTab[0] = Color.FromArgb(255, 255, 255);      // 白色=无
            s_colorTab[1] = Color.FromArgb(  0, 128,   0);      // 墨绿=建筑屋顶
            s_colorTab[2] = Color.FromArgb(  0,   0,   0);      // 黑色=山岩顶
            System.Drawing.Bitmap bmp = new Bitmap("C:\\Users\\Administrator\\Desktop\\roof.bmp");
            //ushort[] data = new ushort[bmp.Width * bmp.Width];
            byte[] data = new byte[bmp.Width * bmp.Width * 2];
            // try
            for (int y = 0; y < bmp.Height; y++)
            {
                for (int x = 0; x < bmp.Width; x++)
                {
                    System.Drawing.Color c = bmp.GetPixel(x, y);
                    // 0 if not found
                    int k = 0;
                    // find
                    for (int i = 0; i < s_colorTab.Length; i++)
                    {
                        if (s_colorTab[i] == c)
                        {
                            k = i;
                            break;
                        }
                    }
                    //System.Diagnostics.Debug.Assert(k >= 0);
                    int idx = (bmp.Height - 1 - y) * bmp.Width + x;
                    // x86 little endian
                    ushort key = w_vectorTab[k];
                    data[idx * 2] = (byte)(key & 0x00FF);
                    data[idx * 2 + 1] = (byte)((key >> 8) & 0x00FF);
                }
            }
            // bmp will auto-dispose
            using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
            {
                using (System.IO.Compression.DeflateStream ds = new System.IO.Compression.DeflateStream(ms, System.IO.Compression.CompressionMode.Compress, true))
                {
                    ds.Write(data, 0, data.Length);
                    ds.Close();
                    byte[] buf = ms.ToArray();
                    ms.Close();
                    string str = Convert.ToBase64String(buf);
                    this.textBox1.Text = InsertCrLf(str, 100, "\r\n");
                }
            }
            // ret
        }

无图但有真相

上一篇:学习笔记: 系统IO实际应用(显示bmp图片)


下一篇:Qt 使用setUserData和userData要注意的问题