8

マジックナンバーを使用してコンテンツの開始をマークする単純な自己解凍型アーカイブを作成しています。今のところ、それはテキストファイルです:

MAGICNUMBER ....テキストファイルの内容

次に、実行可能ファイルの末尾にテキスト ファイルをコピーします。

programm.exe/b+textfile.txt/b sfx.exe をコピーします。

次のコードを使用して、2 番目に出現するマジック ナンバー (最初のものは明らかにハードコードされた定数) を見つけようとしています。

    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);
    }

そのため、すべてのファイルを 1 回スキャンしたところ、マジック ナンバーの最後の出現箇所であることがわかり、ここから最後までコピーしました。この手順で作成されたファイルは以前の試行よりも小さく見えますが、「自己解凍型」アーカイブに添付したファイルと同じではありません。なんで?

私の推測では、バイナリから文字列への変換が使用されているため、添付ファイルの先頭の位置計算が間違っていると思われます。もしそうなら、どのように位置計算を修正して正しくする必要がありますか?

また、マジックナンバーを選択して実際のファイル、たとえば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 ファイル形式に関する知識が必要なため、外部ツールを使用しないとより手間がかかります

于 2013-02-12T15:26:23.540 に答える