File: Program.cs
Web Access
Project: src\src\runtime\src\tools\ilasm\src\ilasm\ilasm.csproj (ilasm)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Parsing;
using System.Diagnostics;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text;

namespace ILAssembler;

internal sealed class Program
{
    private readonly IlasmRootCommand _command;

    public Program(IlasmRootCommand command)
    {
        _command = command;
    }

    public int Run()
    {
        Stopwatch? stopwatch = null;
        if (Get(_command.Clock))
        {
            stopwatch = Stopwatch.StartNew();
        }

        string[]? inputFiles = _command.Result.GetValue(_command.InputFilePaths);
        bool quiet = Get(_command.Quiet);

        if (!Get(_command.NoLogo) && !quiet)
        {
            Console.WriteLine(IlasmRootCommand.ProductName);
            Console.WriteLine();
        }

        if (inputFiles is null || inputFiles.Length == 0)
        {
            Console.Error.WriteLine("Error: No input file specified");
            return 1;
        }

        // Validate all input files exist
        foreach (string file in inputFiles)
        {
            if (!File.Exists(file))
            {
                Console.Error.WriteLine($"Error: Input file not found: {file}");
                return 1;
            }
        }

        // Determine output file (based on first input file)
        bool isDll = Get(_command.BuildDll);
        string? outputPath = Get(_command.OutputFilePath) ??
            $"{Path.GetFileNameWithoutExtension(inputFiles[0])}{(isDll ? ".dll" : ".exe")}";

        int exitCode = 0;
        try
        {
            // Report each file being assembled
            foreach (string file in inputFiles)
            {
                if (!quiet)
                {
                    Console.WriteLine($"Assembling '{file}' to {(isDll ? "DLL" : "EXE")} --> '{outputPath}'");
                }
            }

            // Concatenate all input files
            var contentBuilder = new StringBuilder();
            foreach (string file in inputFiles)
            {
                contentBuilder.AppendLine(File.ReadAllText(file));
            }
            string content = contentBuilder.ToString();

            // Use the first file as the primary document for source tracking
            var document = new SourceText(content, inputFiles[0]);

            // Build options
            bool errorTolerant = Get(_command.ErrorTolerant);
            var options = new Options
            {
                NoAutoInherit = Get(_command.NoAutoInherit),
                ErrorTolerant = errorTolerant,
            };

            // Apply PE header overrides from command line
            int subsystem = Get(_command.Subsystem);
            if (subsystem != 0)
            {
                options.Subsystem = (Subsystem)subsystem;
            }

            string? ssver = Get(_command.SubsystemVersion);
            if (!string.IsNullOrEmpty(ssver))
            {
                var parts = ssver.Split('.');
                if (parts.Length == 2 && ushort.TryParse(parts[0], out ushort ssvMajor) && ushort.TryParse(parts[1], out ushort ssvMinor))
                {
                    options.SubsystemVersion = (ssvMajor, ssvMinor);
                }
            }

            int alignment = Get(_command.Alignment);
            if (alignment != 0)
            {
                options.FileAlignment = alignment;
            }

            long imageBase = Get(_command.ImageBase);
            if (imageBase != 0)
            {
                options.ImageBase = imageBase;
            }

            int stackReserve = Get(_command.StackReserve);
            if (stackReserve != 0)
            {
                options.StackReserve = stackReserve;
            }

            int flags = Get(_command.Flags);
            if (flags != 0)
            {
                options.CorFlags = (CorFlags)flags;
            }

            // Target machine
            if (Get(_command.TargetX64) || Get(_command.Pe64))
            {
                options.Machine = Machine.Amd64;
            }
            else if (Get(_command.TargetArm))
            {
                options.Machine = Machine.Arm;
            }
            else if (Get(_command.TargetArm64))
            {
                options.Machine = Machine.Arm64;
            }

            // PE characteristics
            options.AppContainer = Get(_command.AppContainer);
            options.HighEntropyVA = Get(_command.HighEntropyVa);
            options.StripReloc = Get(_command.StripReloc);
            options.Prefer32Bit = Get(_command.Prefer32Bit);

            // Deterministic and metadata version
            options.Deterministic = Get(_command.Deterministic);
            options.MetadataVersion = Get(_command.MetadataVersion);

            // Debug options
            options.Debug = Get(_command.Debug);
            options.Pdb = Get(_command.Pdb);
            options.DebugMode = Get(_command.DebugMode);

            // Assembly options
            options.AssemblyName = Get(_command.AssemblyName);
            options.KeyFile = Get(_command.KeyFile);
            options.Optimize = Get(_command.Optimize);
            options.Fold = Get(_command.Fold);

            // Set up include path for #include directive resolution
            string? includePath = Get(_command.IncludePath);
            string baseDir = Path.GetDirectoryName(Path.GetFullPath(inputFiles[0])) ?? ".";

            SourceText LoadIncludedDocument(string path)
            {
                // Try the path as-is first
                if (File.Exists(path))
                {
                    return new SourceText(File.ReadAllText(path), path);
                }

                // Try relative to the base directory
                string fullPath = Path.Combine(baseDir, path);
                if (File.Exists(fullPath))
                {
                    return new SourceText(File.ReadAllText(fullPath), fullPath);
                }

                // Try the include path if specified
                if (!string.IsNullOrEmpty(includePath))
                {
                    fullPath = Path.Combine(includePath, path);
                    if (File.Exists(fullPath))
                    {
                        return new SourceText(File.ReadAllText(fullPath), fullPath);
                    }
                }

                throw new FileNotFoundException($"Include file not found: {path}");
            }

            byte[] LoadResource(string path)
            {
                // Try the path as-is first
                if (File.Exists(path))
                {
                    return File.ReadAllBytes(path);
                }

                // Try relative to the base directory
                string fullPath = Path.Combine(baseDir, path);
                if (File.Exists(fullPath))
                {
                    return File.ReadAllBytes(fullPath);
                }

                throw new FileNotFoundException($"Resource file not found: {path}");
            }

            // Compile
            var compiler = new DocumentCompiler();
            var (diagnostics, peBuilder) = compiler.Compile(
                document,
                LoadIncludedDocument,
                LoadResource,
                options);

            // Report diagnostics
            bool hasErrors = false;
            foreach (var diagnostic in diagnostics)
            {
                if (diagnostic.Severity == DiagnosticSeverity.Error)
                {
                    hasErrors = true;
                    Console.Error.WriteLine($"Error: {diagnostic.Location}: {diagnostic.Message}");
                }
                else if (diagnostic.Severity == DiagnosticSeverity.Warning && !errorTolerant)
                {
                    Console.WriteLine($"Warning: {diagnostic.Location}: {diagnostic.Message}");
                }
            }

            // In error-tolerant mode, continue even with errors
            if (peBuilder is null)
            {
                Console.Error.WriteLine("***** FAILURE *****");
                return 1;
            }

            if (hasErrors && !errorTolerant)
            {
                Console.Error.WriteLine("***** FAILURE *****");
                return 1;
            }

            // Write output
            using var outputStream = File.Create(outputPath);
            var blobBuilder = new BlobBuilder();
            peBuilder.Serialize(blobBuilder);
            blobBuilder.WriteContentTo(outputStream);

            if (hasErrors)
            {
                if (!quiet)
                {
                    Console.WriteLine("Output file contains errors");
                }
            }
            else if (!quiet)
            {
                Console.WriteLine("Operation completed successfully");
            }
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"Error: {ex.Message}");
            exitCode = 1;
        }

        if (stopwatch is not null)
        {
            stopwatch.Stop();
            Console.WriteLine($"Total time: {stopwatch.ElapsedMilliseconds} ms");
        }

        return exitCode;
    }

    private T Get<T>(Argument<T> argument) => _command.Result.GetValue(argument)!;

    private T Get<T>(Option<T> option) => _command.Result.GetValue(option)!;

    private static int Main(string[] args) =>
        new IlasmRootCommand()
            .Parse(args)
            .Invoke();
}