public void FinalizeFile_updates_import_section_header()
        {
            var stream = new MemoryStream();
            var writer = new PortableExecutableWriter(stream, null);

            // Create 300 methods, each consuming 16 bytes.
            // This makes the .text section consume two memory pages, pushing .idata to 2*0x1000 relative to image base
            writer.MarkEntryPoint();
            for (var i = 0; i < 300; i++)
            {
                writer.StartNewMethod(i, "");
                writer.Emitter.EmitNop();
            }

            writer.AddImport(300, Encoding.ASCII.GetBytes("method"), Encoding.ASCII.GetBytes("library"));
            writer.AddImport(301, Encoding.ASCII.GetBytes("something"), Encoding.ASCII.GetBytes("other"));
            writer.AddImport(302, Encoding.ASCII.GetBytes("whatever"), Encoding.ASCII.GetBytes("library"));

            const int SectionSizeOnDisk = 3 * 20         // Import directory table, two entries plus one empty
                                          + 5 * 8        // Import lookup table, contains two null entries indicating end of list for some library
                                          + 5 * 8        // The same, but for import addresses
                                          + 10 + 12 + 12 // Hint/name table entries for the methods: 2 bytes + ASCII string + null + padding to even
                                          + 8 + 6;       // Null-terminated ASCII strings for the library names

            writer.FinalizeFile();

            // The file should be 1024 (header) + 5120 (.text) + 512 (.idata) bytes long
            Assert.That(stream.Length, Is.EqualTo(6656));

            // The RVA table entry for the import table should have 0x2000 + base of code (0x1000) for the offset
            Assert.That(ReadIntAt(0x110), Is.EqualTo(0x3000));
            Assert.That(ReadIntAt(0x114), Is.EqualTo(SectionSizeOnDisk));

            // The .idata section header starts at file offset 0x1B0.
            Assert.That(ReadIntAt(0x1B8), Is.EqualTo(0x1000));            // Virtual size
            Assert.That(ReadIntAt(0x1BC), Is.EqualTo(0x3000));            // Virtual address relative to image base
            Assert.That(ReadIntAt(0x1C0), Is.EqualTo(SectionSizeOnDisk)); // Raw size
            Assert.That(ReadIntAt(0x1C4), Is.EqualTo(1024 + 5120));       // File pointer to raw data

            int ReadIntAt(int offset)
            {
                return(BitConverter.ToInt32(stream.GetBuffer().AsSpan().Slice(offset, 4)));
            }
        }
        public void FinalizeFile_coalesces_import_libraries_with_different_casing()
        {
            var stream = new MemoryStream();
            var writer = new PortableExecutableWriter(stream, null);

            writer.MarkEntryPoint();
            writer.StartNewMethod(0, "");
            writer.Emitter.EmitNop();

            // Add two imports to the same method
            writer.AddImport(1, Encoding.ASCII.GetBytes("method"), Encoding.ASCII.GetBytes("library"));
            writer.AddImport(2, Encoding.ASCII.GetBytes("method2"), Encoding.ASCII.GetBytes("LIBRARY"));
            writer.FinalizeFile();

            // Check that the import section contains two entries, based on the size given in the RVA table
            const int SectionSizeOnDisk = 2 * 20    // Import directory table: one library plus null entry
                                          + 3 * 8   // Import lookup table: two entries plus one null entry
                                          + 3 * 8   // The same, but for import addresses
                                          + 10 + 10 // Hint/name table entries for the methods: 2 bytes + ASCII string + null + padding to even
                                          + 8;      // Null-terminated ASCII strings for the library name

            Assert.That(BitConverter.ToInt32(stream.GetBuffer().AsSpan().Slice(0x114, 4)), Is.EqualTo(SectionSizeOnDisk));
        }
        public void FinalizeFile_applies_import_fixup()
        {
            var stream = new MemoryStream();
            var writer = new PortableExecutableWriter(stream, null);

            // Add a call to an imported method
            writer.MarkEntryPoint();
            writer.StartNewMethod(0, "Main");
            writer.Emitter.EmitCallIndirectWithFixup(1, "Callee", out var fixup);
            writer.AddCallFixup(fixup);
            writer.AddImport(1, Encoding.ASCII.GetBytes("method"), Encoding.ASCII.GetBytes("library"));

            writer.FinalizeFile();

            // The import table starts at offset 0x1000 relative to base of code.
            // There are 40 bytes for the import directory table, followed by 16 bytes of import lookup table.
            // The import address table is immediately after the import lookup table, before the name table.
            // Thus the desired import address will be at 0x1038.
            // The instruction at 0x400 is "call rip+0x1032".
            var peSpan          = stream.GetBuffer().AsSpan();
            var callInstruction = peSpan.Slice(0x400, 6).ToArray();

            CollectionAssert.AreEqual(new byte[] { 0xFF, 0x15, 0x32, 0x10, 0x00, 0x00 }, callInstruction);
        }