吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 866|回复: 29
收起左侧

[其他原创] 【午夜随笔】(致新人小白,大佬绕道)几款小游戏的文件提取的C#控制台程序代码

[复制链接]
烟99 发表于 2024-11-29 14:17
本帖最后由 烟99 于 2024-11-29 16:41 编辑

我称他为午夜随笔是因为我从初学逆向的时候,从晚上11点开始,一口气儿写了六款游戏的资源文件解包代码,调试完了程序然后又注释的代码,瞅了一眼窗外,天已经蒙蒙亮了,再一看墙上的挂钟已经早上五点半了,就差把命搭进去了

本次分享的代码有四个,一个XOR运算代码、三个解包代码,主要讲述一些十几年前的老游戏的包文件内部数据的存储方式,
目的是让各位同学特别是正在学逆向的新人小白能够理解数据解包的最基本的原理,提取到的文件仅供个人研究,切勿非法用途!

本次分享的六款游戏的解包代码为:
1、劳拉之城(Laruaville)
2、诅咒之屋(CursedHouse)
3、埃及祖玛4 探索永恒(LuxoQuestForTheAfterlife)
4、大富翁7 正传
5、大富翁7 游宝岛
6、大富翁7 游香江

使用方法很简单,创建一个C#控制台工程,拷贝后编译运行即可。由于文件头部检查用到了SequenceEqual方法,所以需要在.Net Framework4.0以上的版本才能运行。
XOR运算代码留在外面,解包的代码回帖可见,五天后的这个时候对外开放。

[C#] 纯文本查看 复制代码
using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {

        Console.WriteLine(
            "-----------------------------------------\n" +
            "文件异或运算工具\n" +
            "-----------------------------------------\n" +
            "\n请输入要异或运算的文件:\n(可拖拽文件到命令行窗口)"
            );
        string fileName = Console.ReadLine().Trim('\"');        // 加一个Trim方法用于去除拖拽文件时首尾出现的引号,以防路径格式不正确

        Console.Write("请输入要与哪个HEX单字节进行异或运算(例如:FF): ");
        string hexByteStr = Console.ReadLine();

        // 检查HEX单字节格式是否正确
        if (hexByteStr.Length != 2)
        {
            Console.WriteLine("输入的HEX单字节格式不正确");
            return;
        }

        // 尝试将文本型数据转换转为字节数据
        byte xorByte;
        try
        {
            xorByte = Convert.ToByte(hexByteStr, 16);
        }
        catch (FormatException)
        {
            Console.WriteLine("输入的HEX单字节无法转换");
            return;
        }

        // 开始异或运算
        byte[] buffer = new byte[1024];
        long totalBytes = new FileInfo(fileName).Length;
        long processedBytes = 0;

        // 创建文件输入输出流
        using (FileStream inputStream = new FileStream(fileName, FileMode.Open))
        {
            using (FileStream outputStream = new FileStream(fileName + ".xor", FileMode.Create))
            {
                // 一边异或,一边写出
                while (true)
                {
                    // 读入字节
                    int bytesRead = await inputStream.ReadAsync(buffer, 0, buffer.Length);
                    if (bytesRead == 0)
                        break;
                    
                    // 开始异或运算
                    for (int i = 0; i < bytesRead; i++)
                    {
                        buffer[i] = (byte)(buffer[i] ^ xorByte);
                    }

                    // 写出异或结果
                    await outputStream.WriteAsync(buffer, 0, bytesRead);

                    // 调试输出处理进度
                    processedBytes += bytesRead;
                    Console.WriteLine($"已处理: {processedBytes * 100 / totalBytes}%");
                }
            }
        }
        // 操作完成,按任意键退出
        Console.WriteLine("XOR运算完成!按任意键退出...");
        Console.ReadKey();
    }
}



解包代码
《劳拉之城》和《诅咒之屋》的源码:
特别注意:这两个游戏解包BIN文件前必须要先对BIN文件进行XOR运算才能解包。
其中,《劳拉之城》的BIN与0xC2进行XOR运算;《诅咒之屋》的BIN与0xAD进行XOR运算,否则会出错!

单字节XOR是早年小游戏的包文件最常用的加密方法,之所以为什么把XOR和解包分开写就是为了方便大家能XOR其他游戏的文件
[C#] 纯文本查看 复制代码
using System;
using System.IO;
using System.Text;

namespace UNBIN
{
    internal class Program
    {
        /// <summary>
        /// 入口函数
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            // 等待用户指定文件
            Console.WriteLine(
                "-----------------------------------------\n" +
                "BIN提取工具\n" +
                "-----------------------------------------\n" +
                "\n支持的游戏:\n" +
                "1、劳拉之城(Laruaville)\n" +
                "2、诅咒之屋(CursedHouse)\n" +
                "\n请先对BIN文件进行异或运算(XOR)再使用本程序提取资源!\n" +
                "游戏Laruaville的XOR值:0xC2;游戏CursedHouse的XOR值:0xAD\n\n" +
                "\n异或运算后,请输入已进行异或运算的文件路径:\n(可拖拽文件到命令行窗口)"
                );
            string file = Console.ReadLine().Trim('\"');        // 加一个Trim方法用于去除拖拽文件时首尾出现的引号,以防路径格式不正确
            try
            {
                // 开始解包
                UnBIN(file);

                // 解包结束按任意键退出
                Console.WriteLine("完成!按任意键退出...");
                Console.ReadKey();
            }
            catch (Exception ex)
            {
                Console.WriteLine(
                    "\n\n解包发生错误!可能的原因:\n" +
                    "1、路径格式不正确。\n" +
                    "2、文件未经异或运算就解包。\n" +
                    "3、根本就不是有效的文件。\n" +
                    "4、其他未知错误:\n" +
                     ex.Message +
                     "\n\n按任意键退出,请解决上述问题后再运行本程序尝试提取。");
                Console.ReadKey();
            }

        }

        /// <summary>
        /// 解包资源文件
        /// </summary>
        /// <param name="filePath"></param>
        static void UnBIN(string filePath)
        {
            // 开启文件流,模式为读模式
            using (FileStream fs = new FileStream(filePath, FileMode.Open))
            {
                // 置解包文件夹,输出路径为文件所在路径下的已文件名+ _Unpacked命名的文件夹。
                string savePath = Path.GetDirectoryName(filePath) + "\\" + Path.GetFileNameWithoutExtension(filePath) + "_Unpacked\\";

                // 取文件数量
                fs.Seek(4, SeekOrigin.Begin);
                byte[] fileCountBit = new byte[4];      // 字节集状态下的数据区物理大小数值
                long fileCount;                         // 长整型的数据区物理大小数值
                fs.Read(fileCountBit, 0, fileCountBit.Length);
                fileCount = BitConverter.ToInt64(EightByteConverter(fileCountBit), 0);

                // 定义一个现行文件流指针变量值,并设为8,准备执行解包操作
                long currentPos = 8;

                // 通过for循环方式开始解包,循环次数为文件数量
                for (int i = 0; i < fileCount; i++)
                {
                   /* 
                    * 目录区数据读取规则:
                    * 取文件偏移、大小、文件名长度、文件名,除了读取完文件名好要按照文件名长度偏移指定
                    * 字节外,其他信息读取完成后均偏移四个字节。
                    */

                    // 取文件偏移
                    fs.Seek(currentPos, SeekOrigin.Begin);
                    byte[] fileOffsetBit = new byte[4];
                    fs.Read(fileOffsetBit, 0, fileOffsetBit.Length);
                    long fileOffset = BitConverter.ToInt64(EightByteConverter(fileOffsetBit), 0);
                    currentPos += fileOffsetBit.Length;

                    // 取文件大小
                    fs.Seek(currentPos, SeekOrigin.Begin);
                    byte[] fileSizeBit = new byte[4];
                    fs.Read(fileSizeBit, 0, fileSizeBit.Length);
                    long fileSize = BitConverter.ToInt64(EightByteConverter(fileSizeBit), 0);
                    currentPos += fileSizeBit.Length;

                    // 取文件名长度
                    fs.Seek(currentPos, SeekOrigin.Begin);
                    byte[] fileLengthBit = new byte[4];
                    fs.Read(fileLengthBit, 0, fileLengthBit.Length);
                    long fileLength = BitConverter.ToInt64(EightByteConverter(fileLengthBit), 0);
                    currentPos += fileLengthBit.Length;

                    // 取文件名称
                    fs.Seek(currentPos, SeekOrigin.Begin);
                    byte[] fileNamehBit = new byte[fileLength];
                    fs.Read(fileNamehBit, 0, fileNamehBit.Length);
                    string fileName = Encoding.UTF8.GetString(fileNamehBit);
                    currentPos += fileNamehBit.Length;

                    // 将文件流指针移到文件偏移位置,开始拷贝数据
                    long copyPos = fileOffset;
                    fs.Seek(copyPos, SeekOrigin.Begin);

                    // 取文件数据
                    byte[] unPackByte = new byte[fileSize];
                    fs.Read(unPackByte, 0, unPackByte.Length);

                    // 文件绝对路径为解包路径+包内文件路径,如果文件夹不存在则创建                
                    string saveTarget = savePath + fileName;

                    if (!Directory.Exists(Path.GetDirectoryName(saveTarget)))
                    {
                        Directory.CreateDirectory(Path.GetDirectoryName(saveTarget));
                    }

                    // 调试输出处理结果
                    Console.WriteLine(
                        "-----------------------------------------\n" +
                        "正在解包:" + fileName +
                        "\n文件偏移:0x" + fileOffset.ToString("X") +
                        "\n文件大小:" + BytesToSize(fileSize) +
                        "\n-----------------------------------------\n");

                    // 写到文件
                    File.WriteAllBytes(saveTarget, unPackByte);

                    // 文件流指针回到下一条目录信息的偏移位置
                    fs.Seek(currentPos, SeekOrigin.Begin);
                }
                Console.WriteLine("输出目录:"+ savePath);
                // 关闭文件流,解除文件占用
                fs.Close();
            }
        }

        /// <summary>
        ///  <字节集> 8字节补零
        /// <param name="bytes">(字节集 欲填充0x00的8字节字节集数组)</param>
        /// <returns><para>返回处理后的8字节数组</para></returns>
        /// </summary>
        static byte[] EightByteConverter(byte[] bytes)
        {
            // 补到8字节用于Bit转换
            byte[] newByees = new byte[8];
            Array.Copy(bytes, 0, newByees, 0, bytes.Length);
            for (int i = bytes.Length; i < 8; i++)
            {
                newByees[i] = 0x00; // 补充0x00
            }
            // 将处理完的字节集数组交还给bytes变量
            return newByees;
        }

        /// <summary>
        /// <文本型> 字节大小数值转换
        /// <param name="size">(长整型 欲转换的字节大小数组)</param>
        /// <returns><para>成功返回字节大小</para></returns>
        /// </summary>
        public static string BytesToSize(long size)
        {
            var num = 1024.00; //byte
            if (size < num)
                return size + " Byte";
            if (size < Math.Pow(num, 2))
                return (size / num).ToString("f2") + " KB";
            if (size < Math.Pow(num, 3))
                return (size / Math.Pow(num, 2)).ToString("f2") + " MB";
            if (size < Math.Pow(num, 4))
                return (size / Math.Pow(num, 3)).ToString("f2") + " GB";
            if (size < Math.Pow(num, 5))
                return (size / Math.Pow(num, 4)).ToString("f2") + " TB";
            if (size < Math.Pow(num, 6))
                return (size / Math.Pow(num, 5)).ToString("f2") + " PB";
            if (size < Math.Pow(num, 7))
                return (size / Math.Pow(num, 6)).ToString("f2") + " EB";
            if (size < Math.Pow(num, 8))
                return (size / Math.Pow(num, 7)).ToString("f2") + " ZB";
            if (size < Math.Pow(num, 9))
                return (size / Math.Pow(num, 8)).ToString("f2") + " YB";
            if (size < Math.Pow(num, 10))
                return (size / Math.Pow(num, 9)).ToString("f2") + "DB";
            return (size / Math.Pow(num, 10)).ToString("f2") + "NB";
        }
    }
}


《埃及祖玛4 探索永恒》的源码:

[C#] 纯文本查看 复制代码
using System;
using System.IO;
using System.Linq;
using System.Text;

namespace UNNPK
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 等待用户指定文件
            Console.WriteLine(
                "-----------------------------------------\n" +
                "NPK提取工具\n" +
                "-----------------------------------------\n" +
                "\n支持的游戏:\n" +
                "埃及祖玛4 探索永恒(LuxoQuestForTheAfterlife)\n" +
                "\n请输入要提取的NPK文件路径:\n(可拖拽文件到命令行窗口)"
                );
            string file = Console.ReadLine().Trim('\"');        // 加一个Trim方法用于去除拖拽文件时首尾出现的引号,以防路径格式不正确
            try
            {
                // 开始解包
                UnNPK(file);

                // 解包结束按任意键退出
                Console.WriteLine("完成!按任意键退出...");
                Console.ReadKey();
            }
            catch (Exception ex)
            {
                Console.WriteLine(
                    "\n\n解包发生错误!可能的原因:\n" +
                    "1、路径格式不正确。\n" +
                    "2、根本就不是有效的文件。\n" +
                    "\n具体错误:\n" +
                     ex.Message +
                     "\n\n按任意键退出,请解决上述问题后再运行本程序尝试提取。");
                Console.ReadKey();
            }
        }

        /// <summary>
        /// 解包资源文件
        /// </summary>
        /// <param name="filePath"></param>
        static void UnNPK(string filePath)
        {
            // 开启文件流,模式为读模式
            using (FileStream fs = new FileStream(filePath, FileMode.Open))
            {
                // 检查文件头部是否合规
                fs.Seek(0, SeekOrigin.Begin);

                // 定义文件魔数
                byte[] magNum = { 0x4E, 0x6F, 0x76, 0x61, 0x50, 0x61, 0x63, 0x6B };

                // 取目标NPK文件魔数
                byte[] existMagNum = new byte[magNum.Length];
                Array.Copy(magNum, 0, existMagNum, 0, existMagNum.Length);

                // 检查是否为NPK文件
                if (!existMagNum.SequenceEqual(magNum))
                {
                    throw new Exception("The file\"" + filePath + "\" is not a valid NPK format file!");
                }


                // 置解包文件夹,输出路径为文件所在路径下的已文件名+ _Unpacked命名的文件夹。
                string savePath = Path.GetDirectoryName(filePath) + "\\" + Path.GetFileNameWithoutExtension(filePath) + "_Unpacked\\";

                // 开始解析目录区
                // 目录区起始标记为“DictStart”,结束标记为“DictEnd”,定义一个字节集数组并放入
                byte[] dictStartMarker = { 0x44, 0x69, 0x63, 0x74, 0x53, 0x74, 0x61, 0x72, 0x74 };
                byte[] dicteEndMarker = { 0x44, 0x69, 0x63, 0x74, 0x45, 0x6E, 0x64 };
                // 取这两个标记的所在位置
                long dictStartIndex = FindIndex(fs, dictStartMarker) + dictStartMarker.Length;
                long dictEndIndex = FindIndex(fs, dicteEndMarker);

                // 目录区起始结束位置处理完成,开始拷贝目录区数据、获取文件数量
                fs.Seek(dictStartIndex, SeekOrigin.Begin);
                // 定义目录区字节集数组,大小为目录区起始结束位置之差
                byte[] dictByte = new byte[dictEndIndex - dictStartIndex];
                fs.Read(dictByte, 0, dictByte.Length);

                // 目录区字节集数组拷贝完成,开始获取文件数量
                // 取文件个数
                byte[] fileCountBit = new byte[4];      // 字节集状态下的数据区物理大小数值
                long fileCount;                         // 长整型的数据区物理大小数值
                Array.Copy(dictByte, 0, fileCountBit, 0, 4);
                fileCount = BitConverter.ToInt64(EightByteConverter(fileCountBit), 0);

                // 创建字节偏移记录,因为文件数量已经占去了四字节,所以初始位置要取其长度
                long byteOffset = fileCountBit.Length;

                // 通过for循环方式开始解包,循环次数为文件数量
                for (int i = 0; i < fileCount; i++)
                {
                    /* 
                     * 目录区数据读取规则:
                     * 取文件名长度、文件名、偏移、大小,除了读取完文件名好要按照文件名长度偏移指定
                     * 字节外,其他信息读取完成后均偏移四个字节。
                     */
                    
                    // 取文件名长度
                    byte[] fileNameLenghBit = new byte[4];
                    Array.Copy(dictByte, byteOffset, fileNameLenghBit, 0, fileNameLenghBit.Length);
                    int fileNameLengh = BitConverter.ToInt32(EightByteConverter(fileNameLenghBit), 0);
                    byteOffset += 4;

                    // 取文件名
                    byte[] fileNameBit = new byte[fileNameLengh];
                    Array.Copy(dictByte, byteOffset, fileNameBit, 0, fileNameLengh);
                    string fileName = Encoding.Default.GetString(fileNameBit);                   
                    byteOffset += fileNameLengh;            //偏移移动文件名长度位数

                    // 取文件偏移
                    byte[] fileOffsetBit = new byte[4];
                    Array.Copy(dictByte, byteOffset, fileOffsetBit, 0, fileOffsetBit.Length);                    
                    long fileOffset = BitConverter.ToInt64(EightByteConverter(fileOffsetBit), 0);
                    byteOffset += 4;
                    fs.Seek(byteOffset, SeekOrigin.Begin);

                    // 取文件大小
                    byte[] fileSizeBit = new byte[4];
                    Array.Copy(dictByte, byteOffset, fileSizeBit, 0, fileSizeBit.Length);
                    long fileSize = BitConverter.ToInt64(EightByteConverter(fileSizeBit), 0);

                    // 偏移四个字节,为下一次拷贝文件做准备
                    byteOffset += 4;

                    // 将文件流指针移到文件偏移位置,开始拷贝数据
                    fs.Seek(fileOffset, SeekOrigin.Begin);

                    // 取文件数据
                    byte[] unPackByte = new byte[fileSize];
                    fs.Read(unPackByte, 0, unPackByte.Length);

                    // 文件绝对路径为解包路径+包内文件路径,如果文件夹不存在则创建                
                    string saveTarget = savePath + fileName;
                    if (!Directory.Exists(Path.GetDirectoryName(saveTarget)))
                    {
                        Directory.CreateDirectory(Path.GetDirectoryName(saveTarget));
                    }

                    // 调试输出处理结果
                    Console.WriteLine(
                        "-----------------------------------------\n" +
                        "正在解包:" + fileName +
                        "\n文件偏移:0x" + fileOffset.ToString("X") +
                        "\n文件大小:" + BytesToSize(fileSize) +
                        "\n-----------------------------------------\n");

                    // 写到文件
                    File.WriteAllBytes(saveTarget, unPackByte);
                }
                Console.WriteLine("输出目录:"+ savePath);
                // 关闭文件流,解除文件占用
                fs.Close();
            }
        }

        /// <summary>
        ///  <字节集> 八字节补零
        /// <param name="bytes">(字节集 欲填充0x00的8字节字节集数组)</param>
        /// <returns><para>返回处理后的八字节数组</para></returns>
        /// </summary>
        static byte[] EightByteConverter(byte[] bytes)
        {
            // 补到8字节用于Bit转换
            byte[] newByees = new byte[8];
            Array.Copy(bytes, 0, newByees, 0, bytes.Length);
            for (int i = bytes.Length; i < 8; i++)
            {
                newByees[i] = 0x00; // 补充0x00
            }
            // 将处理完的字节集数组交还给bytes变量
            return newByees;
        }
        /// <summary>
        /// <文本型> 字节大小数值转换
        /// <param name="size">(长整型 欲转换的字节大小数组)</param>
        /// <returns><para>成功返回字节大小</para></returns>
        /// </summary>
        public static string BytesToSize(long size)
        {
            var num = 1024.00; //byte
            if (size < num)
                return size + " Byte";
            if (size < Math.Pow(num, 2))
                return (size / num).ToString("f2") + " KB";
            if (size < Math.Pow(num, 3))
                return (size / Math.Pow(num, 2)).ToString("f2") + " MB";
            if (size < Math.Pow(num, 4))
                return (size / Math.Pow(num, 3)).ToString("f2") + " GB";
            if (size < Math.Pow(num, 5))
                return (size / Math.Pow(num, 4)).ToString("f2") + " TB";
            if (size < Math.Pow(num, 6))
                return (size / Math.Pow(num, 5)).ToString("f2") + " PB";
            if (size < Math.Pow(num, 7))
                return (size / Math.Pow(num, 6)).ToString("f2") + " EB";
            if (size < Math.Pow(num, 8))
                return (size / Math.Pow(num, 7)).ToString("f2") + " ZB";
            if (size < Math.Pow(num, 9))
                return (size / Math.Pow(num, 8)).ToString("f2") + " YB";
            if (size < Math.Pow(num, 10))
                return (size / Math.Pow(num, 9)).ToString("f2") + "DB";
            return (size / Math.Pow(num, 10)).ToString("f2") + "NB";
        }

        /// <summary>
        /// <长整型>在文件流中寻找字节集
        /// <param name="fs">(文件流 目标文件流, </param>
        /// <param name="marker">字节集 欲寻找的字节集数组, </param>
        /// <param name="startPosition">长整型 开始寻找的位置)</param>
        /// <returns><para>成功返回所在位置,失败则返回-1</para></returns>
        /// </summary>
        public static long FindIndex(FileStream fs, byte[] marker, long startPosition = 0)
        {
            byte[] buffer = new byte[marker.Length];
            long index = startPosition;
            while (true)
            {
                fs.Seek(index, SeekOrigin.Begin);
                int read = fs.Read(buffer, 0, buffer.Length);
                if (read != buffer.Length)
                {
                    return -1;
                }
                bool found = true;
                for (int i = 0; i < buffer.Length; i++)
                {
                    if (buffer[i] != marker[i])
                    {
                        found = false;
                        break;
                    }
                }
                if (found)
                {
                    return index;
                }
                index++;
            }
        }
    }
}


《大富翁7》全系列的源码:
[C#] 纯文本查看 复制代码
using System;
using System.IO;
using System.Linq;
using System.Text;

namespace UNPCK
{
    internal class Program
    {
        /// <summary>
        /// 入口函数
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            // 等待用户指定文件
            Console.WriteLine(
                "-----------------------------------------\n" +
                "PCK提取工具\n" +
                "-----------------------------------------\n" +
                "\n支持的游戏:\n" +
                "大富翁7全系列(包括但不限于正传、游宝岛、游香江)\n" +
                "\n请输入要提取的PCK文件路径::\n(可拖拽文件到命令行窗口)"
                );
            string file = Console.ReadLine().Trim('\"');        // 加一个Trim方法用于去除拖拽文件时首尾出现的引号,以防路径格式不正确
            try
            {
                // 开始解包
                UnPCK(file);

                // 解包结束按任意键退出
                Console.WriteLine("完成!按任意键退出...");
                Console.ReadKey();
            }
            catch (Exception ex)
            {
                Console.WriteLine(
                    "\n\n解包发生错误!可能的原因:\n" +
                    "1、路径格式不正确。\n" +
                    "2、根本就不是有效的文件。\n" +
                    "\n具体错误:\n" +
                     ex.Message +
                     "\n\n按任意键退出,请解决上述问题后再运行本程序尝试提取。");
                Console.ReadKey();
            }
        }

        /// <summary>
        /// 解包资源文件
        /// </summary>
        /// <param name="filePath"></param>
        static void UnPCK(string filePath)
        {
            // 开启文件流,模式为读模式
            using (FileStream fs = new FileStream(filePath, FileMode.Open))
            {
                fs.Seek(0, SeekOrigin.Begin);

                // 检查文件头部是否合规
                // 定义文件魔数
                byte[] magNum = { 0x50, 0x43, 0x4B, 0x00 };

                // 取目标PCK文件魔数
                byte[] existMagNum = new byte[magNum.Length];
                Array.Copy(magNum, 0, existMagNum, 0, existMagNum.Length);

                // 检查是否为PCK文件
                if (!existMagNum.SequenceEqual(magNum))
                {
                    throw new Exception("The file\"" + filePath + "\" is not a valid PCK format file!");
                }

                // 置解包文件夹,输出路径为文件所在路径下的已文件名+ _Unpacked命名的文件夹。
                string savePath = Path.GetDirectoryName(filePath) + "\\" + Path.GetFileNameWithoutExtension(filePath) + "_Unpacked\\";

                // 取文件数量
                fs.Seek(4, SeekOrigin.Begin);
                byte[] fileCountBit = new byte[4];      // 字节集状态下的数据区物理大小数值
                long fileCount;                         // 长整型的数据区物理大小数值
                fs.Read(fileCountBit, 0, fileCountBit.Length);
                fileCount = BitConverter.ToInt64(EightByteConverter(fileCountBit), 0);

                // 定义一个现行文件流指针变量值,并设为8,准备执行解包操作
                long currentPos = 8;

                // 文件名称块大小为256
                int fileBlockSize = 256;

                // 通过for循环方式开始解包,循环次数为文件数量
                for (int i = 0; i < fileCount; i++)
                {
                    /* 
                     * 目录区数据读取规则:
                     * 1、从第8个字节开始,先读取文件名称块大小,256字节。
                     * 2、计算文件名称块数据的非零字节数,并按计算结果拷贝文件名。
                     * 3、取偏移、大小,除了读取完文件名好要按照文件名长度偏移指定
                     * 字节外,其他信息读取完成后均偏移四个字节。、                   
                     */

                    // 取文件名称块数据
                    fs.Seek(currentPos, SeekOrigin.Begin);
                    byte[] fileNamehBit = new byte[fileBlockSize];
                    fs.Read(fileNamehBit, 0, fileBlockSize);

                    // 取非零字节数
                    int nonZeroByteCount = 0;
                    foreach (byte b in fileNamehBit)
                    {
                        if (b != 0x00)
                        {
                            nonZeroByteCount++;
                        }
                    }

                    // 在读取到的文件名称块数据中复制非零字节数到另一个字节集数组就是文件名称
                    byte[] fileNamehBitnonZero = new byte[nonZeroByteCount];
                    Array.Copy(fileNamehBit, fileNamehBitnonZero, fileNamehBitnonZero.Length);
                    string fileName = Encoding.UTF8.GetString(fileNamehBitnonZero);
                    currentPos += fileBlockSize;

                    // 取文件偏移
                    fs.Seek(currentPos, SeekOrigin.Begin);
                    byte[] fileOffsetBit = new byte[4];
                    fs.Read(fileOffsetBit, 0, fileOffsetBit.Length);
                    long fileOffset = BitConverter.ToInt64(EightByteConverter(fileOffsetBit), 0);
                    currentPos += fileOffsetBit.Length;

                    // 取文件大小
                    fs.Seek(currentPos, SeekOrigin.Begin);
                    byte[] fileSizeBit = new byte[4];
                    fs.Read(fileSizeBit, 0, fileSizeBit.Length);
                    long fileSize = BitConverter.ToInt64(EightByteConverter(fileSizeBit), 0);
                    currentPos += fileSizeBit.Length;               //偏移移动文件名长度位数

                    // 将文件流指针移到文件偏移位置,开始拷贝数据
                    long copyPos = fileOffset;
                    fs.Seek(copyPos, SeekOrigin.Begin);

                    // 取文件数据
                    byte[] unPackByte = new byte[fileSize];
                    fs.Read(unPackByte, 0, unPackByte.Length);

                    // 文件绝对路径为解包路径+包内文件路径,如果文件夹不存在则创建                
                    string saveTarget = savePath + fileName;
                    if (!Directory.Exists(Path.GetDirectoryName(saveTarget)))
                    {
                        Directory.CreateDirectory(Path.GetDirectoryName(saveTarget));
                    }

                    // 调试输出处理结果
                    Console.WriteLine(
                        "-----------------------------------------\n" +
                        "正在解包:" + fileName +
                        "\n文件偏移:0x" + fileOffset.ToString("X") +
                        "\n文件大小:" + BytesToSize(fileSize) +
                        "\n-----------------------------------------\n");

                    // 写到文件
                    File.WriteAllBytes(saveTarget, unPackByte);

                    // 文件流指针回到下一条目录信息的偏移位置
                    fs.Seek(currentPos, SeekOrigin.Begin);
                }
                Console.WriteLine("输出目录:"+ savePath);
                // 关闭文件流,解除文件占用
                fs.Close();
            }
        }

        /// <summary>
        ///  <字节集> 8字节补零
        /// <param name="bytes">(字节集 欲填充0x00的8字节字节集数组)</param>
        /// <returns><para>返回处理后的8字节数组</para></returns>
        /// </summary>
        static byte[] EightByteConverter(byte[] bytes)
        {
            // 补到8字节用于Bit转换
            byte[] newByees = new byte[8];
            Array.Copy(bytes, 0, newByees, 0, bytes.Length);
            for (int i = bytes.Length; i < 8; i++)
            {
                newByees[i] = 0x00; // 补充0x00
            }
            // 将处理完的字节集数组交还给bytes变量
            return newByees;
        }

        /// <summary>
        /// <文本型> 字节大小数值转换
        /// <param name="size">(长整型 欲转换的字节大小数组)</param>
        /// <returns><para>成功返回字节大小</para></returns>
        /// </summary>
        public static string BytesToSize(long size)
        {
            var num = 1024.00; //byte
            if (size < num)
                return size + " Byte";
            if (size < Math.Pow(num, 2))
                return (size / num).ToString("f2") + " KB";
            if (size < Math.Pow(num, 3))
                return (size / Math.Pow(num, 2)).ToString("f2") + " MB";
            if (size < Math.Pow(num, 4))
                return (size / Math.Pow(num, 3)).ToString("f2") + " GB";
            if (size < Math.Pow(num, 5))
                return (size / Math.Pow(num, 4)).ToString("f2") + " TB";
            if (size < Math.Pow(num, 6))
                return (size / Math.Pow(num, 5)).ToString("f2") + " PB";
            if (size < Math.Pow(num, 7))
                return (size / Math.Pow(num, 6)).ToString("f2") + " EB";
            if (size < Math.Pow(num, 8))
                return (size / Math.Pow(num, 7)).ToString("f2") + " ZB";
            if (size < Math.Pow(num, 9))
                return (size / Math.Pow(num, 8)).ToString("f2") + " YB";
            if (size < Math.Pow(num, 10))
                return (size / Math.Pow(num, 9)).ToString("f2") + "DB";
            return (size / Math.Pow(num, 10)).ToString("f2") + "NB";
        }
    }
}

免费评分

参与人数 2威望 +1 吾爱币 +11 热心值 +2 收起 理由
苏紫方璇 + 1 + 10 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
sky_walk + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

24427 发表于 2024-11-29 14:52
终于找到了,多谢分享
vitalist 发表于 2024-11-29 14:56
niubizilaodao 发表于 2024-11-29 15:07
abies 发表于 2024-11-29 15:13
看一下大富翁
flyfish441 发表于 2024-11-29 15:21
感谢分享!~~~~~~~~~~~~~~~~
GraceMackintos 发表于 2024-11-29 15:34
感谢分享。
lovesdeeply 发表于 2024-11-29 15:40
新手看一下能不能玩转。致小白,小白应该能看懂七七八八吧。
zhangfeng_223 发表于 2024-11-29 15:45
感谢分享
提拉米苏子冉 发表于 2024-11-29 15:49
感谢大佬分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-1-7 20:03

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表