Beispiel #1
0
        private static int Main(string[] args)
        {
            if (args.Length < 1)
            {
                Console.WriteLine("Usage: LoTextExtractor <__data>");
                return(1);
            }

            var translationFinder = new TranslationFinder();

            using var stream = File.OpenRead(args[0]);
            using var helper = new AssetsToolsBundle(File.ReadAllBytes(@"Resources\classdata.tpk"));

            if (!helper.Load(stream))
            {
                Console.WriteLine("Failed to load bundle");
                return(1);
            }

            var localizationPatchExtractor = new LocalizationPatchExtractor();
            var serializedTextExtractor    = new SerializedTextExtractor();

            var entries = new List <ExtractedText>();

            var bundleAssets = helper.GetAssets();

            foreach (var asset in bundleAssets)
            {
                // We don't care about non-text assets, the Korean data files are handled with the Japanese
                if (!asset.Type.Equals("TextAsset", StringComparison.OrdinalIgnoreCase) ||
                    asset.Name.EndsWith("_ko.bin", StringComparison.OrdinalIgnoreCase))
                {
                    Debug.WriteLine($"Skipped asset '{asset.Name}' ({asset.Type})");
                    continue;
                }

                var count = entries.Count();

                if (asset.Name.Equals("LocalizationPatch", StringComparison.OrdinalIgnoreCase))
                {
                    Console.Write($"Processing {asset.Name} ");

                    entries.AddRange(localizationPatchExtractor.ExtractText(asset.GetScript()));
                }
                else if (asset.Name.EndsWith(".bin", StringComparison.OrdinalIgnoreCase))
                {
                    Console.Write($"Processing {asset.Name} ");

                    var koreanAsset = bundleAssets.FirstOrDefault(b => b.Name == asset.Name.Replace(".bin", "_ko.bin"));
                    var koreanBytes = koreanAsset != null?koreanAsset.GetScript() : Array.Empty <byte>();

                    entries.AddRange(serializedTextExtractor.ExtractText(asset.GetScript(), koreanBytes));
                }
                else
                {
                    Debug.WriteLine($"Skipped asset '{asset.Name}' ({asset.Type})");
                    continue;
                }

                var newCount = entries.Count();
                Console.WriteLine($"{newCount - count:N0} entries extracted");
            }

            Console.WriteLine($"Filtering and translating extracted text");

            var groups = new SortedDictionary <string, List <ExtractedText> >();

            foreach (var entry in entries)
            {
                if (string.IsNullOrEmpty(entry.Japanese) || Regex.Match(entry.Japanese, "^[\x00-\x7F]+$").Success)
                {
                    continue;
                }

                // ###-An error occurred - Added in multiple files during 7/29 patch
                if (Regex.IsMatch(entry.Japanese, "^[0-9]{10}-エラーが発生しました。$"))
                {
                    continue;
                }

                var foreignTexts = new[] { entry.Japanese, entry.Korean };
                entry.English = translationFinder.FindTranslation(foreignTexts);

                var comments = new List <string>();

                // We only run on BuffEffect for now. It can match some partials in Skill, ItemConsumable, and
                // ShopDesc, but not enough for it to be worth it yet.
                if (string.IsNullOrEmpty(entry.English) && entry.Source.Contains("BuffEffect", StringComparison.Ordinal))
                {
                    entry.English = translationFinder.FindPartialTranslation(foreignTexts);

                    if (!string.IsNullOrEmpty(entry.English))
                    {
                        // This comment prevents this translation from being loaded again when extracting text. This
                        // prevents the partial translation existing from preventing the entry from being processed
                        // again in case additional partials have been added since we were last run.
                        comments.Add(LanguageCatalog.PartialMatchComment);
                    }
                }

                comments.AddRange(
                    translationFinder.FindComments(entry.Korean, entry.Japanese).Where(c => !comments.Contains(c))
                    );

                entry.Comment = string.Join("; ", comments);

                var parts = Regex.Split(entry.Source, "[^a-zA-Z_]");
                if (parts.Length < 1)
                {
                    Debug.WriteLine($"Unknown source format {entry.Source}");
                    continue;
                }

                var group = parts[0];

                // At time of creating this app the text extracted text from _Client tables are the same as the table
                // without _Client. Putting them in the same group will ensure only one copy of them is stored
                // (whichever ends up first after sorting)
                if (group.EndsWith("_Client"))
                {
                    group = Regex.Replace(group, "_Client$", "");
                }

                // Split up DialogScript into 2 groups, one for events and one for normal stages.
                if (group == "DialogScript" && !Regex.IsMatch(entry.Source, "Ch[0-9]+Stage"))
                {
                    group = "DialogScriptEvent";
                }

                if (!groups.ContainsKey(group))
                {
                    groups[group] = new List <ExtractedText>();
                }

                groups[group].Add(entry);
            }

            Console.WriteLine($"Saving translation catalogs");

            var outputPath = new DirectoryInfo(Path.Combine(
                                                   Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullName),
                                                   "Extracted"
                                                   ));

            if (outputPath.Exists)
            {
                foreach (var file in Directory.GetFiles(outputPath.FullName, "*.po"))
                {
                    File.Delete(file);
                }
            }
            else
            {
                Directory.CreateDirectory(outputPath.FullName);
            }

            var warnings    = new List <string>();
            var versionDate = DateTime.Now;

            File.WriteAllText(Path.Join(outputPath.FullName, "VERSION"), string.Format("{0:yyyy.MM.dd.00}", versionDate));

            foreach (var group in groups)
            {
                var file = Path.Join(outputPath.FullName, $"{group.Key}.po");

                Console.WriteLine($" - Saving {file}");

                var catalogManager  = new CatalogManager();
                var catalogWarnings = catalogManager.SaveTo(file, versionDate, group.Value);

                warnings.AddRange(catalogWarnings);
            }

            var warningFile = Path.Join(outputPath.FullName, "WARNINGS");

            if (warnings.Any())
            {
                File.WriteAllLines(Path.Join(outputPath.FullName, "WARNINGS"), warnings);
            }
            else
            {
                if (File.Exists(warningFile))
                {
                    File.Delete(warningFile);
                }
            }

            return(0);
        }
Beispiel #2
0
        public bool Patch(Stream stream, IProgress <PatchProgress> progressReporter)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            if (progressReporter == null)
            {
                throw new ArgumentNullException(nameof(progressReporter));
            }

            using var bundle = new AssetsToolsBundle(Properties.Resources.classdata);

            if (!bundle.Load(stream))
            {
                throw new Exception("Failed to load bundle");
            }

            var assetReplacers = new List <AssetsReplacer>();

            foreach (var asset in bundle.GetAssets())
            {
                // We don't care about non-text assets or the Korean data files
                if (asset.Type != "TextAsset" || asset.Name.Contains("_ko.bin", StringComparison.Ordinal))
                {
                    continue;
                }

                progressReporter.Report(new PatchProgress()
                {
                    SetTargetAndReset = $"{asset.Name}"
                });

                var scriptBytes = asset.GetScript();
                if (scriptBytes == null)
                {
                    continue;
                }

                using var scriptStream = new MemoryStream();
                scriptStream.Write(scriptBytes);

                var patched = false;

                foreach (var target in targets)
                {
                    if (target.CanPatch(scriptStream))
                    {
                        if (target.Patch(scriptStream, progressReporter))
                        {
                            patched = true;
                        }
                    }
                }

                if (!patched)
                {
                    continue;
                }

                assetReplacers.Add(asset.BuildScriptReplacer(scriptStream.ToArray()));
            }

            if (assetReplacers.Count > 0)
            {
                bundle.SaveTo(assetReplacers, stream);
            }

            return(assetReplacers.Count > 0);
        }