src/programFrames/TinySharp.cs

//code from https://blog.washi.dev/posts/tinysharp/
using System;
using System.IO;
using System.Text;
using AsmResolver;
using AsmResolver.DotNet;
using AsmResolver.DotNet.Builder.Metadata.Blob;
using AsmResolver.DotNet.Builder.Metadata.Strings;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Signatures;
using AsmResolver.IO;
using AsmResolver.PE;
using AsmResolver.PE.DotNet.Builder;
using AsmResolver.PE.Code;
using AsmResolver.PE.DotNet;
using AsmResolver.PE.DotNet.Cil;
using AsmResolver.PE.DotNet.Metadata;
using AsmResolver.PE.DotNet.Metadata.Tables;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
using AsmResolver.PE.Win32Resources;
using AsmResolver.PE.Win32Resources.Icon;
using AsmResolver.PE.File;
using AsmResolver.PE.File.Headers;
 
namespace TinySharp {
    public class Program {
        public static PEFile Compile(
            string outputValue, string architecture = "x64"
        ) {
            string baseFunction = "_putws";
            var module = new ModuleDefinition("Dummy");
 
            // Segment containing our string to print.
            var segment = new DataSegment(Encoding.Unicode.GetBytes(outputValue+'\0'));
 
            var PEKind = OptionalHeaderMagic.PE64;
            var ArchType = MachineType.Amd64;
            if (architecture != "x64") {
                PEKind = OptionalHeaderMagic.PE32;
                ArchType = MachineType.I386;
            }
 
            // Initialize a new PE image and set up some default values.
            var image = new PEImage {
                ImageBase = 0x00000000004e0000,
                PEKind = PEKind,
                MachineType = ArchType
            };
 
            // Ensure PE is loaded at the provided image base.
            image.DllCharacteristics &= ~DllCharacteristics.DynamicBase;
 
            // Create new metadata streams.
            var tablesStream = new TablesStream();
            var blobStreamBuffer = new BlobStreamBuffer();
            var stringsStreamBuffer = new StringsStreamBuffer();
 
            // Add empty module row.
            tablesStream.GetTable<ModuleDefinitionRow>().Add(new ModuleDefinitionRow());
 
            // Add container type def for our main function (<Module>).
            tablesStream.GetTable<TypeDefinitionRow>().Add(new TypeDefinitionRow(
                0, 0, 0, 0, 1, 1
            ));
 
            var methodTable = tablesStream.GetTable<MethodDefinitionRow>();
 
            // Add puts method.
            methodTable.Add(new MethodDefinitionRow(
                SegmentReference.Null,
                MethodImplAttributes.PreserveSig,
                MethodAttributes.Static | MethodAttributes.PInvokeImpl,
                stringsStreamBuffer.GetStringIndex(baseFunction),
                blobStreamBuffer.GetBlobIndex(new DummyProvider(),
                    MethodSignature.CreateStatic(module.CorLibTypeFactory.Void, module.CorLibTypeFactory.IntPtr), ThrowErrorListener.Instance),
                1
            ));
 
            // Add main method calling puts.
            using(var codeStream = new MemoryStream()) {
                var assembler = new CilAssembler(new BinaryStreamWriter(codeStream), new CilOperandBuilder(new OriginalMetadataTokenProvider(null), ThrowErrorListener.Instance));
                assembler.WriteInstruction(new CilInstruction(CilOpCodes.Ldc_I4, 0x00000000)); // To be replaced with the address to the string to print (applied with a patch below).
                assembler.WriteInstruction(new CilInstruction(CilOpCodes.Call, new MetadataToken(TableIndex.Method, 1)));
                assembler.WriteInstruction(new CilInstruction(CilOpCodes.Ret));
 
                var body = new CilRawTinyMethodBody(codeStream.ToArray())
                    .AsPatchedSegment()
                    .Patch(2, AddressFixupType.Absolute32BitAddress, new Symbol(segment.ToReference()));
 
                methodTable.Add(new MethodDefinitionRow(
                    body.ToReference(),
                    0,
                    MethodAttributes.Static,
                    0,
                    blobStreamBuffer.GetBlobIndex(new DummyProvider(),
                        MethodSignature.CreateStatic(module.CorLibTypeFactory.Void), ThrowErrorListener.Instance),
                    1
                ));
            }
 
            // Add urctbase module reference
            var baseLibrary = "ucrtbase";
            tablesStream.GetTable<ModuleReferenceRow>().Add(new ModuleReferenceRow(stringsStreamBuffer.GetStringIndex(baseLibrary)));
 
            // Add P/Invoke metadata to the puts method.
            tablesStream.GetTable<ImplementationMapRow>().Add(new ImplementationMapRow(
                ImplementationMapAttributes.CallConvCdecl,
                tablesStream.GetIndexEncoder(CodedIndex.MemberForwarded).EncodeToken(new MetadataToken(TableIndex.Method, 1)),
                stringsStreamBuffer.GetStringIndex(baseFunction),
                1
            ));
 
            // Define assembly manifest.
            tablesStream.GetTable<AssemblyDefinitionRow>().Add(new AssemblyDefinitionRow(
                0,
                1, 0, 0, 0,
                0,
                0,
                stringsStreamBuffer.GetStringIndex(baseFunction), // The CLR does not allow for assemblies with a null name. Reuse the name "puts" to safe space.
                0
            ));
 
            // Add all .NET metadata to the PE image.
            image.DotNetDirectory = new DotNetDirectory {
                EntryPoint = new MetadataToken(TableIndex.Method, 2),
                Metadata = new Metadata {
                    VersionString = "v4.0.", // Needs the "." at the end. (original: v4.0.30319)
                    Streams = {
                        tablesStream,
                        blobStreamBuffer.CreateStream(),
                        stringsStreamBuffer.CreateStream()
                    }
                }
            };
            if (architecture == "anycpu")
                image.DotNetDirectory.Flags &= ~DotNetDirectoryFlags.Bit32Required;
 
            // Assemble PE file.
            var file = new MyBuilder().CreateFile(image);
 
            // Put string to print in the padding data.
            file.ExtraSectionData = segment;
 
            return file;
        }
    }
 
    internal class DummyProvider : ITypeCodedIndexProvider {
        public uint GetTypeDefOrRefIndex(ITypeDefOrRef type) { throw new NotImplementedException(); }
    }
 
    public class MyBuilder : ManagedPEFileBuilder {
        protected override PESection CreateTextSection(IPEImage image, ManagedPEBuilderContext context) {
            // We override this method to only have it emit the bare minimum .text section.
 
            var methodTable = context.DotNetSegment.DotNetDirectory.Metadata.GetStream<TablesStream>().GetTable<MethodDefinitionRow>();
 
            for (uint rid = 1; rid <= methodTable.Count; rid++) {
                var methodRow = methodTable.GetByRid(rid);
 
                var bodySegment = methodRow.Body.IsBounded
                    ? methodRow.Body.GetSegment()
                    : null;
 
                if (bodySegment != null) {
                    context.DotNetSegment.MethodBodyTable.AddNativeBody(bodySegment, 4);
                    methodRow.Body = bodySegment.ToReference();
                }
            }
 
            return new PESection(".text",
                SectionFlags.ContentCode | SectionFlags.MemoryRead,
                context.DotNetSegment);
        }
    }
}