8

我正在使用幻数创建简单的自解压存档来标记内容的开头。现在它是一个文本文件:

MAGICNUMBER .... 文本文件的内容

接下来,将文本文件复制到可执行文件的末尾:

复制programm.exe/b+textfile.txt/b sfx.exe

我正在尝试使用以下代码查找幻数的第二次出现(第一个显然是硬编码常量):

    string my_filename = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
    StreamReader file = new StreamReader(my_filename);
    const int block_size = 1024;
    const string magic = "MAGICNUMBER";
    char[] buffer = new Char[block_size];
    Int64 count = 0;
    Int64 glob_pos = 0;
    bool flag = false;
    while (file.ReadBlock(buffer, 0, block_size) > 0)
    {
        var rel_pos = buffer.ToString().IndexOf(magic);
        if ((rel_pos > -1) & (!flag))
        {
            flag = true;
            continue;
        }

        if ((rel_pos > -1) & (flag == true))
        {
            glob_pos = block_size * count + rel_pos;
            break;
        }
        count++;
    }



    using (FileStream fs = new FileStream(my_filename, FileMode.Open, FileAccess.Read))
    {
        byte[] b = new byte[fs.Length - glob_pos];
        fs.Seek(glob_pos, SeekOrigin.Begin);
        fs.Read(b, 0, (int)(fs.Length - glob_pos));
        File.WriteAllBytes("c:/output.txt", b);

但由于某种原因,我复制了几乎整个文件,而不是最后的几千字节。是因为编译器优化,在类似的while循环中内联魔术常量吗?

我应该如何正确地进行自解压存档?

猜想我应该向后读取文件以避免编译器内联魔术常数乘法次数的问题。所以我通过以下方式修改了我的代码:

    string my_filename = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
    StreamReader file = new StreamReader(my_filename);
    const int block_size = 1024;
    const string magic = "MAGIC";
    char[] buffer = new Char[block_size];
    Int64 count = 0;
    Int64 glob_pos = 0;
    while (file.ReadBlock(buffer, 0, block_size) > 0)
    {
        var rel_pos = buffer.ToString().IndexOf(magic);
        if (rel_pos > -1)
        {
            glob_pos = block_size * count + rel_pos;
        }
        count++;
    }



    using (FileStream fs = new FileStream(my_filename, FileMode.Open, FileAccess.Read))
    {
        byte[] b = new byte[fs.Length - glob_pos];
        fs.Seek(glob_pos, SeekOrigin.Begin);
        fs.Read(b, 0, (int)(fs.Length - glob_pos));
        File.WriteAllBytes("c:/output.txt", b);
    }

所以我扫描了所有文件一次,发现我将是最后一次出现幻数并从这里复制到它的末尾。虽然此过程创建的文件似乎比以前的尝试小,但它绝不是我附加到我的“自解压”存档中的相同文件。为什么?

我的猜测是,由于使用了从二进制到字符串的转换,附件开头的位置计算是错误的。如果是这样,我应该如何修改我的头寸计算以使其正确?

另外我应该如何选择幻数然后使用真实文件,例如pdf?我将无法轻松修改 pdf 以在其中包含预定义的幻数。

4

4 回答 4

4

试试这个。一些 C# 流 IO 101:

    public static void Main()
    {
        String path = @"c:\here is your path";

        // Method A: Read all information into a Byte Stream
        Byte[] data = System.IO.File.ReadAllBytes(path);
        String[] lines = System.IO.File.ReadAllLines(path);

        // Method B: Use a stream to do essentially the same thing. (More powerful)
        // Using block essentially means 'close when we're done'. See 'using block' or 'IDisposable'.
        using (FileStream stream = File.OpenRead(path))
        using (StreamReader reader = new StreamReader(stream))
        {
            // This will read all the data as a single string
            String allData = reader.ReadToEnd();
        }

        String outputPath = @"C:\where I'm writing to";

        // Copy from one file-stream to another
        using (FileStream inputStream = File.OpenRead(path))
        using (FileStream outputStream = File.Create(outputPath))
        {
            inputStream.CopyTo(outputStream);

            // Again, this will close both streams when done.
        }

        // Copy to an in-memory stream
        using (FileStream inputStream = File.OpenRead(path))
        using (MemoryStream outputStream = new MemoryStream())
        {
            inputStream.CopyTo(outputStream);

            // Again, this will close both streams when done.
            // If you want to hold the data in memory, just don't wrap your 
            // memory stream in a using block.
        }

        // Use serialization to store data.
        var serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

        // We'll serialize a person to the memory stream.
        MemoryStream memoryStream = new MemoryStream();
        serializer.Serialize(memoryStream, new Person() { Name = "Sam", Age = 20 });

        // Now the person is stored in the memory stream (just as easy to write to disk using a 
        // file stream as well.

        // Now lets reset the stream to the beginning:
        memoryStream.Seek(0, SeekOrigin.Begin);

        // And deserialize the person
        Person deserializedPerson = (Person)serializer.Deserialize(memoryStream);

        Console.WriteLine(deserializedPerson.Name); // Should print Sam

    }

    // Mark Serializable stuff as serializable.
    // This means that C# will automatically format this to be put in a stream
    [Serializable]
    class Person
    {
        public String Name { get; set; }
        public Int32 Age { get; set; }
    }
于 2013-02-07T15:12:48.343 回答
3

您可以将压缩文件作为资源添加到项目本身:

项目 > 属性
在此处输入图像描述

将此资源的属性设置为Binary

然后,您可以使用检索资源

byte[] resource = Properties.Resources.NameOfYourResource;
于 2013-02-07T15:48:27.600 回答
3

最简单的解决方法是更换

const string magic = "MAGICNUMBER";

static string magic = "magicnumber".ToUpper();

但是整个魔术串方法存在更多问题。包含魔术字符串的文件是什么?我认为最好的解决方案是将文件大小放在文件之后。这样提取要容易得多:从最后一个字节读取长度并从文件末尾读取所需的字节数。

更新:除非您的文件很大,否则这应该可以工作。(在这种情况下,您需要使用一对循环缓冲区(以小块读取文件)):

string inputFilename = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
string outputFilename = inputFilename + ".secret";
string magic = "magic".ToUpper();

byte[] data = File.ReadAllBytes(inputFilename);
byte[] magicData = Encoding.ASCII.GetBytes(magic);

for (int idx = magicData.Length - 1; idx < data.Length; idx++) {
    bool found = true;
    for (int magicIdx = 0; magicIdx < magicData.Length; magicIdx++) {
        if (data[idx - magicData.Length + 1 + magicIdx] != magicData[magicIdx]) {
            found = false;
            break;
        }
    }
    if (found) {
        using (FileStream output = new FileStream(outputFilename, FileMode.Create)) {
            output.Write(data, idx + 1, data.Length - idx - 1);
        }
    }
}

Update2:这应该更快,使用很少的内存并处理各种大小的文件,但是您的程序必须是正确的可执行文件(大小是 512 字节的倍数):

string inputFilename = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
string outputFilename = inputFilename + ".secret";
string marker = "magic".ToUpper();

byte[] data = File.ReadAllBytes(inputFilename);
byte[] markerData = Encoding.ASCII.GetBytes(marker);
int markerLength = markerData.Length;

const int blockSize = 512; //important!

using(FileStream input = File.OpenRead(inputFilename)) {
    long lastPosition = 0;
    byte[] buffer = new byte[blockSize];
    while (input.Read(buffer, 0, blockSize) >= markerLength) {
        bool found = true;
        for (int idx = 0; idx < markerLength; idx++) {
            if (buffer[idx] != markerData[idx]) {
                found = false;
                break;
            }
        }
        if (found) {
            input.Position = lastPosition + markerLength;
            using (FileStream output = File.OpenWrite(outputFilename)) {
                input.CopyTo(output);
            }
        }
        lastPosition = input.Position;
    }
}

在此处阅读一些方法:http: //www.strchr.com/creating_self-extracting_executables

于 2013-02-10T21:47:43.623 回答
2

向后而不是向前搜索(假设您的文件不包含所述幻数)。

或附加您的(文本)文件,最后附加它的长度(或原始 exe 的长度),因此您只需要读取最后一个 DWORD /几个字节即可查看文件的长度 - 然后不需要幻数。

更可靠的是,将文件作为附加数据部分存储在可执行文件中。这在没有外部工具的情况下更加繁琐,因为它需要了解用于 NT 可执行文件的 PE 文件格式,qv http://msdn.microsoft.com/en-us/library/ms809762.aspx

于 2013-02-12T15:26:23.540 回答