简体   繁体   中英

Use C# exe to modify resources of a different C# exe

Solved! See below.

I have 2 C# applications. Application a is supposed to modify the internal resources of application b. Application b is supposed to do something with its (modified) resources when executed.

How can I accomplish that?

Here is what I tried:

public static void addFileToResources(string dest, string src)
{
    Assembly a_dest = Assembly.LoadFile(dest);

    using (Stream s_dest = a_dest.GetManifestResourceStream("Elevator.Properties.Resources.resources"))
    {
        using (ResourceWriter rw = new ResourceWriter(s_dest))
        {
            byte[] b_src = File.ReadAllBytes(src);
            rw.AddResource("target", b_src);
        }
    }
}

I get a System.ArgumentException with The stream is readonly. on System.Resources.ResourceWriter..ctor(Stream stream)

EDIT
Since that does not seem to be possible with .net resources: Is there any other way?
I want to produce a single file (namely the exe of application b ) which is executable and can work with the data (stored in the exe) that is given prior to execution from application a . Preferably without having to actually compile b in order to give it the data.

Assumptions in order to make it a little easier:

  • a is always executed before b
  • a is only executed once
  • both applications are written by me

Edit - Sollution
Since it is not possible to accomplish that via resources I used the following workaround:
Apparently you can append ANYTHING to an exe file and it will still be executable, so here is what I came up with:

public class Packer : IDisposable
{
    // chosen quite arbitrarily; can be anything you'd like but should be reasonably unique
    private static byte[] MAGIC_NUMBER = { 0x44, 0x61, 0x6c, 0x65, 0x6b, 0x4c, 0x75, 0x63 };

    private Stream inStream;

    public Packer(string filename, bool openReadonly = false)
    {
        // The FileAccess.Read is necessary when I whant to read from the file that is being executed.
        // Hint: To get the path for the executing file I used:
        // System.Reflection.Assembly.GetExecutingAssembly().Location
        inStream = File.Open(filename, FileMode.Open, openReadonly ? FileAccess.Read : FileAccess.ReadWrite, openReadonly ? FileShare.Read : FileShare.None);
    }

    public byte[] ReadData(int index)
    {
        byte[] mn_buf = new byte[MAGIC_NUMBER.Length];
        byte[] len_buf = new byte[sizeof(Int32)];
        int data_len = 0;
        inStream.Seek(0, SeekOrigin.End);
        for (int i = 0; i <= index; ++i)
        {
            // Read the last few bytes
            inStream.Seek(-MAGIC_NUMBER.Length, SeekOrigin.Current);
            inStream.Read(mn_buf, 0, MAGIC_NUMBER.Length);
            inStream.Seek(-MAGIC_NUMBER.Length, SeekOrigin.Current);
            for (int j = 0; j < MAGIC_NUMBER.Length; ++j)
            {   // Check if the last bytes are equals to my MAGIC_NUMBER
                if (mn_buf[j] != MAGIC_NUMBER[j])
                {
                    throw new IndexOutOfRangeException("Not enough data.");
                }
            }
            inStream.Seek(-sizeof(Int32), SeekOrigin.Current);
            inStream.Read(len_buf, 0, sizeof(Int32));
            inStream.Seek(-sizeof(Int32), SeekOrigin.Current);
            // Read the length of the data
            data_len = BitConverter.ToInt32(len_buf, 0);
            inStream.Seek(-data_len, SeekOrigin.Current);
        }
        byte[] data = new byte[data_len];
        // Read the actual data and return it
        inStream.Read(data, 0, data_len);
        return data;
    }

    public void AddData(byte[] data)
    {
        // append it
        inStream.Seek(0, SeekOrigin.End);
        inStream.Write(data, 0, data.
        inStream.Write(BitConverter.GetBytes(data.Length), 0, sizeof(Int32));
        inStream.Write(MAGIC_NUMBER, 0, MAGIC_NUMBER.Length);
    }

    public void Dispose()
    {
        inStream.Dispose();
    }
}

If you want to use this snippet go ahead but note that if you add data to a file the indexes are in reverse order when retrieving:
Let's say you write data set A first and then data set B, if you read the data later B will have the index 0 and A the index 1.

Based on your assumptions you can update/add resources of your executable using Mono.Cecil library

Here are three essential methods for Resource Manipulation using Mono.Cecil:

    public static void ReplaceResource(string path, string resourceName, byte[] resource)
    {
        var definition =
            AssemblyDefinition.ReadAssembly(path);

        for (var i = 0; i < definition.MainModule.Resources.Count; i++)
            if (definition.MainModule.Resources[i].Name == resourceName)
            {
                definition.MainModule.Resources.RemoveAt(i);
                break;
            }

        var er = new EmbeddedResource(resourceName, ManifestResourceAttributes.Public, resource);
        definition.MainModule.Resources.Add(er);
        definition.Write(path);
    }

    public static void AddResource(string path, string resourceName, byte[] resource)
    {
        var definition =
            AssemblyDefinition.ReadAssembly(path);

        var er = new EmbeddedResource(resourceName, ManifestResourceAttributes.Public, resource);
        definition.MainModule.Resources.Add(er);
        definition.Write(path);
    }

    public static MemoryStream GetResource(string path, string resourceName)
    {
        var definition =
            AssemblyDefinition.ReadAssembly(path);

        foreach (var resource in definition.MainModule.Resources)
            if (resource.Name == resourceName)
            {
                var embeddedResource =(EmbeddedResource) resource;
                var stream = embeddedResource.GetResourceStream();

                var bytes = new byte[stream.Length];
                stream.Read(bytes, 0, bytes.Length);

                var memStream = new MemoryStream();
                memStream.Write(bytes,0,bytes.Length);
                memStream.Position = 0;
                return memStream;
            }

        return null;
    }

You can use GetResource method to retrieve current resource stream (Writable),

using ResourceWriter , ResourceReader or ResourceEditor classes you can read/write or modify current resource or create a new resource, then simply put it back in the executable by calling ReplaceResource or add it as a new one by calling AddResource

Here is an example of replacing an image in resource (by creating a new resource from scratch):

            var ms = new MemoryStream();
            var writer = new ResourceWriter(ms);
            writer.AddResource("good_luck",new Bitmap("good_luck.png"));
            writer.Generate();   
            ReplaceResource(@"my executale.exe", "ResourceTest.Properties.Resources.resources",ms.ToArray());

you can obtain Cecil through PM> Install-Package Mono.Cecil nuget.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM