示例#1
0
        /**
         * Creates an SFX of the given source, using the current HXE executable, to the given target.
         */
        public static void Compile(Configuration configuration)
        {
            var source = configuration.Source;
            var target = configuration.Target;
            var filter = configuration.Filter;

            target.Create();

            /**
             * We will create an SFX for all of the files residing in the given source directory. Our goal is to contain all
             * of the data in a single file, and subsequently reproduce the directory tree & file contents.
             *
             * For a reasonable balance between space savings and process time, we will DEFLATE each file before including it
             * in the output SFX binary.
             *
             * The structure of the output SFX is essentially:
             *
             * [pe executable] + [deflate data] + [sfx object] + [sfx object length]
             * |-------------|   |------------|   |----------|   |-----------------|
             *               |                |              |                     |
             *               |                |              |                     +- used for seeking the start of this sfx
             *               |                |              |                        object, by reading backwards from EOF
             *               |                |              |
             *               |                |              +----------------------- specifies the deflate offsets & output
             *               |                |                                       file name + path on the filesystem
             *               |                |
             *               |                +-------------------------------------- deflate data for each discovered file in
             *               |                                                        the source directory
             *               |
             *               +------------------------------------------------------- contents of this executable; essentially
             *                                                                        we append the aforementioned data to the
             *                                                                        end of this executable
             */

            var sfx   = new SFX();
            var files = source.GetFiles(filter, SearchOption.AllDirectories);

            var sourceExe = configuration.Executable;
            var targetExe = new FileInfo(Combine(target.FullName, sourceExe.Name));

            Info($"Source: {source.FullName}");
            Info($"Target: {target.FullName}");

            Info($"Source EXE: {sourceExe.FullName}");
            Info($"Target SFX: {targetExe.FullName}");

            if (targetExe.Exists)
            {
                targetExe.Delete();
            }

            var sourceSize = files.Sum(x => x.Length);
            var sfxSize    = 0L;

            /**
             * We create a copy of this executable for subsequent appending of the DEFLATE & SFX data. We refresh the FileInfo
             * object to ensure that we have the real length of the executable on the fs.
             */

            sourceExe.CopyTo(targetExe.FullName);
            targetExe.Refresh();

            Info($"Copied {targetExe.Length} bytes to: {targetExe.Length}");

            /**
             * For each discovered file in the given source directory, we will:
             *
             * 1. Append a DEFLATE representation of it to the copied HXE executable;
             * 2. Create an SFX entry specifying:
             *    a) the file name & path on the fs
             *    b) the file length on the fs
             *    c) offset of the DEFLATE data in SFX
             */
            using (var oStream = System.IO.File.Open(targetExe.FullName, Append))
            {
                for (var i = 0; i < files.Length; i++)
                {
                    var file = files[i];
                    Info($"Packaging file: {file.Name}");

                    /**
                     * We append the DEFLATE data to the SFX binary. After the procedure is done, we will refresh the FileInfo
                     * for the SFX to retrieve its new length. This length will be used to determine length of the DEFALTE and thus
                     * its offset in the SFX binary.
                     */

                    var  length = file.Length;
                    long deflateLength;

                    {
                        using (var iStream = file.OpenRead())
                            using (var dStream = new DeflateStream(oStream, Compress, true))
                            {
                                iStream.CopyTo(dStream);
                                dStream.Close();
                                oStream.Flush(true);
                            }

                        WriteLine(NewLine + new string('-', 80));

                        var oldLength = targetExe.Length;
                        targetExe.Refresh();
                        var newLength = targetExe.Length;

                        Info($"SFX increased from {oldLength} to {newLength} bytes.");

                        deflateLength = newLength - oldLength;

                        sfxSize += deflateLength;

                        if (deflateLength < length)
                        {
                            Info($"DEFLATE length is thus {(decimal) deflateLength / length * 100:##.##}% of {length} bytes.");
                        }
                        else
                        {
                            Warn($"DEFLATE length is higher by {deflateLength - length} bytes than the raw file length.");
                        }
                    }

                    /**
                     * We will add an entry for the current file and its DEFLATE representation to teach the SFX how to recreate
                     * the file down the line.
                     *
                     * The entries are designed to recreate the structure of the source directory, in a given arbitrary target
                     * directory. As such, we will avoid absoltue paths for the files and instead infer paths relative to the source
                     * directory.
                     *
                     * Each DEFLATE entry will be appended at the end of the SFX binary. To determine where each file's DEFLATE
                     * representation starts, we determine the offset by extracting the DEFLATE length, from the SFX binary
                     * length.
                     */

                    {
                        var name   = file.Name;
                        var offset = targetExe.Length - deflateLength;
                        var path   = file.DirectoryName != null && file.DirectoryName.Equals(source.FullName)
                                                        ? string.Empty
                                                        : file.DirectoryName?.Substring(source.FullName.Length + 1);

                        Info($"Acknowledging new entry: {targetExe.Name} <= {path}\\{name}");
                        Info($"DEFLATE starts at offset 0x{offset:x8} in the SFX binary.");

                        sfx.Entries.Add(new Entry
                        {
                            Name   = name,
                            Path   = path,
                            Length = length,
                            Offset = offset
                        });

                        targetExe.Refresh();
                    }

                    WriteLine(NewLine + new string('-', 80));
                    Info($"Finished packaging file: {file.Name}");
                    Info($"{files.Length - (i + 1)} files are currently remaining.");
                    WriteLine(NewLine + new string('=', 80));
                }

                /**
                 * Once we have created & appended the DEFLATE data for each file, and also populated the SFX object, we will
                 * serialise it to a byte array which in turn gets appended to the SFX binary as well. This makes the SFX
                 * completely self-contained and portable.
                 *
                 * Because an SFX object's array representation is a variable length, we will append the said length to the binary
                 * as well. This will allow HXE to determine both the start and end of the SFX data when deserialising it, by
                 * seeking backwards from the EOF. By seeking as many bytes backwards as specified in the length value, the start
                 * of the SFX data can be determined.
                 */

                {
                    var sfxData   = sfx.Serialise();
                    var sfxOffset = targetExe.Length;

                    Info($"Appending SFX length ({sfxData.Length}) at offset 0x{sfxOffset:x8}.");

                    using (var binWriter = new BinaryWriter(oStream))
                    {
                        binWriter.Write(sfxData);
                        binWriter.Write(sfxOffset);

                        oStream.Flush(true);
                        binWriter.Close();
                    }
                }
            }

            {
                WriteLine(NewLine + new string('*', 80));
                Info($"Finished packaging {targetExe.Name} with {files.Length} files.");

                var percentage = (double)sfxSize / sourceSize * 100;

                Info($"Source directory size: {sourceSize} bytes");
                Info($"SFX DEFLATE data size: {sfxSize} bytes");
                Info($"Data has been compressed down to {percentage:##.##}%");
            }
        }
示例#2
0
文件: Program.cs 项目: yumiris/HCE
        /// <summary>
        ///   Console API to the HXE kernel, installer and compiler.
        /// </summary>
        /// <param name="args">
        ///   --config            Opens configuration GUI
        ///   --load              Initiates HCE/SPV3
        ///   --install=VALUE     Installs HCE/SPV3   to destination
        ///   --compile=VALUE     Compiles HCE/SPV3   to destination
        ///   --console           Loads HCE           with console mode
        ///   --devmode           Loads HCE           with developer mode
        ///   --screenshot        Loads HCE           with screenshot ability
        ///   --window            Loads HCE           in window mode
        ///   --nogamma           Loads HCE           without gamma overriding
        ///   --adapter=VALUE     Loads HCE           on monitor X
        ///   --path=VALUE        Loads HCE           with custom profile path
        ///   --vidmode=VALUE     Loads HCE           with video mode
        /// </param>
        private static void InvokeProgram(string[] args)
        {
            Directory.CreateDirectory(Paths.Directory);

            var help       = false;        /* Displays commands list              */
            var config     = false;        /* Opens configuration GUI             */
            var positions  = false;        /* Opens positions GUI                 */
            var install    = string.Empty; /* Installs HCE/SPV3 to destination    */
            var compile    = string.Empty; /* Compiles HCE/SPV3 to destination    */
            var update     = string.Empty; /* Updates directory using manifest    */
            var registry   = string.Empty; /* Write to Windows Registry           */
            var infer      = false;        /* Infer the running Halo executable   */
            var console    = false;        /* Loads HCE with console mode         */
            var devmode    = false;        /* Loads HCE with developer mode       */
            var screenshot = false;        /* Loads HCE with screenshot ability   */
            var window     = false;        /* Loads HCE in window mode            */
            var nogamma    = false;        /* Loads HCE without gamma overriding  */
            var adapter    = string.Empty; /* Loads HCE on monitor X              */
            var path       = string.Empty; /* Loads HCE with custom profile path  */
            var exec       = string.Empty; /* Loads HCE with custom init file     */
            var vidmode    = string.Empty; /* Loads HCE with custom res. and Hz   */
            var refresh    = string.Empty; /* Loads HCE with custom refresh rate  */

            var options = new OptionSet()
                          .Add("help", "Displays commands list", s => help                  = s != null)                 /* hxe command   */
                          .Add("config", "Opens configuration GUI", s => config             = s != null)                 /* hxe command   */
                          .Add("positions", "Opens positions GUI", s => positions           = s != null)                 /* hxe command   */
                          .Add("install=", "Installs HCE/SPV3 to destination", s => install = s)                         /* hxe parameter */
                          .Add("compile=", "Compiles HCE/SPV3 to destination", s => compile = s)                         /* hxe parameter */
                          .Add("update=", "Updates directory using manifest", s => update   = s)                         /* hxe parameter */
                          .Add("registry=", "Create Registry keys for Retail, Custom, Trial, or HEK", s => registry = s) /* hxe parameter */
                          .Add("infer", "Infer the running Halo executable", s => infer           = s != null)           /* hxe parameter */
                          .Add("console", "Loads HCE with console mode", s => console             = s != null)           /* hce parameter */
                          .Add("devmode", "Loads HCE with developer mode", s => devmode           = s != null)           /* hce parameter */
                          .Add("screenshot", "Loads HCE with screenshot ability", s => screenshot = s != null)           /* hce parameter */
                          .Add("window", "Loads HCE in window mode", s => window              = s != null)               /* hce parameter */
                          .Add("nogamma", "Loads HCE without gamma overriding", s => nogamma  = s != null)               /* hce parameter */
                          .Add("adapter=", "Loads HCE on monitor X", s => adapter             = s)                       /* hce parameter */
                          .Add("path=", "Loads HCE with custom profile path", s => path       = s)                       /* hce parameter */
                          .Add("exec=", "Loads HCE with custom init file", s => exec          = s)                       /* hce parameter */
                          .Add("vidmode=", "Loads HCE with custom res. and Hz", s => vidmode  = s)                       /* hce parameter */
                          .Add("refresh=", "Loads HCE with custom refresh rate", s => refresh = s);                      /* hce parameter */

            var input = options.Parse(args);

            foreach (var i in input)
            {
                Info("Discovered CLI command: " + i);
            }

            var hce = new Executable();

            if (help)
            {
                options.WriteOptionDescriptions(Out);
                Exit(0);
            }

            if (config)
            {
                new Application().Run(new Settings());
                Exit(0);
            }

            if (positions)
            {
                new Application().Run(new Positions());
                Exit(0);
            }

            if (infer)
            {
                var descriptions = new Dictionary <Process.Type, string>
                {
                    { Process.Type.Unknown, "N/A" },
                    { Process.Type.Retail, "Halo: Combat Evolved" },
                    { Process.Type.HCE, "Halo: Custom Edition" },
                    { Process.Type.Steam, "Halo: MCC - CEA (Steam)" },
                    { Process.Type.Store, "Halo: MCC - CEA (Store)" },
                };

                Info($"Inferred the following Halo process: {descriptions[Process.Infer()]}");
                Info("Press any key to exit.");
                ReadLine();
                Exit(0);
            }

            if (!string.IsNullOrWhiteSpace(install))
            {
                Run(() =>
                {
                    SFX.Extract(new SFX.Configuration
                    {
                        Target = new DirectoryInfo(install)
                    });
                });
            }

            if (!string.IsNullOrWhiteSpace(compile))
            {
                Run(() =>
                {
                    SFX.Compile(new SFX.Configuration
                    {
                        Source = new DirectoryInfo(CurrentDirectory),
                        Target = new DirectoryInfo(compile)
                    });
                });
            }

            if (!string.IsNullOrWhiteSpace(update))
            {
                Run(() =>
                {
                    var updateModule = new Update();
                    updateModule.Import(update);
                    updateModule.Commit();
                });
            }

            if (!string.IsNullOrWhiteSpace(registry))
            {
            }



            /**
             * Implicit verification for legal HCE installations.
             */

            try
            {
                hce = Executable.Detect();
            }
            catch (Exception e)
            {
                var msg = " -- Legal copy of HCE needs to be installed for loading!\n Error:  " + e.ToString() + "\n";
                var log = (File)Paths.Exception;
                log.AppendAllText(msg);
                Error(msg);
            }

            if (console)
            {
                hce.Debug.Console = true;
            }

            if (devmode)
            {
                hce.Debug.Developer = true;
            }

            if (screenshot)
            {
                hce.Debug.Screenshot = true;
            }

            if (window)
            {
                hce.Video.Window = true;
            }

            if (nogamma)
            {
                hce.Video.NoGamma = true;
            }

            if (!string.IsNullOrWhiteSpace(adapter))
            {
                hce.Video.Adapter = byte.Parse(adapter);
            }

            if (!string.IsNullOrWhiteSpace(path))
            {
                hce.Profile.Path = path;
            }

            if (!string.IsNullOrWhiteSpace(exec))
            {
                hce.Debug.Initiation = exec;
            }

            if (!string.IsNullOrWhiteSpace(vidmode))
            {
                var a = vidmode.Split(',');

                if (a.Length < 2)
                {
                    return;
                }

                hce.Video.DisplayMode = true;
                hce.Video.Width       = ushort.Parse(a[0]);
                hce.Video.Height      = ushort.Parse(a[1]);

                if (a.Length > 2) /* optional refresh rate */
                {
                    hce.Video.Refresh = ushort.Parse(a[2]);
                }
            }

            if (!string.IsNullOrWhiteSpace(refresh))
            {
                hce.Video.Refresh = ushort.Parse(refresh);
            }

            /**
             * Implicitly invoke the HXE kernel with the HCE loading procedure.
             */

            Run(() => { Kernel.Invoke(hce); });

            /**
             * This method is used for running code asynchronously and catching exceptions at the highest level.
             */

            void Run(Action action)
            {
                try
                {
                    Task.Run(action).GetAwaiter().GetResult();
                    WithCode(Code.Success);
                }
                catch (Exception e)
                {
                    var msg = " -- EXEC.START HALTED\n Error:  " + e.ToString() + "\n";
                    var log = (File)Paths.Exception;
                    log.AppendAllText(msg);
                    Error(msg);
                    System.Console.Error.WriteLine("\n\n" + e.StackTrace);
                    WithCode(Code.Exception);
                }
            }
        }
示例#3
0
        /**
         * Extracts the SFX contents of the current HXE executable to the given target.
         */
        public static void Extract(Configuration configuration)
        {
            var target = configuration.Target;

            target.Create();

            /**
             * We will assume that the current HXE executable contains SFX data to be extracted.
             */

            var exe = configuration.Executable;
            var sfx = new SFX();

            Info($"Determining SFX structure in {exe.Name}");

            /**
             * We first hydrate the SFX object with information from the SFX binary. The start of the SFX data is inferred
             * from the length specified at the EOF. We also need to keep in mind the length of the variable that specifies
             * the length of the SFX data. Since it's a long, the size would be 8.
             *
             * With the above confusion in mind:
             * - Starting offset of the SFX data = (hxe length - sizeof(long) - sfx data length)
             * - Ending offset of the SFX data   = (hxe length - sizeof(long))
             */

            using (var binReader = new BinaryReader(exe.OpenRead()))
            {
                binReader.BaseStream.Seek(exe.Length - sizeof(long), Begin);
                binReader.BaseStream.Seek(binReader.ReadInt64(), Begin);
                sfx.Deserialise
                (
                    binReader.ReadBytes
                    (
                        (int)exe.Length - (int)binReader.BaseStream.Position - sizeof(long)
                    )
                );
            }

            /**
             * For each entry specified in the SFX object, we seek its DEFLATE data and extract it to the fs at the given path
             * and filename. Because .NET doesn't support copying a specified amount of bytes from a stream to another, we are
             * implementing our own method for copying the data using buffers.
             */

            for (var i = 0; i < sfx.Entries.Count; i++)
            {
                var entry    = sfx.Entries[i];
                var original = new FileInfo(Combine(target.FullName, entry.Path, entry.Name));

                original.Directory?.Create();

                Info($"Found {entry.Length} bytes at 0x{entry.Offset:x8}: {entry.Name}");
                Info($"Extracting its DEFLATE data to: {original.FullName}");

                using (var iStream = exe.OpenRead())
                    using (var oStream = original.Create())
                        using (var dStream = new DeflateStream(iStream, Decompress))
                        {
                            dStream.BaseStream.Seek(entry.Offset, Begin);
                            var bytes  = entry.Length;
                            var buffer = new byte[0x8000];
                            int read;
                            while (bytes > 0 && (read = dStream.Read(buffer, 0, Math.Min(buffer.Length, (int)bytes))) > 0)
                            {
                                oStream.Write(buffer, 0, read);
                                bytes -= read;
                            }
                        }

                WriteLine(NewLine + new string('-', 80));
                Info($"Finished extracting file: {entry.Name}");
                Info($"{sfx.Entries.Count - (i + 1)} files are currently remaining.");

                WriteLine(NewLine + new string('=', 80));
            }
        }