private Dictionary<string, string> ReadStrings(DirectoryInfo translationDir)
        {
            String[] strings = Directory.GetFiles(Path.Combine(translationDir.FullName, "Strings"), "*.strings", SearchOption.AllDirectories);

            Dictionary<string, string> result = new Dictionary<string, string>(16000);
            Maximum = strings.Length;

            for (int i = 0; i < strings.Length; i++)
            {
                if (CancelEvent.IsSet())
                    return result;

                using (FileStream input = File.OpenRead(strings[i]))
                {
                    string name;
                    ZtrTextReader unpacker = new ZtrTextReader(input, StringsZtrFormatter.Instance);
                    ZtrFileEntry[] entries = unpacker.Read(out name);

                    foreach (ZtrFileEntry entry in entries)
                    {
                        string key = entry.Key;
                        string value = entry.Value;
                        result[key] = value;
                    }
                }
            }

            return result;
        }
        private DirectoryInfo ExtractZipToTempFolder(string zipPath)
        {
            using (ZipArchive zipFile = ZipFile.OpenRead(zipPath))
            {
                if (CancelEvent.IsSet())
                    return null;

                DirectoryInfo dir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()));
                zipFile.ExtractToDirectory(dir.FullName);

                return dir;
            }
        }
        private async Task Patch(DirectoryInfo translationDir)
        {
            Position = 0;

            if (CancelEvent.IsSet())
                return;

            FFXIIIGamePart gamePart = FFXIIIGamePart.Part1; // TODO
            InteractionService.SetGamePart(gamePart);

            String configurationFilePath = Path.Combine(translationDir.FullName, PatcherService.ConfigurationFileName);
            XmlElement config = XmlHelper.LoadDocument(configurationFilePath);
            LocalizatorEnvironmentInfo info = LocalizatorEnvironmentInfo.FromXml(config["LocalizatorEnvironment"]);
            info.Validate();

            LocalizatorEnvironmentInfo currentInfo = InteractionService.LocalizatorEnvironment.Provide();
            currentInfo.UpdateUrls(info);

            InteractionService.LocalizatorEnvironment.SetValue(currentInfo);
            InteractionService.WorkingLocation.SetValue(new WorkingLocationInfo(translationDir.FullName));

            if (currentInfo.IsIncompatible(typeof(App).Assembly.GetName().Version))
            {
                if (MessageBox.Show(this.GetParentElement<Window>(), "Ваша версия программы установки несовместима с текущим перевод. Обновить?", "Ошибка!", MessageBoxButton.YesNo, MessageBoxImage.Error) != MessageBoxResult.Yes)
                    return;

                string path = await DownloadLatestPatcher();
                DirectoryInfo updatePath = ExtractZipToTempFolder(path);
                string destination = AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\');

                string patcherPath = Path.Combine(updatePath.FullName, "Pulse.Patcher.exe");
                ProcessStartInfo procInfo = new ProcessStartInfo(patcherPath, $"/u \"{destination}\"")
                {
                    CreateNoWindow = true,
                    UseShellExecute = false,
                    WorkingDirectory = updatePath.FullName
                };
                
                Process.Start(procInfo);
                Environment.Exit(0);
            }

            if (CancelEvent.IsSet())
                return;

            GameLocationInfo gameLocation = PatcherService.GetGameLocation(gamePart);
            await Task.Run(() => Patch(translationDir, gameLocation));
        }
        protected override async Task DoAction()
        {
            Label = InstallationLabel;
            DirectoryInfo dir = null;
            try
            {
                dir = ExtractZipToTempFolder(PatcherService.ArchiveFileName);

                if (CancelEvent.IsSet())
                    return;

                await Patch(dir);
            }
            finally
            {
                Label = InstallLabel;
                dir?.Delete(true);
            }
        }
        private void Patch(DirectoryInfo translationDir, GameLocationInfo gameLocation)
        {
            if (CancelEvent.IsSet())
                return;

            Dictionary<string, string> dic = ReadStrings(translationDir);
            if (CancelEvent.IsSet())
                return;

            FileSystemInjectionSource source = new FileSystemInjectionSource();
            source.RegisterStrings(dic);

            UiArchiveTreeBuilder builder = new UiArchiveTreeBuilder(gameLocation);
            UiArchives archives = builder.Build();
            Position = 0;
            Maximum = archives.Count;
            foreach (UiContainerNode archive in archives)
            {
                Check(archive);
                OnProgress(1);
            }

            if (CancelEvent.IsSet())
                return;

            IUiLeafsAccessor[] accessors = archives.AccessToCheckedLeafs(new Wildcard("*"), null, false).ToArray();
            Position = 0;
            Maximum = accessors.Length;

            UiInjectionManager manager = new UiInjectionManager();
            foreach (IUiLeafsAccessor accessor in accessors)
            {
                accessor.Inject(source, manager);
                OnProgress(1);
            }

            manager.WriteListings();
        }