我正在使用幻数创建简单的自解压存档来标记内容的开头。 现在它是一个文本文件:
MAGICNUMBER ....文本文件的内容
接下来,将textfile复制到可执行文件的末尾:
复制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?我无法轻易修改pdfs以包含预定义的幻数。
答案 0 :(得分:4)
试一试。一些C#Stream 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; }
}
答案 1 :(得分:3)
您可以将压缩文件作为资源添加到项目本身:
项目>特性
将此资源的属性设置为Binary
。
然后,您可以使用
检索资源byte[] resource = Properties.Resources.NameOfYourResource;
答案 2 :(得分: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
答案 3 :(得分:2)
向后搜索而不是向前搜索(假设您的文件不会包含所说的幻数)。
或者附加你的(文本)文件,然后是它的长度(或原始exe的长度),所以你只需要读取最后一个DWORD /几个字节来查看文件的长度 - 那么就不需要幻数
更强大,将文件存储为可执行文件中的附加数据部分。没有外部工具,这更加繁琐,因为它需要知道用于NT可执行文件的q.v.的PE文件格式。 http://msdn.microsoft.com/en-us/library/ms809762.aspx