3

背景:

我对 C# 相当陌生,目前正在从事一个主要用于学习的项目。我正在创建一个库来处理 Quake 和 Quake II 使用的 .PAK 文件。我可以很好地阅读和提取它们,以及编写一个空白存档,并且我正在将数据写入存档。

可以在这里找到基本规范以供参考:http ://debian.fmi.uni-sofia.bg/~sergei/cgsr/docs/pak.txt

问题:

我想寻找二进制数据部分的末尾并从给定文件中插入数据。创建空文件时,我在方法中使用 BinaryWriter,所以这就是我要用于插入的方法。根据我的发现,使用 BinaryWriter 查找和写入将在给定位置写入并覆盖现有数据。

我的目标是简单地在给定点插入数据而不会丢失任何现有数据。如果这是不可能的,我想我需要提取包含单个文件信息(名称、偏移量、长度)的整个索引,添加二进制数据,然后为我正在编写的文件附加索引和附加信息。

有什么想法或建议吗?

4

4 回答 4

4

不能使用 C# IO 类直接将数据插入文件。最简单的方法是将其读入缓冲区并操作缓冲区,或者将第一部分写入临时文件,然后插入新数据,然后写入文件的其余部分,然后重命名。或者,您可以在文件末尾附加一些数据并将所有内容向上移动以创建适量的可用空间,以便在其中插入数据。

看看http://www.codeproject.com/Articles/17716/Insert-Text-into-Existing-Files-in-C-Without-Temp

于 2012-05-21T16:51:24.113 回答
3

直接使用基于流的对象(如 FileStream)是文件级操作的更好选择。

没有办法在中间插入数据到任何文件中,您必须读取整个文件并将其与新插入的部分一起保存或复制到新文件直到插入点,添加新数据然后复制其余部分(您可以此时删除原始/重命名新)。

于 2012-05-21T16:35:04.387 回答
2

很难有效地进行插入。特别是如果您想对同一个流进行多次插入。您可以编写自己的Stream类来跟踪您所做的任何插入,并在实际上没有插入更改时假装已插入更改。例如,如果您有一个Aat 位置 10 并且您在该位置插入一个X。您的类可以,而不是将插入写入流并移动所有后续字节,而是X在您请求位置 10 时返回,并A在您请求位置 11 时从基础流返回(而在基础流中它仍位于位置 10)。然后当你打电话FlushClose在流上,整个更改的流一次全部刷新到底层流。这需要一些非常仔细的工程才能做到正确,因为您可能在插入中插入,以任何顺序插入,等等......

这是我为您编写的一些代码,它将任意字节插入流中并移动所有后续字节,而无需任何巧妙的缓存。不幸的是,它不使用 aBinaryWriter而是直接在Stream(必须是可搜索的)上工作,因为这样更容易编码。它在流的当前位置插入,然后该位置就在插入的字节之后。要插入字符串,请使用Encoding类将它们转换为字节。

public static class StreamExtensions
{
    /// <summary>
    /// Inserts the specified binary data at the current position in the stream.
    /// </summary>
    /// <param name="stream">The stream.</param>
    /// <param name="array">The array.</param>
    /// <remarks>
    /// <para>Note that this is a very expensive operation, as all the data following the insertion point has to
    /// be moved.</para>
    /// <para>When this method returns, the writer will be positioned at the end of the inserted data.</para>
    /// </remarks>
    public static void Insert(this Stream stream, byte[] array)
    {
        #region Contract
        if (stream == null)
            throw new ArgumentNullException("writer");
        if (array == null)
            throw new ArgumentNullException("array");
        if (!stream.CanRead || !stream.CanWrite || !stream.CanSeek)
            throw new ArgumentException("writer");
        #endregion

        long originalPosition = stream.Position;
        long readingPosition = originalPosition;
        long writingPosition = originalPosition;

        int length = array.Length;
        int bufferSize = 4096;
        while (bufferSize < length)
            bufferSize *= 2;

        int currentBuffer = 1;
        int[] bufferContents = new int[2];
        byte[][] buffers = new [] { new byte[bufferSize], new byte[bufferSize] };
        Array.Copy(array, buffers[1 - currentBuffer], array.Length);
        bufferContents[1 - currentBuffer] = array.Length;

        bool done;

        do
        {
            bufferContents[currentBuffer] = ReadBlock(stream, ref readingPosition, buffers[currentBuffer], out done);

            // Switch buffers.
            currentBuffer = 1 - currentBuffer;

            WriteBlock(stream, ref writingPosition, buffers[currentBuffer], bufferContents[currentBuffer]);

        } while (!done);

        // Switch buffers.
        currentBuffer = 1 - currentBuffer;

        // Write the remaining data.
        WriteBlock(stream, ref writingPosition, buffers[currentBuffer], bufferContents[currentBuffer]);

        stream.Position = originalPosition + length;
    }

    private static void WriteBlock(Stream stream, ref long writingPosition, byte[] buffer, int length)
    {
        stream.Position = writingPosition;
        stream.Write(buffer, 0, length);
        writingPosition += length;
    }

    private static int ReadBlock(Stream stream, ref long readingPosition, byte[] buffer, out bool done)
    {
        stream.Position = readingPosition;
        int bufferContent = 0;
        int read;
        do
        {
            read = stream.Read(buffer, bufferContent, buffer.Length - bufferContent);
            bufferContent += read;
            readingPosition += read;
        } while (read > 0 && read < buffer.Length);
        done = read == 0;
        return bufferContent;
    }
}

要使用此方法,请将代码粘贴到与您的代码相同的命名空间中,然后:

Stream someStream = ...;
byte[] dataToInsert = new byte[]{ 0xDE, 0xAD, 0xBE, 0xEF };
someStream.Position = 5;
someStream.Insert(dataToInsert);

既然你说你有一个BinaryWriter,因为你用它来创建一个空文件,为了能够使用这个类,你需要实际的Stream. 您可以改用静态File.Create()File.Open()方法,它们会返回FileStream对象。如果您以后决定仍然需要 a BinaryWriter,则可以使用将流提供给BinaryWriter构造函数。要从 writer 获取流,请使用BinaryWriter.BaseStream。请注意,关闭 aBinaryWriter也会关闭底层流,除非您使用NonClosingStreamWrapperMiscUtil中的

于 2012-05-21T18:00:03.127 回答
0

是的,正如您所说,您不能在文件中“插入”数据。您应该使用一些读写器组合,直到插入点并写入新数据。

我不知道 .pak 格式,但我认为这个例子可以说明你。

    var sourceStream = System.IO.File.OpenRead("myFile.pak");
    var destStream = System.IO.File.Create("modified.pak");

    using (var reader = new System.IO.BinaryReader(sourceStream))
    {
        using (var writer = new System.IO.BinaryWriter(destStream))
        {
            while (reader.BaseStream.CanRead)
            {
                char data = reader.ReadChar();
                if (data == '?')
                {
                    writer.Write("<Found!>");
                }
                else
                {
                    writer.Write(data);
                }
            }
        }
    }
于 2012-05-21T16:52:46.930 回答