C# 提取 RK27/RK28 固件的图片资源

瑞芯微 RK27/RK28 固件的图片资源提取工具。已测试 OPPO S33/S39 MP4 固件

https://github.com/he55/RK28UIAssets

  • 图片预览
  • 图片透明通道显示
  • 图片批量保存
  • 拖拽打开文件
  • 拖拽保存图片

C# 提取 RK27/RK28 固件的图片资源

背景

十年前 OPPO 还在做 MP4 的时候(这也是 OPPO 最后一年做 MP4,之后都做手机业务了)。那时我还在上高中看上了 OPPO 的一款 MP4,我还记得型号是 S33(和这个一起的还有一个型号 S39)。在那时流行玩 MP4 的年代,班上同学基本上一半的人都有一台。当时被 OPPO 这款 MP4 外观和 UI 惊艳到了,于是攒了好几个月的钱买了一台。

C# 提取 RK27/RK28 固件的图片资源

我当时就觉得 UI 非常好看,就想把固件里面的资源提取出来,好在官方出了一个固件美化工具。这个工具可以修改固件的图片,但是并没有导出图片的功能。我研究一番发现,软件右边显示的图片会缓存到用户的临时文件夹,文件名为 tmp.bmp 的文件。这下总算可以拿到固件里面的图片资源了,但是如果图片有透明背景保存的图片就有问题。实际上这个软件本身解析的图片就不正确,当时我并没有继续探究原因(那时我还不会写代码,也不懂数据结构)。

C# 提取 RK27/RK28 固件的图片资源

最近我又想起了十年前困扰我的这个问题,这次我成功解决了之前的问题。

固件文件

OPPO S33 的固件文件实际上是 FAT16 文件系统镜像文件,用 7z 可以查看固件里面的文件结构。存放位图资源的文件路径 RESOURCE\BID\BMP0.BIN,所有图片资源都在这个文件里面。这个文件就是要解析出图片资源的文件。

C# 提取 RK27/RK28 固件的图片资源

提取 BMP0.BIN 文件

BMP0.BIN 文件可以用 7z 手动提取,但官方的固件美化工具可以直接从固件中提取 BMP0.BIN 文件。用 IDA 分析官方的固件美化工具,发现是调用了当前目录下的 RK28FSDll.dll 文件。

C# 提取 RK27/RK28 固件的图片资源

使用 DLL Export Viewer 查看导出函数,只有 5 个函数

C# 提取 RK27/RK28 固件的图片资源

使用 IDA 反编译 RK28FSDll.dll 文件,分析出了调用参数。导出函数的 C# 代码

public static class RK28FS
{
    public const string RK28FSDll = "RK28FSDll.dll";

    [DllImport(RK28FSDll)]
    public static extern int FS_Initialize(string imagePath, int arg2, int arg3);

    [DllImport(RK28FSDll)]
    public static extern int FS_DeInitialize();

    [DllImport(RK28FSDll)]
    public static extern int FS_GetLoaderPath(IntPtr ptr);

    [DllImport(RK28FSDll)]
    public static extern int FS_WriteFileToImg(string inputPath, string resPath);

    [DllImport(RK28FSDll)]
    public static extern int FS_WriteFileToPC(string resPath, string outputPath);
}

提取文件代码。FS_WriteFileToPC 方法第一个参数是镜像文件里的资源文件路径,要以 C:\ 开头

RK28FS.FS_Initialize("OPPO_S33_10.828.img", 0, 0);
RK28FS.FS_WriteFileToPC("C:\\RESOURCE\\BID\\BMP0.BIN", "BMP0.BIN");
RK28FS.FS_DeInitialize();

BMP0.BIN 文件数据结构

public class RKRS_H
{
    public int offset;
    public short size;
    public short count;
    public ushort value;
    public List<BID_H> _bids;
}

public class BID_H
{
    /// <summary>
    /// 长度
    /// </summary>
    public int length;

    /// <summary>
    /// 偏移
    /// </summary>
    public int offset;
    public int _bi;
    public int _bid;
    public BIDD_H _bidd;

    public string Id => $"BID_{_bid:X}";
    public string Name => "";
    public string Size => $"{_bidd.width}*{_bidd.height}";
    public string Offset => $"0x{offset:X}";
    public string Length => $"{length - 14}";
    public string D3 => $"0x{_bidd.d3:X}";
    public string D4 => $"0x{_bidd.d4:X}";
    public string D5 => $"0x{_bidd.d5:X}";
}

public class BIDD_H
{
    /// <summary>
    /// 图片宽度
    /// </summary>
    public short width;

    /// <summary>
    /// 图片高度
    /// </summary>
    public short height;

    /// <summary>
    /// 颜色深度
    /// </summary>
    public short d3;

    /// <summary>
    /// 压缩模式
    /// </summary>
    public short d4;
    public ushort d5;
    public ushort d6;
    public ushort d7;
}

BMP0.BIN 文件解析

public static RKRS_H ReadFile(string filePath)
{
    using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
    using (BinaryReader binaryReader = new BinaryReader(fileStream))
    {
        if (binaryReader.ReadInt32() != 0x00000028 || binaryReader.ReadInt32() != 0x53524b52)
        {
            throw new Exception("二进制文件格式错误");
        }

        RKRS_H rkrs = new RKRS_H();
        rkrs._bids = new List<BID_H>();

        fileStream.Position = 0x30;
        rkrs.offset = binaryReader.ReadInt32();

        fileStream.Position = 0x3c;
        rkrs.size = binaryReader.ReadInt16();
        rkrs.count = binaryReader.ReadInt16();

        fileStream.Position = 0x42;
        rkrs.value = binaryReader.ReadUInt16();

        fileStream.Position = rkrs.offset;

        for (int i = 0; i < rkrs.count; i++)
        {
            BID_H bid = new BID_H();
            bid.length = binaryReader.ReadInt32();
            bid.offset = binaryReader.ReadInt32();
            bid._bi = i;
            bid._bid = rkrs.value + i;
            rkrs._bids.Add(bid);
        }

        foreach (BID_H bid in rkrs._bids)
        {
            fileStream.Position = bid.offset;

            BIDD_H bidd = new BIDD_H();
            bid._bidd = bidd;

            bidd.width = binaryReader.ReadInt16();
            bidd.height = binaryReader.ReadInt16();
            bidd.d3 = binaryReader.ReadInt16();
            bidd.d4 = binaryReader.ReadInt16();
            bidd.d5 = binaryReader.ReadUInt16();
            bidd.d6 = binaryReader.ReadUInt16();
            bidd.d7 = binaryReader.ReadUInt16();
        }
        return rkrs;
    }
}

源码

https://github.com/he55/RK28UIAssets

链接

C# 提取 RK27/RK28 固件的图片资源

上一篇:你知道CSO对云安全的最大担忧是什么吗?


下一篇:AI按照网格对象的边缘抽取路径技术介绍