[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/mkitto/MLAPI/master/MLAPI-Editor/MLAPIEditor.cs [Back]  [Original]

#define NOASYNC

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;

public class VersionUpgradePopup : EditorWindow
{
    private Vector2 scrollPos = Vector2.zero;
    private GithubRelease[] releases = new GithubRelease[0];
    private Action continuationAction = null;
    public void SetData(GithubRelease[] releases, Action continuationAction)
    {
        this.releases = releases;
        this.continuationAction = continuationAction;
    }

    public void OnGUI()
    {
        float padding = 20f;
        float extraPaddingBottom = 30f;
        GUILayout.BeginArea(new Rect(padding, padding, position.width - (padding * 2f), (position.height - (padding * 2f)) - extraPaddingBottom));
        scrollPos = GUILayout.BeginScrollView(scrollPos);
        GUIStyle warningStyle = new GUIStyle(EditorStyles.wordWrappedLabel);
        warningStyle.normal.textColor = Color.yellow;
        warningStyle.alignment = TextAnchor.MiddleCenter;
        warningStyle.fontStyle = FontStyle.Bold;

        GUIStyle errorStyle = new GUIStyle(EditorStyles.wordWrappedLabel);
        errorStyle.normal.textColor = Color.red;
        errorStyle.alignment = TextAnchor.MiddleCenter;
        errorStyle.fontStyle = FontStyle.Bold;


        EditorGUILayout.LabelField("The version you are upgrading to has a greater major version. This means that there are non backwards compatibile changes. \n" +
            "For more information about the MLAPI versioning, please visit SemVer.org", warningStyle);
        EditorGUILayout.LabelField("It's ALWAYS recommended to do a backup AND reading all the breaking changes when upgrading major versions. If your project doesn't compile " +
            "There is good chance serialized data will be PERMANENTLY LOST. Don't be stupid.", errorStyle);
        EditorGUILayout.LabelField("Here are the versions with breaking changes you are skipping.", EditorStyles.wordWrappedLabel);
        GUILayout.Space(5);
        for (int i = 0; i < releases.Length; i++)
        {
            string bodySummary = releases[i].body.Substring(0, releases[i].body.Length > 100 ? 100 : releases[i].body.Length);
            if (releases[i].body.Length > 100)
                bodySummary += "...";
            EditorGUILayout.LabelField(releases[i].tag_name + ": " + releases[i].name + (bodySummary.Trim().Length > 0 ? (" - " + bodySummary) : ""));
        }
        GUILayout.EndScrollView();
        GUILayout.EndArea();
        float buttonHeight = 30f;
        float buttonWidth = 120f;
        GUILayout.BeginArea(new Rect((position.width / 2f) - buttonWidth, position.height - buttonHeight, buttonWidth * 2f, buttonHeight));
        GUILayout.BeginHorizontal();
        if (GUILayout.Button("Cancel"))
        {
            if (continuationAction != null)
                continuationAction(false);
            this.Close();
        }
        if (GUILayout.Button("I Understand"))
        {
            if (continuationAction != null)
                continuationAction(true);
            this.Close();
        }
        GUILayout.EndHorizontal();
        GUILayout.EndArea();
    }

    public void OnLostFocus()
    {
        this.Focus();
    }
}

//SemVer.org
[Serializable]
public struct MLAPIVersion
{
    public byte MAJOR;
    public byte MINOR;
    public byte PATCH;

    public static bool operator >(MLAPIVersion v, MLAPIVersion v1)
    {
        return v.MAJOR > v1.MAJOR || (v.MAJOR == v1.MAJOR && (v.MINOR > v1.MINOR || (v.MINOR == v1.MINOR && (v.PATCH > v1.PATCH))));
    }

    public static bool operator =(MLAPIVersion v1, MLAPIVersion v2)
    {
        return v1 == v2 || v1 > v2;
    }

    public static bool operator  0 && !EditorApplication.isCompiling && !EditorApplication.isUpdating && !PendingPackageLock)
        {
            PendingPackageLock = true;

            string packageName = PendingPackages[PendingPackages.Count - 1];
            PendingPackages.RemoveAt(PendingPackages.Count - 1);

            AssetDatabase.importPackageCompleted += OnPackageImported;
            AssetDatabase.importPackageFailed += OnPackageImportFailed;

            AssetDatabase.ImportPackage(Application.dataPath + "/MLAPI/Lib/" + packageName, false);
        }

        GUILayout.BeginArea(new Rect(5, 0, position.width - 5, position.height - (40 + ((string.IsNullOrEmpty(statusMessage) ? 0 : 20) + (canRefetch ? 20 : 0)))));
        scrollPos = GUILayout.BeginScrollView(scrollPos);
        tab = GUILayout.Toolbar(tab, new string[] { "MLAPI", "Transports" });
        if (tab == 0)
        {
            if (releaseFoldoutStatus != null)
            {
                for (int i = 0; i < releaseFoldoutStatus.Length; i++)
                {
                    if (releases[i] == null)
                        continue;

                    releaseFoldoutStatus[i] = EditorGUILayout.Foldout(releaseFoldoutStatus[i], releases[i].tag_name + ((releases[i].tag_name == currentVersion) ? " - [Installed]" : ""));

                    if (releaseFoldoutStatus[i])
                    {
                        EditorGUI.indentLevel++;
                        EditorGUILayout.LabelField("Release notes", EditorStyles.boldLabel);
                        EditorGUILayout.LabelField(releases[i].body, EditorStyles.wordWrappedLabel);
                        EditorGUILayout.Space();
                        EditorGUILayout.Space();
                        if (releases[i].prerelease)
                        {
                            GUIStyle style = new GUIStyle(EditorStyles.boldLabel);
                            style.normal.textColor = new Color(1f, 0.5f, 0f);
                            EditorGUILayout.LabelField("Pre-release", style);
                        }
                        else
                        {
                            GUIStyle style = new GUIStyle(EditorStyles.boldLabel);
                            style.normal.textColor = new Color(0f, 1f, 0f);
                            EditorGUILayout.LabelField("Stable-release", style);
                        }

                        if (currentVersion == releases[i].tag_name)
                        {
                            GUIStyle boldStyle = new GUIStyle(EditorStyles.boldLabel);
                            boldStyle.normal.textColor = new Color(0.3f, 1f, 0.3f);
                            EditorGUILayout.LabelField("Installed", boldStyle);
                        }
                        EditorGUILayout.LabelField("Release date: " + DateTime.Parse(DateTime.Parse(releases[i].published_at).ToString()), EditorStyles.miniBoldLabel);

                        if (currentVersion != releases[i].tag_name && GUILayout.Button("Install"))
                            EditorCoroutine.Start(InstallRelease(i));

                        EditorGUI.indentLevel--;
                    }
                }
            }
        }
        else if (tab == 1)
        {
            if (transportArtifactIncompatble && !forceRenderAtrifacts)
            {
                EditorGUILayout.HelpBox("The transport installer version you are using is incompatbile with the latest manifest. Please upgrade your installer by downloading the latest MLAPI version..", MessageType.Warning);

                if (GUILayout.Button(new GUIContent("Ignore", "Ignore the version diff and try to render the content anyways. This might lead to render failures or other errors.")))
                {
                    forceRenderAtrifacts = true;
                }
            }
            else
            {
                MLAPIVersion currentMLAPIVersion = MLAPIVersion.Parse(currentVersion);

                if (transportArtifacts != null && transportArtifacts.artifacts != null && transportFoldoutStatus != null)
                {
                    for (int i = 0; i < transportArtifacts.artifacts.Length; i++)
                    {
                        if (transportArtifacts.artifacts[i] == null)
                            continue;

                        string transportDirectory = Path.Combine(Path.Combine(Path.Combine(Application.dataPath, "MLAPI"), "OfficialTransports"), transportArtifacts.artifacts[i].id);
                        bool isInstalled = Directory.Exists(transportDirectory) && Directory.GetFiles(transportDirectory).Length > 0;

                        transportFoldoutStatus[i] = EditorGUILayout.Foldout(transportFoldoutStatus[i], transportArtifacts.artifacts[i].name + ((isInstalled) ? " - [Installed]" : ""));

                        if (transportFoldoutStatus[i])
                        {
                            EditorGUI.indentLevel++;

                            EditorGUILayout.LabelField("Description", EditorStyles.boldLabel);
                            EditorGUILayout.LabelField(transportArtifacts.artifacts[i].description, EditorStyles.wordWrappedLabel);

                            EditorGUILayout.LabelField("Credits", EditorStyles.boldLabel);
                            EditorGUILayout.LabelField(transportArtifacts.artifacts[i].credits, EditorStyles.wordWrappedLabel);

                            EditorGUILayout.LabelField("Platform Compatibility", EditorStyles.boldLabel);
                            EditorGUILayout.LabelField(transportArtifacts.artifacts[i].platform_compatibility_description, EditorStyles.wordWrappedLabel);

                            EditorGUILayout.LabelField("Licence", EditorStyles.boldLabel);
                            EditorGUILayout.LabelField(transportArtifacts.artifacts[i].licence, EditorStyles.wordWrappedLabel);

                            if (currentMLAPIVersion.MAJOR != (byte)transportArtifacts.artifacts[i].mlapi_major_version)
                            {
                                EditorGUILayout.Space();
                                GUIStyle style = new GUIStyle(EditorStyles.wordWrappedLabel);
                                style.normal.textColor = new Color(1f, 0f, 0f);
                                EditorGUILayout.LabelField("The MLAPI version you have installed through the installer has a different major version from the transports major version. You have version v" + currentMLAPIVersion.ToString() + " while this transport targets version v" + transportArtifacts.artifacts[i].mlapi_major_version + ".x.x. This means there could potentially be compatibility issues, but its not guaranteed. If you have installed the MLAPI manually and have version v" + transportArtifacts.artifacts[i].mlapi_major_version + ".x.x you can ignore this message.", style);
                                EditorGUILayout.Space();
                            }

                            if (transportArtifacts.artifacts[i].required_build_targets == null)
                            {
                                transportArtifacts.artifacts[i].required_build_targets = new int[0];
                            }

                            for (int j = 0; j < transportArtifacts.artifacts[i].required_build_targets.Length; j++)
                            {
                                try
                                {
                                    if (!MLAPIEditorExtensions.IsPlatformSupported((BuildTarget)transportArtifacts.artifacts[i].required_build_targets[j]))
                                    {
                                        EditorGUILayout.HelpBox("This transport requires the " + ((BuildTarget)transportArtifacts.artifacts[i].required_build_targets[j]) + " build target to be installed!", MessageType.Warning);
                                        break;
                                    }
                                }
                                catch (Exception)
                                {

                                }
                            }


                            if (transportArtifacts.artifacts[i].experimental)
                            {
                                EditorGUILayout.Space();
                                GUIStyle style = new GUIStyle(EditorStyles.boldLabel);
                                style.normal.textColor = new Color(1f, 0.5f, 0f);
                                EditorGUILayout.LabelField("Experimental", style);
                            }
                            else
                            {
                                EditorGUILayout.Space();
                                GUIStyle style = new GUIStyle(EditorStyles.boldLabel);
                                style.normal.textColor = new Color(0f, 1f, 0f);
                                EditorGUILayout.LabelField("Stable", style);
                            }

                            if (isInstalled)
                            {
                                GUIStyle boldStyle = new GUIStyle(EditorStyles.boldLabel);
                                boldStyle.normal.textColor = new Color(0.3f, 1f, 0.3f);
                                EditorGUILayout.LabelField("Installed", boldStyle);

                                if (EditorApplication.isUpdating || EditorApplication.isCompiling)
                                {
                                    GUI.enabled = false;
                                }

                                // This is installed
                                if (GUILayout.Button("Reinstall Latest"))
                                {
                                    EditorCoroutine.Start(InstallTransport(i));
                                }

                                if (GUILayout.Button("Remove"))
                                {
                                    Directory.Delete(transportDirectory, true);

                                    string metaFileName = transportDirectory;

                                    if (metaFileName.EndsWith(Path.DirectorySeparatorChar.ToString()) ||
                                        metaFileName.EndsWith(Path.AltDirectorySeparatorChar.ToString()))
                                    {
                                        metaFileName = metaFileName.Substring(metaFileName.Length, metaFileName.Length - 1);
                                    }

                                    metaFileName += ".meta";

                                    if (File.Exists(metaFileName))
                                    {
                                        File.Delete(metaFileName);
                                    }

                                    try
                                    {
                                        AssetDatabase.Refresh();
                                    }
                                    catch (Exception e)
                                    {
                                        Debug.LogError(e.ToString());
                                        Debug.LogError(e.GetType().FullName);
                                    }
                                }

                                if (EditorApplication.isUpdating || EditorApplication.isCompiling)
                                {
                                    GUI.enabled = true;
                                }
                            }
                            else
                            {
                                if (EditorApplication.isUpdating || EditorApplication.isCompiling)
                                {
                                    GUI.enabled = false;
                                }

                                if (GUILayout.Button("Install Latest"))
                                {
                                    EditorCoroutine.Start(InstallTransport(i));
                                }

                                if (EditorApplication.isUpdating || EditorApplication.isCompiling)
                                {
                                    GUI.enabled = true;
                                }
                            }

                            EditorGUILayout.Space();
                            EditorGUI.indentLevel--;
                        }
                    }
                }
            }
        }
        GUILayout.EndScrollView();
        GUILayout.EndArea();

        GUILayout.BeginArea(new Rect(5, position.height - (40 + ((string.IsNullOrEmpty(statusMessage) ? 0 : 20) + (canRefetch ? 20 : 0))), position.width - 5, (60 + ((string.IsNullOrEmpty(statusMessage) ? 0 : 20) + (canRefetch ? 20 : 0)))));

        string lastUpdatedString = lastUpdated == 0 ? "Never" : new DateTime(lastUpdated).ToShortTimeString();
        GUILayout.Label("Last checked: " + lastUpdatedString, EditorStyles.centeredGreyMiniLabel);

        if (canRefetch && GUILayout.Button("Fetch All"))
            EditorCoroutine.Start(FetchAll());
        if (!string.IsNullOrEmpty(statusMessage))
            GUILayout.Label(statusMessage, EditorStyles.centeredGreyMiniLabel);
        if (GUILayout.Button("Reset defaults"))
        {
            releases = new GithubRelease[0];
            releaseFoldoutStatus = new bool[0];
            if (EditorPrefs.HasKey(Application.productName + "/MLAPI_version"))
                EditorPrefs.DeleteKey(Application.productName + "/MLAPI_version");
            if (EditorPrefs.HasKey(Application.productName + "/MLAPI_lastUpdated"))
                EditorPrefs.DeleteKey(Application.productName + "/MLAPI_lastUpdated");
        }

        GUILayout.EndArea();

        if ((releases.Length == 0 && (DateTime.Now - new DateTime(lastUpdated)).TotalSeconds > 600) || (DateTime.Now - new DateTime(lastUpdated)).TotalSeconds > 3600)
            EditorCoroutine.Start(FetchAll());

        Repaint();
    }

    private void OnPackageImported(string packageName)
    {
        AssetDatabase.importPackageCompleted -= OnPackageImported;
        PendingPackageLock = false;
    }

    private void OnPackageImportFailed(string packageName, string errorMessage)
    {
        AssetDatabase.importPackageFailed -= OnPackageImportFailed;
        PendingPackageLock = false;
    }

    private List GetMajorVersionsBetween(MLAPIVersion currentVersion, MLAPIVersion targetVersion)
    {
        List versionsBetween = new List();
        for (int i = 0; i < releases.Length; i++)
        {
            MLAPIVersion version = MLAPIVersion.Parse(releases[i].tag_name);
            if (version >= currentVersion && version  0)
                {
                    versionsBetween.Add(version);
                }
            }
        }
        return versionsBetween;
    }

    private GithubRelease GetReleaseOfVersion(MLAPIVersion version)
    {
        for (int i = 0; i < releases.Length; i++)
        {
            if (MLAPIVersion.Parse(releases[i].tag_name) == version)
                return releases[i];
        }
        return null;
    }

    private GithubRelease[] GetReleasesFromVersions(List versions)
    {
        GithubRelease[] releases = new GithubRelease[versions.Count];
        for (int i = 0; i < versions.Count; i++)
        {
            releases[i] = GetReleaseOfVersion(versions[i]);
        }
        return releases;
    }

    private IEnumerator InstallRelease(int index)
    {
        PendingPackages.Clear();
        PendingPackageLock = true;
        bool waiting = true;
        bool accepted = false;
        MLAPIVersion currentMLAPIVersion = MLAPIVersion.Parse(currentVersion);
        List versions = GetMajorVersionsBetween(currentMLAPIVersion, MLAPIVersion.Parse(releases[index].tag_name));
        if (versions.Count > 0 && currentMLAPIVersion.IsValid() && currentMLAPIVersion > new MLAPIVersion() { MAJOR = 2, MINOR = 0, PATCH = 0 })
        {
            VersionUpgradePopup window = ScriptableObject.CreateInstance();
            Rect mainWindowRect = MLAPIEditorExtensions.GetEditorMainWindowPos();
            float widthFill = 0.5f;
            float heightFill = 0.3f;
            window.position = new Rect(mainWindowRect.center.x - ((mainWindowRect.width * widthFill) / 2f), mainWindowRect.center.y - ((mainWindowRect.height * heightFill) / 2f), mainWindowRect.width * widthFill, mainWindowRect.height * heightFill);
            window.SetData(GetReleasesFromVersions(versions), (result) =>
            {
                //Called if the user proceeds with the upgrade.
                accepted = result;
                waiting = false;
            });
            window.ShowPopup();
        }
        else
        {
            accepted = true;
            waiting = false;
        }

        while (waiting)
            yield return null;

        if (accepted)
        {
            showProgressBar = true;
            progressTarget = releases[index].assets.Length;
            progress = 0;

            statusMessage = "Cleaning lib folder";
            yield return null;

            if (Directory.Exists(Application.dataPath + "/MLAPI/Lib/"))
                Directory.Delete(Application.dataPath + "/MLAPI/Lib/", true);

            Directory.CreateDirectory(Application.dataPath + "/MLAPI/Lib/");

            bool downloadFail = false;
            for (int i = 0; i < releases[index].assets.Length; i++)
            {
                using (UnityWebRequest www = UnityWebRequest.Get(releases[index].assets[i].browser_download_url))
                {
                    www.SendWebRequest();
                    while (!www.isDone && string.IsNullOrEmpty(www.error))
                    {
                        statusMessage = "Downloading " + releases[index].assets[i].name + "(" + (i + 1) + "/" + releases[index].assets.Length + ") " + www.downloadProgress + "%";
                        yield return null;
                    }

                    if (!string.IsNullOrEmpty(www.error))
                    {
                        //Some kind of error
                        downloadFail = true;
                        statusMessage = "Failed to download asset " + releases[index].assets[i].name + ". Error: " + www.error;
                        double startTime = EditorApplication.timeSinceStartup;
                        //Basically = yield return new WaitForSeconds(5);
                        while (EditorApplication.timeSinceStartup - startTime  x.FilenameInZip.Contains("net45"));

#if NET_4_6
                    bool supportsNet45 = true;
#else
                    bool supportsNet45 = false;
#endif

                    bool useNet35 = (!supportsNet45 || !net45Exists || !transportArtifacts.artifacts[index].preferNet45) && net35Exists;
                    bool useNet45 = net45Exists && supportsNet45 && !useNet35;

                    if (!useNet35 && !useNet45)
                    {
                        Debug.LogError(("Could not download transport \"" + transportArtifacts.artifacts[index].name + "\". There is no valid target for your platform."));
                    }

                    foreach (ZipStorer.ZipFileEntry entry in dir)
                    {
                        if (useNet35 && entry.FilenameInZip.Contains("net35") && entry.FilenameInZip.Length > entry.FilenameInZip.LastIndexOf("net35", StringComparison.Ordinal) + 6)
                        {
                            int lastIndexOfNet35 = entry.FilenameInZip.LastIndexOf("net35", StringComparison.Ordinal);
                            string fileSubPath = entry.FilenameInZip.Substring(lastIndexOfNet35 + 6, entry.FilenameInZip.Length - (lastIndexOfNet35 + 6));

                            zip.ExtractFile(entry, Path.Combine(transportDirectory, fileSubPath));
                        }
                        else if (useNet45 && entry.FilenameInZip.Contains("net45") && entry.FilenameInZip.Length > entry.FilenameInZip.LastIndexOf("net35", StringComparison.Ordinal) + 6)
                        {
                            int lastIndexOfNet45 = entry.FilenameInZip.LastIndexOf("net45", StringComparison.Ordinal);
                            string fileSubPath = entry.FilenameInZip.Substring(lastIndexOfNet45 + 6, entry.FilenameInZip.Length - (lastIndexOfNet45 + 6));

                            zip.ExtractFile(entry, Path.Combine(transportDirectory, fileSubPath));
                        }
                    }

                    zip.Close();
                }

                yield return null;
            }
        }

        yield return null;
        statusMessage = "";
        showProgressBar = false;

        try
        {
            AssetDatabase.Refresh();
        }
        catch (Exception e)
        {
            Debug.LogError(e.ToString());
            Debug.LogError(e.GetType().FullName);
        }
    }

    private IEnumerator FetchAll()
    {
        lastUpdated = DateTime.Now.Ticks;

        using (UnityWebRequest www = UnityWebRequest.Get(API_URL))
        {
            www.SendWebRequest();
            isFetching = true;
            while (!www.isDone && string.IsNullOrEmpty(www.error))
            {
                statusMessage = "Fetching releases " + www.downloadProgress + "%";
                yield return null;
            }

            if (!string.IsNullOrEmpty(www.error))
            {
                //Some kind of error
                statusMessage = "Failed to fetch releases. Error: " + www.error;
                double startTime = EditorApplication.timeSinceStartup;
                //Basically = yield return new WaitForSeconds(5);
                while (EditorApplication.timeSinceStartup - startTime  0)
                        builder.Append(json[i]);

                    if (depth == 0 && json[i] == ',')
                    {
                        releasesJson.Add(builder.ToString());
                        builder.Length = 0;
                    }

                    //Parse in smaller batches
                    if (i % (json.Length / 100) == 0)
                    {
                        statusMessage = "Splitting JSON " + (i / (float)json.Length) * 100f + "%";
                        yield return null;
                    }

                    statusMessage = "";
                }

                releases = new GithubRelease[releasesJson.Count];
                releaseFoldoutStatus = new bool[releasesJson.Count];

                for (int i = 0; i < releasesJson.Count; i++)
                {
                    releases[i] = JsonUtility.FromJson(releasesJson[i]);
                    if (i == 0)
                        releaseFoldoutStatus[i] = true;
                    else
                        releaseFoldoutStatus[i] = false;

                    if (i % (releasesJson.Count / 30f) == 0)
                    {
                        yield return null;
                        statusMessage = "Parsing JSON " + (i / (float)releasesJson.Count) * 100f + "%";
                    }
                }

                statusMessage = "";
                isParsing = false;
            }
        }


        using (UnityWebRequest www = UnityWebRequest.Get(TRANSPORT_ARTIFACT_PATH_URL))
        {
            www.SendWebRequest();
            isFetching = true;
            while (!www.isDone && string.IsNullOrEmpty(www.error))
            {
                statusMessage = "Fetching transports " + www.downloadProgress + "%";
                yield return null;
            }

            if (!string.IsNullOrEmpty(www.error))
            {
                //Some kind of error
                statusMessage = "Failed to fetch transports. Error: " + www.error;
                double startTime = EditorApplication.timeSinceStartup;
                //Basically = yield return new WaitForSeconds(5);
                while (EditorApplication.timeSinceStartup - startTime  t.Name == "ContainerWindow").FirstOrDefault();
        if (containerWinType == null)
            throw new MissingMemberException("Can't find internal type ContainerWindow. Maybe something has changed inside Unity");
        FieldInfo showModeField = containerWinType.GetField("m_ShowMode", BindingFlags.NonPublic | BindingFlags.Instance);
        PropertyInfo positionProperty = containerWinType.GetProperty("position", BindingFlags.Public | BindingFlags.Instance);
        if (showModeField == null || positionProperty == null)
            throw new MissingFieldException("Can't find internal fields 'm_ShowMode' or 'position'. Maybe something has changed inside Unity");
        UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(containerWinType);
        foreach (UnityEngine.Object win in windows)
        {
            int showmode = (int)showModeField.GetValue(win);
            if (showmode == 4) // main window
            {
                Rect pos = (Rect)positionProperty.GetValue(win, null);
                return pos;
            }
        }
        throw new NotSupportedException("Can't find internal main window. Maybe something has changed inside Unity");
    }

    public static bool IsPlatformSupported(BuildTarget target)
    {
        try
        {
            Type moduleManager = Type.GetType("UnityEditor.Modules.ModuleManager,UnityEditor.dll");
            MethodInfo isPlatformSupportLoadedMethod = moduleManager.GetMethod("IsPlatformSupportLoaded", BindingFlags.Static | BindingFlags.NonPublic);
            MethodInfo getTargetStringFromBuildTargetMethod = moduleManager.GetMethod("GetTargetStringFromBuildTarget", BindingFlags.Static | BindingFlags.NonPublic);

            return (bool)isPlatformSupportLoadedMethod.Invoke(null, new object[] { (string)getTargetStringFromBuildTargetMethod.Invoke(null, new object[] { target }) });
        }
        catch (Exception)
        {
            return true;
        }
    }
}

#region ZipStorer
/*
# The MIT License (MIT)

Copyright (c) 2016 Jaime Olivares

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#if !NOASYNC
    using System.Threading.Tasks;
#endif

namespace System.IO.Compression
{
    /// 
    /// Unique class for compression/decompression file. Represents a Zip file.
    /// 
    public class ZipStorer : IDisposable
    {
        /// 
        /// Compression method enumeration
        /// 
        public enum Compression : ushort
        {
            /// Uncompressed storage
            Store = 0,
            /// Deflate compression method
            Deflate = 8
        }

        /// 
        /// Represents an entry in Zip file directory
        /// 
        public class ZipFileEntry
        {
            /// Compression method
            public Compression Method;
            /// Full path and filename as stored in Zip
            public string FilenameInZip;
            /// Original file size
            public long FileSize;
            /// Compressed file size
            public long CompressedSize;
            /// Offset of header information inside Zip storage
            public long HeaderOffset;
            /// Offset of file inside Zip storage
            public long FileOffset;
            /// Size of header information
            public uint HeaderSize;
            /// 32-bit checksum of entire file
            public uint Crc32;
            /// Last modification time of file
            public DateTime ModifyTime;
            /// Creation time of file
            public DateTime CreationTime;
            /// Last access time of file
            public DateTime AccessTime;
            /// User comment for file
            public string Comment;
            /// True if UTF8 encoding for filename and comments, false if default (CP 437)
            public bool EncodeUTF8;

            /// Overriden method
            /// Filename in Zip
            public override string ToString()
            {
                return this.FilenameInZip;
            }
        }

        #region Public fields
        /// True if UTF8 encoding for filename and comments, false if default (CP 437)
        public bool EncodeUTF8 = false;
        /// Force deflate algotithm even if it inflates the stored file. Off by default.
        public bool ForceDeflating = false;
        #endregion

        #region Private fields
        // List of files to store
        private List Files = new List();
        // Filename of storage file
        private string FileName;
        // Stream object of storage file
        private Stream ZipFileStream;
        // General comment
        private string Comment = string.Empty;
        // Central dir image
        private byte[] CentralDirImage = null;
        // Existing files in zip
        private long ExistingFiles = 0;
        // File access for Open method
        private FileAccess Access;
        // leave the stream open after the ZipStorer object is disposed
        private bool leaveOpen;
        // Static CRC32 Table
        private static UInt32[] CrcTable = null;
        // Default filename encoder
        private static Encoding DefaultEncoding = Encoding.GetEncoding(437);
        #endregion

        #region Public methods
        // Static constructor. Just invoked once in order to create the CRC32 lookup table.
        static ZipStorer()
        {
            // Generate CRC32 table
            CrcTable = new UInt32[256];
            for (int i = 0; i < CrcTable.Length; i++)
            {
                UInt32 c = (UInt32)i;
                for (int j = 0; j < 8; j++)
                {
                    if ((c & 1) != 0)
                        c = 3988292384 ^ (c >> 1);
                    else
                        c >>= 1;
                }
                CrcTable[i] = c;
            }
        }

        /// 
        /// Method to create a new storage file
        /// 
        /// Full path of Zip file to create
        /// General comment for Zip file
        /// A valid ZipStorer object
        public static ZipStorer Create(string _filename, string _comment = null)
        {
            Stream stream = new FileStream(_filename, FileMode.Create, FileAccess.ReadWrite);

            ZipStorer zip = Create(stream, _comment);
            zip.Comment = _comment ?? string.Empty;
            zip.FileName = _filename;

            return zip;
        }

        /// 
        /// Method to create a new zip storage in a stream
        /// 
        /// 
        /// 
        /// true to leave the stream open after the ZipStorer object is disposed; otherwise, false (default).
        /// A valid ZipStorer object
        public static ZipStorer Create(Stream _stream, string _comment = null, bool _leaveOpen = false)
        {
            ZipStorer zip = new ZipStorer()
            {
                Comment = _comment ?? string.Empty,
                ZipFileStream = _stream,
                Access = FileAccess.Write,
                leaveOpen = _leaveOpen
            };

            return zip;
        }

        /// 
        /// Method to open an existing storage file
        /// 
        /// Full path of Zip file to open
        /// File access mode as used in FileStream constructor
        /// A valid ZipStorer object
        public static ZipStorer Open(string _filename, FileAccess _access)
        {
            Stream stream = (Stream)new FileStream(_filename, FileMode.Open, _access == FileAccess.Read ? FileAccess.Read : FileAccess.ReadWrite);

            ZipStorer zip = Open(stream, _access);
            zip.FileName = _filename;

            return zip;
        }

        /// 
        /// Method to open an existing storage from stream
        /// 
        /// Already opened stream with zip contents
        /// File access mode for stream operations
        /// true to leave the stream open after the ZipStorer object is disposed; otherwise, false (default).
        /// A valid ZipStorer object
        public static ZipStorer Open(Stream _stream, FileAccess _access, bool _leaveOpen = false)
        {
            if (!_stream.CanSeek && _access != FileAccess.Read)
                throw new InvalidOperationException("Stream cannot seek");

            ZipStorer zip = new ZipStorer()
            {
                ZipFileStream = _stream,
                Access = _access,
                leaveOpen = _leaveOpen
            };

            if (zip.ReadFileInfo())
                return zip;

            if (!_leaveOpen)
                zip.Close();

            throw new System.IO.InvalidDataException();
        }

        /// 
        /// Add full contents of a file into the Zip storage
        /// 
        /// Compression method
        /// Full path of file to add to Zip storage
        /// Filename and path as desired in Zip directory
        /// Comment for stored file
        public ZipFileEntry AddFile(Compression _method, string _pathname, string _filenameInZip, string _comment = null)
        {
            if (Access == FileAccess.Read)
                throw new InvalidOperationException("Writing is not alowed");

            using (var stream = new FileStream(_pathname, FileMode.Open, FileAccess.Read))
            {
                return this.AddStream(_method, _filenameInZip, stream, File.GetLastWriteTime(_pathname), _comment);
            }
        }

        /// 
        /// Add full contents of a stream into the Zip storage
        /// 
        /// Same parameters and return value as AddStreamAsync()
        public ZipFileEntry AddStream(Compression _method, string _filenameInZip, Stream _source, DateTime _modTime, string _comment = null)
        {
#if NOASYNC
            return this.AddStreamAsync(_method, _filenameInZip, _source, _modTime, _comment);
#else
            return Task.Run(() => this.AddStreamAsync(_method, _filenameInZip, _source, _modTime, _comment)).Result;
#endif
        }

        /// 
        /// Add full contents of a stream into the Zip storage
        /// 
        /// Compression method
        /// Filename and path as desired in Zip directory
        /// Stream object containing the data to store in Zip
        /// Modification time of the data to store
        /// Comment for stored file
#if NOASYNC
        private ZipFileEntry
#else
        public async Task
#endif
        AddStreamAsync(Compression _method, string _filenameInZip, Stream _source, DateTime _modTime, string _comment = null)
        {
            if (Access == FileAccess.Read)
                throw new InvalidOperationException("Writing is not alowed");

            // Prepare the fileinfo
            ZipFileEntry zfe = new ZipFileEntry()
            {
                Method = _method,
                EncodeUTF8 = this.EncodeUTF8,
                FilenameInZip = NormalizedFilename(_filenameInZip),
                Comment = _comment ?? string.Empty,
                Crc32 = 0,  // to be updated later
                HeaderOffset = (uint)this.ZipFileStream.Position,  // offset within file of the start of this local record
                CreationTime = _modTime,
                ModifyTime = _modTime,
                AccessTime = _modTime
            };

            // Write local header
            this.WriteLocalHeader(zfe);
            zfe.FileOffset = (uint)this.ZipFileStream.Position;

            // Write file to zip (store)
#if NOASYNC
            Store(zfe, _source);
#else
                await Store(zfe, _source);
#endif

            _source.Close();
            this.UpdateCrcAndSizes(zfe);
            Files.Add(zfe);

            return zfe;
        }

        /// 
        /// Add full contents of a directory into the Zip storage
        /// 
        /// Compression method
        /// Full path of directory to add to Zip storage
        /// Path name as desired in Zip directory
        /// Comment for stored directory
        public void AddDirectory(Compression _method, string _pathname, string _pathnameInZip, string _comment = null)
        {
            if (Access == FileAccess.Read)
                throw new InvalidOperationException("Writing is not allowed");

            string foldername;
            int pos = _pathname.LastIndexOf(Path.DirectorySeparatorChar);
            string separator = Path.DirectorySeparatorChar.ToString();

            if (pos >= 0)
                foldername = _pathname.Remove(0, pos + 1);
            else
                foldername = _pathname;

            if (!string.IsNullOrEmpty(_pathnameInZip))
                foldername = _pathnameInZip + foldername;

            if (!foldername.EndsWith(separator, StringComparison.CurrentCulture))
                foldername = foldername + separator;

            // this.AddStream(_method, foldername, null, File.GetLastWriteTime(_pathname), _comment);

            // Process the list of files found in the directory.
            string[] fileEntries = Directory.GetFiles(_pathname);

            foreach (string fileName in fileEntries)
                this.AddFile(_method, fileName, foldername + Path.GetFileName(fileName), "");

            // Recurse into subdirectories of this directory.
            string[] subdirectoryEntries = Directory.GetDirectories(_pathname);

            foreach (string subdirectory in subdirectoryEntries)
                this.AddDirectory(_method, subdirectory, foldername, "");
        }

        /// 
        /// Updates central directory (if pertinent) and close the Zip storage
        /// 
        /// This is a required step, unless automatic dispose is used
        public void Close()
        {
            if (this.Access != FileAccess.Read)
            {
                uint centralOffset = (uint)this.ZipFileStream.Position;
                uint centralSize = 0;

                if (this.CentralDirImage != null)
                    this.ZipFileStream.Write(CentralDirImage, 0, CentralDirImage.Length);

                for (int i = 0; i < Files.Count; i++)
                {
                    long pos = this.ZipFileStream.Position;
                    this.WriteCentralDirRecord(Files[i]);
                    centralSize += (uint)(this.ZipFileStream.Position - pos);
                }

                if (this.CentralDirImage != null)
                    this.WriteEndRecord(centralSize + (uint)CentralDirImage.Length, centralOffset);
                else
                    this.WriteEndRecord(centralSize, centralOffset);
            }

            if (this.ZipFileStream != null && !this.leaveOpen)
            {
                this.ZipFileStream.Flush();
                this.ZipFileStream.Dispose();
                this.ZipFileStream = null;
            }
        }

        /// 
        /// Read all the file records in the central directory
        /// 
        /// List of all entries in directory
        public List ReadCentralDir()
        {
            if (this.CentralDirImage == null)
                throw new InvalidOperationException("Central directory currently does not exist");

            List result = new List();

            for (int pointer = 0; pointer < this.CentralDirImage.Length;)
            {
                uint signature = BitConverter.ToUInt32(CentralDirImage, pointer);
                if (signature != 0x02014b50)
                    break;

                bool encodeUTF8 = (BitConverter.ToUInt16(CentralDirImage, pointer + 8) & 0x0800) != 0;
                ushort method = BitConverter.ToUInt16(CentralDirImage, pointer + 10);
                uint modifyTime = BitConverter.ToUInt32(CentralDirImage, pointer + 12);
                uint crc32 = BitConverter.ToUInt32(CentralDirImage, pointer + 16);
                long comprSize = BitConverter.ToUInt32(CentralDirImage, pointer + 20);
                long fileSize = BitConverter.ToUInt32(CentralDirImage, pointer + 24);
                ushort filenameSize = BitConverter.ToUInt16(CentralDirImage, pointer + 28);
                ushort extraSize = BitConverter.ToUInt16(CentralDirImage, pointer + 30);
                ushort commentSize = BitConverter.ToUInt16(CentralDirImage, pointer + 32);
                uint headerOffset = BitConverter.ToUInt32(CentralDirImage, pointer + 42);
                uint headerSize = (uint)(46 + filenameSize + extraSize + commentSize);
                DateTime modifyTimeDT = DosTimeToDateTime(modifyTime) ?? DateTime.Now;

                Encoding encoder = encodeUTF8 ? Encoding.UTF8 : DefaultEncoding;

                ZipFileEntry zfe = new ZipFileEntry()
                {
                    Method = (Compression)method,
                    FilenameInZip = encoder.GetString(CentralDirImage, pointer + 46, filenameSize),
                    FileOffset = GetFileOffset(headerOffset),
                    FileSize = fileSize,
                    CompressedSize = comprSize,
                    HeaderOffset = headerOffset,
                    HeaderSize = headerSize,
                    Crc32 = crc32,
                    ModifyTime = modifyTimeDT,
                    CreationTime = modifyTimeDT,
                    AccessTime = DateTime.Now,
                };

                if (commentSize > 0)
                    zfe.Comment = encoder.GetString(CentralDirImage, pointer + 46 + filenameSize + extraSize, commentSize);

                if (extraSize > 0)
                {
                    this.ReadExtraInfo(CentralDirImage, pointer + 46 + filenameSize, zfe);
                }

                result.Add(zfe);
                pointer += (46 + filenameSize + extraSize + commentSize);
            }

            return result;
        }

        /// 
        /// Copy the contents of a stored file into a physical file
        /// 
        /// Entry information of file to extract
        /// Name of file to store uncompressed data
        /// True if success, false if not.
        /// Unique compression methods are Store and Deflate
        public bool ExtractFile(ZipFileEntry _zfe, string _filename)
        {
            // Make sure the parent directory exist
            string path = Path.GetDirectoryName(_filename);

            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);

            // Check if it is a directory. If so, do nothing.
            if (Directory.Exists(_filename))
                return true;

            bool result;
            using (var output = new FileStream(_filename, FileMode.Create, FileAccess.Write))
            {
                result = this.ExtractFile(_zfe, output);
            }

            if (result)
            {
                File.SetCreationTime(_filename, _zfe.CreationTime);
                File.SetLastWriteTime(_filename, _zfe.ModifyTime);
                File.SetLastAccessTime(_filename, _zfe.AccessTime);
            }

            return result;
        }

        /// 
        /// Copy the contents of a stored file into an opened stream
        /// 
        /// Same parameters and return value as ExtractFileAsync
        public bool ExtractFile(ZipFileEntry _zfe, Stream _stream)
        {
#if NOASYNC
            return this.ExtractFileAsync(_zfe, _stream);
#else
            return Task.Run(() => ExtractFileAsync(_zfe, _stream)).Result;
#endif
        }

        /// 
        /// Copy the contents of a stored file into an opened stream
        /// 
        /// Entry information of file to extract
        /// Stream to store the uncompressed data
        /// True if success, false if not.
        /// Unique compression methods are Store and Deflate
#if NOASYNC
        private bool
#else
        public async Task
#endif
        ExtractFileAsync(ZipFileEntry _zfe, Stream _stream)
        {
            if (!_stream.CanWrite)
                throw new InvalidOperationException("Stream cannot be written");

            // check signature
            byte[] signature = new byte[4];
            this.ZipFileStream.Seek(_zfe.HeaderOffset, SeekOrigin.Begin);

#if NOASYNC
            this.ZipFileStream.Read(signature, 0, 4);
#else
                await this.ZipFileStream.ReadAsync(signature, 0, 4);
#endif

            if (BitConverter.ToUInt32(signature, 0) != 0x04034b50)
                return false;

            // Select input stream for inflating or just reading
            Stream inStream;

            if (_zfe.Method == Compression.Store)
                inStream = this.ZipFileStream;
            else if (_zfe.Method == Compression.Deflate)
                inStream = new DeflateStream(this.ZipFileStream, CompressionMode.Decompress, true);
            else
                return false;

            // Buffered copy
            byte[] buffer = new byte[65535];
            this.ZipFileStream.Seek(_zfe.FileOffset, SeekOrigin.Begin);
            long bytesPending = _zfe.FileSize;

            while (bytesPending > 0)
            {
#if NOASYNC
                int bytesRead = inStream.Read(buffer, 0, (int)Math.Min(bytesPending, buffer.Length));
                _stream.Write(buffer, 0, bytesRead);
#else
                    int bytesRead = await inStream.ReadAsync(buffer, 0, (int)Math.Min(bytesPending, buffer.Length));
                    await _stream.WriteAsync(buffer, 0, bytesRead);
#endif

                bytesPending -= (uint)bytesRead;
            }
            _stream.Flush();

            if (_zfe.Method == Compression.Deflate)
                inStream.Dispose();

            return true;
        }

        /// 
        /// Copy the contents of a stored file into a byte array
        /// 
        /// Entry information of file to extract
        /// Byte array with uncompressed data
        /// True if success, false if not.
        /// Unique compression methods are Store and Deflate
        public bool ExtractFile(ZipFileEntry _zfe, out byte[] _file)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                if (ExtractFile(_zfe, ms))
                {
                    _file = ms.ToArray();
                    return true;
                }
                else
                {
                    _file = null;
                    return false;
                }
            }
        }

        /// 
        /// Removes one of many files in storage. It creates a new Zip file.
        /// 
        /// Reference to the current Zip object
        /// List of Entries to remove from storage
        /// True if success, false if not
        /// This method only works for storage of type FileStream
        public static bool RemoveEntries(ref ZipStorer _zip, List _zfes)
        {
            if (!(_zip.ZipFileStream is FileStream))
                throw new InvalidOperationException("RemoveEntries is allowed just over streams of type FileStream");

            //Get full list of entries
            var fullList = _zip.ReadCentralDir();

            //In order to delete we need to create a copy of the zip file excluding the selected items
            var tempZipName = Path.GetTempFileName();
            var tempEntryName = Path.GetTempFileName();

            try
            {
                var tempZip = ZipStorer.Create(tempZipName, string.Empty);

                foreach (ZipFileEntry zfe in fullList)
                {
                    if (!_zfes.Contains(zfe))
                    {
                        if (_zip.ExtractFile(zfe, tempEntryName))
                        {
                            tempZip.AddFile(zfe.Method, tempEntryName, zfe.FilenameInZip, zfe.Comment);
                        }
                    }
                }

                _zip.Close();
                tempZip.Close();

                File.Delete(_zip.FileName);
                File.Move(tempZipName, _zip.FileName);

                _zip = ZipStorer.Open(_zip.FileName, _zip.Access);
            }
            catch
            {
                return false;
            }
            finally
            {
                if (File.Exists(tempZipName))
                    File.Delete(tempZipName);
                if (File.Exists(tempEntryName))
                    File.Delete(tempEntryName);
            }
            return true;
        }
        #endregion

        #region Private methods
        // Calculate the file offset by reading the corresponding local header
        private uint GetFileOffset(uint _headerOffset)
        {
            byte[] buffer = new byte[2];

            this.ZipFileStream.Seek(_headerOffset + 26, SeekOrigin.Begin);
            this.ZipFileStream.Read(buffer, 0, 2);
            ushort filenameSize = BitConverter.ToUInt16(buffer, 0);
            this.ZipFileStream.Read(buffer, 0, 2);
            ushort extraSize = BitConverter.ToUInt16(buffer, 0);

            return (uint)(30 + filenameSize + extraSize + _headerOffset);
        }

        /* Local file header:
            local file header signature     4 bytes  (0x04034b50)
            version needed to extract       2 bytes
            general purpose bit flag        2 bytes
            compression method              2 bytes
            last mod file time              2 bytes
            last mod file date              2 bytes
            crc-32                          4 bytes
            compressed size                 4 bytes
            uncompressed size               4 bytes
            filename length                 2 bytes
            extra field length              2 bytes
            filename (variable size)
            extra field (variable size)
        */
        private void WriteLocalHeader(ZipFileEntry _zfe)
        {
            long pos = this.ZipFileStream.Position;
            Encoding encoder = _zfe.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding;
            byte[] encodedFilename = encoder.GetBytes(_zfe.FilenameInZip);
            byte[] extraInfo = this.CreateExtraInfo(_zfe);

            this.ZipFileStream.Write(new byte[] { 80, 75, 3, 4, 20, 0 }, 0, 6); // No extra header
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)(_zfe.EncodeUTF8 ? 0x0800 : 0)), 0, 2); // filename and comment encoding
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2);  // zipping method
            this.ZipFileStream.Write(BitConverter.GetBytes(DateTimeToDosTime(_zfe.ModifyTime)), 0, 4); // zipping date and time
            this.ZipFileStream.Write(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 12); // unused CRC, un/compressed size, updated later
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedFilename.Length), 0, 2); // filename length
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)extraInfo.Length), 0, 2); // extra length

            this.ZipFileStream.Write(encodedFilename, 0, encodedFilename.Length);
            this.ZipFileStream.Write(extraInfo, 0, extraInfo.Length);
            _zfe.HeaderSize = (uint)(this.ZipFileStream.Position - pos);
        }

        /* Central directory's File header:
            central file header signature   4 bytes  (0x02014b50)
            version made by                 2 bytes
            version needed to extract       2 bytes
            general purpose bit flag        2 bytes
            compression method              2 bytes
            last mod file time              2 bytes
            last mod file date              2 bytes
            crc-32                          4 bytes
            compressed size                 4 bytes
            uncompressed size               4 bytes
            filename length                 2 bytes
            extra field length              2 bytes
            file comment length             2 bytes
            disk number start               2 bytes
            internal file attributes        2 bytes
            external file attributes        4 bytes
            relative offset of local header 4 bytes
            filename (variable size)
            extra field (variable size)
            file comment (variable size)
        */
        private void WriteCentralDirRecord(ZipFileEntry _zfe)
        {
            Encoding encoder = _zfe.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding;
            byte[] encodedFilename = encoder.GetBytes(_zfe.FilenameInZip);
            byte[] encodedComment = encoder.GetBytes(_zfe.Comment);
            byte[] extraInfo = this.CreateExtraInfo(_zfe);

            this.ZipFileStream.Write(new byte[] { 80, 75, 1, 2, 23, 0xB, 20, 0 }, 0, 8);
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)(_zfe.EncodeUTF8 ? 0x0800 : 0)), 0, 2); // filename and comment encoding
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2);  // zipping method
            this.ZipFileStream.Write(BitConverter.GetBytes(DateTimeToDosTime(_zfe.ModifyTime)), 0, 4);  // zipping date and time
            this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.Crc32), 0, 4); // file CRC
            this.ZipFileStream.Write(BitConverter.GetBytes(get32bitSize(_zfe.CompressedSize)), 0, 4); // compressed file size
            this.ZipFileStream.Write(BitConverter.GetBytes(get32bitSize(_zfe.FileSize)), 0, 4); // uncompressed file size
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedFilename.Length), 0, 2); // Filename in zip
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)extraInfo.Length), 0, 2); // extra length
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedComment.Length), 0, 2);

            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // disk=0
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // file type: binary
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // Internal file attributes
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0x8100), 0, 2); // External file attributes (normal/readable)
            this.ZipFileStream.Write(BitConverter.GetBytes(get32bitSize(_zfe.HeaderOffset)), 0, 4);  // Offset of header

            this.ZipFileStream.Write(encodedFilename, 0, encodedFilename.Length);
            this.ZipFileStream.Write(extraInfo, 0, extraInfo.Length);
            this.ZipFileStream.Write(encodedComment, 0, encodedComment.Length);
        }

        private uint get32bitSize(long size)
        {
            return size >= 0xFFFFFFFF ? 0xFFFFFFFF : (uint)size;
        }

        /*
        Zip64 end of central directory record
            zip64 end of central dir
            signature                       4 bytes  (0x06064b50)
            size of zip64 end of central
            directory record                8 bytes
            version made by                 2 bytes
            version needed to extract       2 bytes
            number of this disk             4 bytes
            number of the disk with the
            start of the central directory  4 bytes
            total number of entries in the
            central directory on this disk  8 bytes
            total number of entries in the
            central directory               8 bytes
            size of the central directory   8 bytes
            offset of start of central
            directory with respect to
            the starting disk number        8 bytes
            zip64 extensible data sector    (variable size)

        Zip64 end of central directory locator
            zip64 end of central dir locator
            signature                       4 bytes  (0x07064b50)
            number of the disk with the
            start of the zip64 end of
            central directory               4 bytes
            relative offset of the zip64
            end of central directory record 8 bytes
            total number of disks           4 bytes
        End of central dir record:
            end of central dir signature    4 bytes  (0x06054b50)
            number of this disk             2 bytes
            number of the disk with the
            start of the central directory  2 bytes
            total number of entries in
            the central dir on this disk    2 bytes
            total number of entries in
            the central dir                 2 bytes
            size of the central directory   4 bytes
            offset of start of central
            directory with respect to
            the starting disk number        4 bytes
            zipfile comment length          2 bytes
            zipfile comment (variable size)
        */
        private void WriteEndRecord(long _size, long _offset)
        {
            long dirOffset = ZipFileStream.Length;

            // Zip64 end of central directory record
            this.ZipFileStream.Position = dirOffset;
            this.ZipFileStream.Write(new byte[] { 80, 75, 6, 6 }, 0, 4);
            this.ZipFileStream.Write(BitConverter.GetBytes((Int64)44), 0, 8); // size of zip64 end of central directory
            this.ZipFileStream.Write(BitConverter.GetBytes((UInt16)45), 0, 2); // version made by
            this.ZipFileStream.Write(BitConverter.GetBytes((UInt16)45), 0, 2); // version needed to extract
            this.ZipFileStream.Write(BitConverter.GetBytes((UInt32)0), 0, 4); // current disk
            this.ZipFileStream.Write(BitConverter.GetBytes((UInt32)0), 0, 4); // start of central directory
            this.ZipFileStream.Write(BitConverter.GetBytes((Int64)Files.Count + ExistingFiles), 0, 8); // total number of entries in the central directory in disk
            this.ZipFileStream.Write(BitConverter.GetBytes((Int64)Files.Count + ExistingFiles), 0, 8); // total number of entries in the central directory
            this.ZipFileStream.Write(BitConverter.GetBytes(_size), 0, 8); // size of the central directory
            this.ZipFileStream.Write(BitConverter.GetBytes(_offset), 0, 8); // offset of start of central directory with respect to the starting disk number

            // Zip64 end of central directory locator
            this.ZipFileStream.Write(new byte[] { 80, 75, 6, 7 }, 0, 4);
            this.ZipFileStream.Write(BitConverter.GetBytes((UInt32)0), 0, 4); // number of the disk
            this.ZipFileStream.Write(BitConverter.GetBytes(dirOffset), 0, 8); // relative offset of the zip64 end of central directory record
            this.ZipFileStream.Write(BitConverter.GetBytes((UInt32)1), 0, 4); // total number of disks

            Encoding encoder = this.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding;
            byte[] encodedComment = encoder.GetBytes(this.Comment);

            this.ZipFileStream.Write(new byte[] { 80, 75, 5, 6, 0, 0, 0, 0 }, 0, 8);
            this.ZipFileStream.Write(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, 0, 12);
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedComment.Length), 0, 2);
            this.ZipFileStream.Write(encodedComment, 0, encodedComment.Length);
        }

        // Copies all the source file into the zip storage
#if NOASYNC
        private Compression
#else
        private async Task
#endif
        Store(ZipFileEntry _zfe, Stream _source)
        {
            byte[] buffer = new byte[16384];
            int bytesRead;
            uint totalRead = 0;
            Stream outStream;

            long posStart = this.ZipFileStream.Position;
            long sourceStart = _source.CanSeek ? _source.Position : 0;

            if (_zfe.Method == Compression.Store)
                outStream = this.ZipFileStream;
            else
                outStream = new DeflateStream(this.ZipFileStream, CompressionMode.Compress, true);

            _zfe.Crc32 = 0 ^ 0xffffffff;

            do
            {
#if NOASYNC
                bytesRead = _source.Read(buffer, 0, buffer.Length);
                if (bytesRead > 0)
                    outStream.Write(buffer, 0, bytesRead);
#else
                bytesRead = await _source.ReadAsync(buffer, 0, buffer.Length);
                if (bytesRead > 0)
                    await outStream.WriteAsync(buffer, 0, bytesRead);
#endif

                for (uint i = 0; i < bytesRead; i++)
                {
                    _zfe.Crc32 = ZipStorer.CrcTable[(_zfe.Crc32 ^ buffer[i]) & 0xFF] ^ (_zfe.Crc32 >> 8);
                }

                totalRead += (uint)bytesRead;
            } while (bytesRead > 0);

            outStream.Flush();

            if (_zfe.Method == Compression.Deflate)
                outStream.Dispose();

            _zfe.Crc32 ^= 0xFFFFFFFF;
            _zfe.FileSize = totalRead;
            _zfe.CompressedSize = (uint)(this.ZipFileStream.Position - posStart);

            // Verify for real compression
            if (_zfe.Method == Compression.Deflate && !this.ForceDeflating && _source.CanSeek && _zfe.CompressedSize > _zfe.FileSize)
            {
                // Start operation again with Store algorithm
                _zfe.Method = Compression.Store;
                this.ZipFileStream.Position = posStart;
                this.ZipFileStream.SetLength(posStart);
                _source.Position = sourceStart;

#if NOASYNC
                return this.Store(_zfe, _source);
#else
                    return await this.Store(_zfe, _source);
#endif
            }

            return _zfe.Method;
        }

        /* DOS Date and time:
            MS-DOS date. The date is a packed value with the following format. Bits Description
                0-4 Day of the month (131)
                5-8 Month (1 = January, 2 = February, and so on)
                9-15 Year offset from 1980 (add 1980 to get actual year)
            MS-DOS time. The time is a packed value with the following format. Bits Description
                0-4 Second divided by 2
                5-10 Minute (059)
                11-15 Hour (023 on a 24-hour clock)
        */
        private uint DateTimeToDosTime(DateTime _dt)
        {
            return (uint)(
                (_dt.Second / 2) | (_dt.Minute > 16) & 31;
            int hours = (int)(_dt >> 11) & 31;
            int minutes = (int)(_dt >> 5) & 63;
            int seconds = (int)(_dt & 31) * 2;

            if (month == 0 || day == 0 || year >= 2107)
                return DateTime.Now;

            return new DateTime(year, month, day, hours, minutes, seconds);
        }

        private byte[] CreateExtraInfo(ZipFileEntry _zfe)
        {
            byte[] buffer = new byte[36 + 36];
            BitConverter.GetBytes((ushort)0x0001).CopyTo(buffer, 0); // ZIP64 Information
            BitConverter.GetBytes((ushort)32).CopyTo(buffer, 2); // Length
            BitConverter.GetBytes((ushort)1).CopyTo(buffer, 8); // Tag 1
            BitConverter.GetBytes((ushort)24).CopyTo(buffer, 10); // Size 1
            BitConverter.GetBytes(_zfe.FileSize).CopyTo(buffer, 12); // MTime
            BitConverter.GetBytes(_zfe.CompressedSize).CopyTo(buffer, 20); // ATime
            BitConverter.GetBytes(_zfe.HeaderOffset).CopyTo(buffer, 28); // CTime

            BitConverter.GetBytes((ushort)0x000A).CopyTo(buffer, 36); // NTFS FileTime
            BitConverter.GetBytes((ushort)32).CopyTo(buffer, 38); // Length
            BitConverter.GetBytes((ushort)1).CopyTo(buffer, 44); // Tag 1
            BitConverter.GetBytes((ushort)24).CopyTo(buffer, 46); // Size 1
            BitConverter.GetBytes(_zfe.ModifyTime.ToFileTime()).CopyTo(buffer, 48); // MTime
            BitConverter.GetBytes(_zfe.AccessTime.ToFileTime()).CopyTo(buffer, 56); // ATime
            BitConverter.GetBytes(_zfe.CreationTime.ToFileTime()).CopyTo(buffer, 64); // CTime

            return buffer;
        }

        private void ReadExtraInfo(byte[] buffer, int offset, ZipFileEntry _zfe)
        {
            if (buffer.Length < 4)
                return;

            int pos = offset;
            uint tag, size;

            while (pos < buffer.Length - 4)
            {
                uint extraId = BitConverter.ToUInt16(buffer, pos);
                uint length = BitConverter.ToUInt16(buffer, pos + 2);

                if (extraId == 0x0001) // ZIP64 Information
                {
                    tag = BitConverter.ToUInt16(buffer, pos + 8);
                    size = BitConverter.ToUInt16(buffer, pos + 10);

                    if (tag == 1 && size >= 24)
                    {
                        if (_zfe.FileSize == 0xFFFFFFFF)
                            _zfe.FileSize = BitConverter.ToInt64(buffer, pos + 12);
                        if (_zfe.CompressedSize == 0xFFFFFFFF)
                            _zfe.CompressedSize = BitConverter.ToInt64(buffer, pos + 20);
                        if (_zfe.HeaderOffset == 0xFFFFFFFF)
                            _zfe.HeaderOffset = BitConverter.ToInt64(buffer, pos + 28);
                    }
                }

                if (extraId == 0x000A) // NTFS FileTime
                {
                    tag = BitConverter.ToUInt16(buffer, pos + 8);
                    size = BitConverter.ToUInt16(buffer, pos + 10);

                    if (tag == 1 && size == 24)
                    {
                        _zfe.ModifyTime = DateTime.FromFileTime(BitConverter.ToInt64(buffer, pos + 12));
                        _zfe.AccessTime = DateTime.FromFileTime(BitConverter.ToInt64(buffer, pos + 20));
                        _zfe.CreationTime = DateTime.FromFileTime(BitConverter.ToInt64(buffer, pos + 28));
                    }
                }

                pos += (int)length + 4;
            }
        }

        /* CRC32 algorithm
          The 'magic number' for the CRC is 0xdebb20e3.
          The proper CRC pre and post conditioning is used, meaning that the CRC register is
          pre-conditioned with all ones (a starting value of 0xffffffff) and the value is post-conditioned by
          taking the one's complement of the CRC residual.
          If bit 3 of the general purpose flag is set, this field is set to zero in the local header and the correct
          value is put in the data descriptor and in the central directory.
        */
        private void UpdateCrcAndSizes(ZipFileEntry _zfe)
        {
            long lastPos = this.ZipFileStream.Position;  // remember position

            this.ZipFileStream.Position = _zfe.HeaderOffset + 8;
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2);  // zipping method

            this.ZipFileStream.Position = _zfe.HeaderOffset + 14;
            this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.Crc32), 0, 4);  // Update CRC
            this.ZipFileStream.Write(BitConverter.GetBytes(get32bitSize(_zfe.CompressedSize)), 0, 4);  // Compressed size
            this.ZipFileStream.Write(BitConverter.GetBytes(get32bitSize(_zfe.FileSize)), 0, 4);  // Uncompressed size

            this.ZipFileStream.Position = lastPos;  // restore position
        }

        // Replaces backslashes with slashes to store in zip header
        private string NormalizedFilename(string _filename)
        {
            string filename = _filename.Replace('\\', '/');

            int pos = filename.IndexOf(':');
            if (pos >= 0)
                filename = filename.Remove(0, pos + 1);

            return filename.Trim('/');
        }

        // Reads the end-of-central-directory record
        private bool ReadFileInfo()
        {
            if (this.ZipFileStream.Length < 22)
                return false;

            try
            {
                this.ZipFileStream.Seek(-17, SeekOrigin.End);
                BinaryReader br = new BinaryReader(this.ZipFileStream);
                do
                {
                    this.ZipFileStream.Seek(-5, SeekOrigin.Current);
                    UInt32 sig = br.ReadUInt32();

                    if (sig == 0x06054b50) // It is central dir
                    {
                        long dirPosition = ZipFileStream.Position - 4;

                        this.ZipFileStream.Seek(6, SeekOrigin.Current);

                        long entries = br.ReadUInt16();
                        long centralSize = br.ReadInt32();
                        long centralDirOffset = br.ReadUInt32();
                        UInt16 commentSize = br.ReadUInt16();

                        var commentPosition = ZipFileStream.Position;

                        if (centralDirOffset == 0xffffffff) // It is a Zip64 file
                        {
                            this.ZipFileStream.Position = dirPosition - 20;

                            sig = br.ReadUInt32();

                            if (sig != 0x07064b50) // Not a Zip64 central dir locator
                                return false;

                            this.ZipFileStream.Seek(4, SeekOrigin.Current);

                            long dir64Position = br.ReadInt64();
                            this.ZipFileStream.Position = dir64Position;

                            sig = br.ReadUInt32();

                            if (sig != 0x06064b50) // Not a Zip64 central dir record
                                return false;

                            this.ZipFileStream.Seek(28, SeekOrigin.Current);
                            entries = br.ReadInt64();
                            centralSize = br.ReadInt64();
                            centralDirOffset = br.ReadInt64();
                        }

                        // check if comment field is the very last data in file
                        if (commentPosition + commentSize != this.ZipFileStream.Length)
                            return false;

                        // Copy entire central directory to a memory buffer
                        this.ExistingFiles = entries;
                        this.CentralDirImage = new byte[centralSize];
                        this.ZipFileStream.Seek(centralDirOffset, SeekOrigin.Begin);
                        this.ZipFileStream.Read(this.CentralDirImage, 0, (int)centralSize);

                        // Leave the pointer at the begining of central dir, to append new files
                        this.ZipFileStream.Seek(centralDirOffset, SeekOrigin.Begin);
                        return true;
                    }
                } while (this.ZipFileStream.Position > 0);
            }
            catch { }

            return false;
        }
        #endregion

        #region IDisposable Members
        /// 
        /// Closes the Zip file stream
        /// 
        public void Dispose()
        {
            this.Close();
        }
        #endregion
    }
}
#endregion

Web Proxy Viewer  |  New URL  |  Original Page