New mix content analysis from ini is nearing completion. Still need to set up the game detection and filename priority system for mix files by analysing the game with the most hash matches.
This commit is contained in:
parent
f506f0cb25
commit
2d178b5e55
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -20,6 +20,8 @@
|
||||
# The line breaks in app.config are preserved for the final .exe.config, so
|
||||
# force this to crlf so builds aren't affected by the system doing the build.
|
||||
*.config text eol=crlf
|
||||
# If included, are always meant as DOS/Windows style data.
|
||||
*.ini text eol=crlf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.png binary
|
||||
|
@ -622,7 +622,7 @@
|
||||
<Compile Include="Utility\ClassicSpriteLoader.cs" />
|
||||
<Compile Include="Utility\EnhFormatString.cs" />
|
||||
<Compile Include="Utility\ExtensionMethods.cs" />
|
||||
<Compile Include="Utility\FileNameGenerator.cs" />
|
||||
<Compile Include="Utility\MixFileNameGenerator.cs" />
|
||||
<Compile Include="Utility\GameTextManager.cs" />
|
||||
<Compile Include="Utility\GameTextManagerClassic.cs" />
|
||||
<Compile Include="Utility\GeneralUtils.cs" />
|
||||
@ -810,6 +810,15 @@
|
||||
<None Include="Resources\mixcontent.ini">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Resources\mixcontent_ra1.ini">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Resources\mixcontent_sole.ini">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Resources\mixcontent_td.ini">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="Resources\UI_CustomMissionPreviewDefault.png" />
|
||||
<Content Include="License.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
|
@ -31,11 +31,12 @@
|
||||
this.mixContentsListView = new System.Windows.Forms.ListView();
|
||||
this.nameColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.typeColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.SizeColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.infoColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.btnCancel = new System.Windows.Forms.Button();
|
||||
this.btnOpen = new System.Windows.Forms.Button();
|
||||
this.btnCloseFile = new System.Windows.Forms.Button();
|
||||
this.SizeColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.DescColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// mixContentsListView
|
||||
@ -47,7 +48,8 @@
|
||||
this.nameColumnHeader,
|
||||
this.typeColumnHeader,
|
||||
this.SizeColumnHeader,
|
||||
this.infoColumnHeader});
|
||||
this.infoColumnHeader,
|
||||
this.DescColumnHeader});
|
||||
this.mixContentsListView.FullRowSelect = true;
|
||||
this.mixContentsListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
|
||||
this.mixContentsListView.HideSelection = false;
|
||||
@ -78,6 +80,12 @@
|
||||
this.typeColumnHeader.Text = "Type";
|
||||
this.typeColumnHeader.Width = 80;
|
||||
//
|
||||
// SizeColumnHeader
|
||||
//
|
||||
this.SizeColumnHeader.Tag = "-80";
|
||||
this.SizeColumnHeader.Text = "Size";
|
||||
this.SizeColumnHeader.Width = 80;
|
||||
//
|
||||
// infoColumnHeader
|
||||
//
|
||||
this.infoColumnHeader.Tag = "1";
|
||||
@ -119,11 +127,11 @@
|
||||
this.btnCloseFile.UseVisualStyleBackColor = true;
|
||||
this.btnCloseFile.Click += new System.EventHandler(this.BtnCloseFile_Click);
|
||||
//
|
||||
// SizeColumnHeader
|
||||
// DescColumnHeader
|
||||
//
|
||||
this.SizeColumnHeader.Tag = "-80";
|
||||
this.SizeColumnHeader.Text = "Size";
|
||||
this.SizeColumnHeader.Width = 80;
|
||||
this.DescColumnHeader.Tag = "1";
|
||||
this.DescColumnHeader.Text = "Description";
|
||||
this.DescColumnHeader.Width = 100;
|
||||
//
|
||||
// OpenFromMixDialog
|
||||
//
|
||||
@ -154,5 +162,6 @@
|
||||
private System.Windows.Forms.Button btnOpen;
|
||||
private System.Windows.Forms.Button btnCloseFile;
|
||||
private System.Windows.Forms.ColumnHeader SizeColumnHeader;
|
||||
private System.Windows.Forms.ColumnHeader DescColumnHeader;
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ namespace MobiusEditor.Dialogs
|
||||
{
|
||||
private bool resizing = false;
|
||||
private List<MixFile> openedMixFiles = new List<MixFile>();
|
||||
private Dictionary<uint, string> encodedFilenames;
|
||||
private Dictionary<uint, MixEntry> encodedFilenames;
|
||||
private SimpleMultiThreading analysisMultiThreader;
|
||||
List<MixEntry> currentMixInfo;
|
||||
private string titleMain;
|
||||
@ -23,7 +23,12 @@ namespace MobiusEditor.Dialogs
|
||||
|
||||
public Label StatusLabel { get; set; }
|
||||
|
||||
public OpenFromMixDialog(MixFile baseMix, Dictionary<uint, string> encodedFilenames)
|
||||
public OpenFromMixDialog(MixFile baseMix, Dictionary<uint, String> encodedFilenames)
|
||||
:this(baseMix, encodedFilenames == null ? null : encodedFilenames.ToDictionary(kvp => kvp.Key, kvp => new MixEntry(kvp.Key, kvp.Value, null)))
|
||||
{
|
||||
|
||||
}
|
||||
public OpenFromMixDialog(MixFile baseMix, Dictionary<uint, MixEntry> encodedFilenames)
|
||||
{
|
||||
InitializeComponent();
|
||||
titleMain = this.Text;
|
||||
@ -244,6 +249,7 @@ namespace MobiusEditor.Dialogs
|
||||
item.SubItems.Add(mixFileInfo.Type.ToString());
|
||||
item.SubItems.Add(mixFileInfo.Length.ToString());
|
||||
item.SubItems.Add(mixFileInfo.Info);
|
||||
item.SubItems.Add(mixFileInfo.Description);
|
||||
mixContentsListView.Items.Add(item).ToolTipText = mixFileInfo.Name ?? mixFileInfo.IdString;
|
||||
}
|
||||
mixContentsListView.EndUpdate();
|
||||
@ -264,7 +270,7 @@ namespace MobiusEditor.Dialogs
|
||||
return;
|
||||
}
|
||||
float totalColumnWidth = 0;
|
||||
int totalAvailablewidth = listView.ClientRectangle.Width - 1;
|
||||
int totalAvailablewidth = listView.ClientRectangle.Width - 2;
|
||||
int availablewidth = totalAvailablewidth;
|
||||
int columns = listView.Columns.Count;
|
||||
int[] tagWidths = new int[columns];
|
||||
|
@ -37,7 +37,6 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using static MobiusEditor.Utility.FileNameGenerator;
|
||||
|
||||
namespace MobiusEditor
|
||||
{
|
||||
@ -466,7 +465,7 @@ namespace MobiusEditor
|
||||
|
||||
private void FileOpenFromMixMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
string[][] theaterInfo = TiberianDawn.TheaterTypes.GetAllTypes().Select(th => new string[] { th.ClassicExtension.Trim('.'), th.ClassicTileset }).ToArray();
|
||||
// string[][] theaterInfo = TiberianDawn.TheaterTypes.GetAllTypes().Select(th => new string[] { th.ClassicExtension.Trim('.'), th.ClassicTileset }).ToArray();
|
||||
/*/
|
||||
string defTh = "{0}make.{1}";
|
||||
StringTypeDefinition stdBld = new StringTypeDefinition(defTh);
|
||||
@ -491,11 +490,11 @@ namespace MobiusEditor
|
||||
Debug.WriteLine(name);
|
||||
}
|
||||
//*/
|
||||
String mixPath = Path.Combine(Program.ApplicationPath, "mixcontent.ini");
|
||||
FileNameGenerator fng = new FileNameGenerator(mixPath);
|
||||
foreach (KeyValuePair<uint, string> kvp in fng.GetAllNameIds())
|
||||
string mixPath = Path.Combine(Program.ApplicationPath, "mixcontent.ini");
|
||||
MixFileNameGenerator fng = new MixFileNameGenerator(mixPath);
|
||||
foreach (MixEntry entry in fng.GetAllNameIds())
|
||||
{
|
||||
Debug.WriteLine(String.Format("{0:X8} : {1}", kvp.Key, kvp.Value));
|
||||
Debug.WriteLine(String.Format("{0:X8} : {1} - {2}", entry.Id, entry.Name, entry.Description ?? String.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ namespace MobiusEditor.Model
|
||||
#region properties
|
||||
public abstract GameType GameType { get; }
|
||||
public abstract string Name { get; }
|
||||
public abstract string IniName { get; }
|
||||
public abstract string DefaultSaveDirectory { get; }
|
||||
public abstract string SaveFilter { get; }
|
||||
public abstract string OpenFilter { get; }
|
||||
|
@ -28,6 +28,7 @@ namespace MobiusEditor.RedAlert
|
||||
{
|
||||
public override GameType GameType => GameType.RedAlert;
|
||||
public override string Name => "Red Alert";
|
||||
public override string IniName => "RedAlert";
|
||||
public override string DefaultSaveDirectory => Path.Combine(Globals.RootSaveDirectory, "Red_Alert");
|
||||
public override string SaveFilter => Constants.FileFilter;
|
||||
public override string OpenFilter => Constants.FileFilter;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -27,6 +27,7 @@ namespace MobiusEditor.SoleSurvivor
|
||||
{
|
||||
public override GameType GameType => GameType.SoleSurvivor;
|
||||
public override string Name => "Sole Survivor";
|
||||
public override string IniName => "SoleSurvivor";
|
||||
public override string DefaultSaveDirectory => Path.Combine(Globals.RootSaveDirectory, "Tiberian_Dawn");
|
||||
public override string OpenFilter => Constants.FileFilter;
|
||||
public override string SaveFilter => Constants.FileFilter;
|
||||
|
@ -27,6 +27,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
{
|
||||
public override GameType GameType => GameType.TiberianDawn;
|
||||
public override string Name => "Tiberian Dawn";
|
||||
public override string IniName => "TiberianDawn";
|
||||
public override string DefaultSaveDirectory => Path.Combine(Globals.RootSaveDirectory, "Tiberian_Dawn");
|
||||
public override string OpenFilter => Constants.FileFilter;
|
||||
public override string SaveFilter => Constants.FileFilter;
|
||||
|
@ -24,6 +24,7 @@ namespace MobiusEditor.Utility
|
||||
/// </summary>
|
||||
public struct EnhFormatString : IFormattable
|
||||
{
|
||||
private static readonly Regex FormatDetectRegex = new Regex("{(\\d+)(?::\\d+(?:-\\d+)?)?}", RegexOptions.Compiled);
|
||||
private static readonly Regex FormatRegex = new Regex("^(\\d+)(?:-(\\d+))?$", RegexOptions.Compiled);
|
||||
public readonly string _string;
|
||||
public EnhFormatString(string str)
|
||||
@ -77,6 +78,27 @@ namespace MobiusEditor.Utility
|
||||
=> new EnhFormatString(code);
|
||||
public static implicit operator string(EnhFormatString language)
|
||||
=> language._string;
|
||||
|
||||
/// <summary>
|
||||
/// Finds the highest {#} argument inside the string that matches the EnhFormatString format.
|
||||
/// </summary>
|
||||
/// <param name="formatStr">String to find arguments in.</param>
|
||||
/// <returns>The highest argument number found in <paramref name="formatStr"/>, or -1 if none were found.</returns>
|
||||
public static int GetHighestArg(string formatStr)
|
||||
{
|
||||
int highestArg = -1;
|
||||
if (formatStr != null)
|
||||
{
|
||||
Match formatMatch = FormatDetectRegex.Match(formatStr);
|
||||
while (formatMatch.Success)
|
||||
{
|
||||
int argNumber = Int32.Parse(formatMatch.Groups[1].Value);
|
||||
highestArg = Math.Max(highestArg, argNumber);
|
||||
formatMatch = formatMatch.NextMatch();
|
||||
}
|
||||
}
|
||||
return highestArg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,400 +0,0 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyright (C) 2004 Sam Hocevar<sam@hocevar.net>
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
using MobiusEditor.Utility.Hashing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace MobiusEditor.Utility
|
||||
{
|
||||
public class FileNameGenerator
|
||||
{
|
||||
private const string parseError = "Error parsing ini: section {0} not found.";
|
||||
private const string gamesHeader = "Games";
|
||||
private const string typesHeader = "FileTypes";
|
||||
|
||||
private static readonly Dictionary<string, HashMethod> hashMethods = HashMethod.GetRegisteredMethods().ToDictionary(m => m.GetSimpleName(), StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly HashMethod defaultHashMethod = HashMethod.GetRegisteredMethods().FirstOrDefault();
|
||||
|
||||
private List<string> games;
|
||||
private Dictionary<string, string[]> gameFiles;
|
||||
private Dictionary<string, string[][]> gameTheaterInfo;
|
||||
private Dictionary<string, string[][]> modTheaterInfo;
|
||||
private Dictionary<string, FileNameGeneratorEntry[]> typeDefinitions;
|
||||
|
||||
public List<string> Games => games?.ToList();
|
||||
|
||||
public FileNameGenerator(string iniPath)
|
||||
:this(null, iniPath)
|
||||
{
|
||||
}
|
||||
|
||||
public FileNameGenerator(INI iniFile)
|
||||
: this(iniFile, Path.Combine(Path.GetDirectoryName("."), "dummy.ini"))
|
||||
{
|
||||
}
|
||||
|
||||
public FileNameGenerator(INI iniFile, string readPath)
|
||||
{
|
||||
games = new List<string>();
|
||||
gameFiles = new Dictionary<string, string[]>();
|
||||
gameTheaterInfo = new Dictionary<string, string[][]>();
|
||||
modTheaterInfo = new Dictionary<string, string[][]>();
|
||||
typeDefinitions = new Dictionary<string, FileNameGeneratorEntry[]>();
|
||||
|
||||
bool validFile = File.Exists(readPath);
|
||||
if (iniFile == null && validFile)
|
||||
{
|
||||
iniFile = new INI();
|
||||
using (TextReader reader = new StreamReader(readPath, Encoding.GetEncoding(437)))
|
||||
{
|
||||
iniFile.Parse(reader);
|
||||
}
|
||||
}
|
||||
INISection gamesSection = iniFile.Sections[gamesHeader];
|
||||
INISection typesSection = iniFile.Sections[typesHeader];
|
||||
if (gamesSection == null)
|
||||
{
|
||||
throw new ArgumentException(String.Format(parseError, gamesHeader), "iniFile");
|
||||
}
|
||||
if (typesSection == null)
|
||||
{
|
||||
throw new ArgumentException(String.Format(parseError, typesHeader), "iniFile");
|
||||
}
|
||||
int index = 0;
|
||||
string indexVal;
|
||||
while (!String.IsNullOrEmpty(indexVal = typesSection.TryGetValue(index.ToString())))
|
||||
{
|
||||
index++;
|
||||
INISection typeSection = iniFile.Sections[indexVal];
|
||||
if (typeSection == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int nameIndex = 0;
|
||||
string nameVal;
|
||||
List<FileNameGeneratorEntry> generators = new List<FileNameGeneratorEntry>();
|
||||
while (!string.IsNullOrEmpty(nameVal = typeSection.TryGetValue(nameIndex.ToString())))
|
||||
{
|
||||
nameIndex++;
|
||||
generators.Add(new FileNameGeneratorEntry(nameVal));
|
||||
}
|
||||
if (generators.Count > 0)
|
||||
{
|
||||
typeDefinitions.Add(indexVal, generators.ToArray());
|
||||
}
|
||||
}
|
||||
index = 0;
|
||||
while (!String.IsNullOrEmpty(indexVal = gamesSection.TryGetValue(index.ToString())))
|
||||
{
|
||||
index++;
|
||||
INISection gameSection = iniFile.Sections[indexVal];
|
||||
if (gameSection == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string[][] theaterInfos = GetTheaterInfo(gameSection, "Theaters", true);
|
||||
string[][] modTheaterInfos = GetTheaterInfo(gameSection, "ModTheaters", false);
|
||||
string externalFile = gameSection.TryGetValue("FilesListIni");
|
||||
string filesList = gameSection.TryGetValue("FilesSection");
|
||||
if (String.IsNullOrEmpty(filesList))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
INISection gameFilesSection = null;
|
||||
if (!String.IsNullOrEmpty(externalFile))
|
||||
{
|
||||
if (validFile)
|
||||
{
|
||||
INI fileListFile = new INI();
|
||||
try
|
||||
{
|
||||
using (TextReader reader = new StreamReader(readPath, Encoding.GetEncoding(437)))
|
||||
{
|
||||
fileListFile.Parse(reader);
|
||||
}
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
gameFilesSection = fileListFile.Sections[filesList];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gameFilesSection = iniFile.Sections[filesList];
|
||||
}
|
||||
if (gameFilesSection == null || gameFilesSection.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
gameTheaterInfo.Add(indexVal, theaterInfos);
|
||||
if (modTheaterInfos != null && modTheaterInfos.Length > 0)
|
||||
{
|
||||
modTheaterInfo.Add(indexVal, modTheaterInfos);
|
||||
}
|
||||
gameFiles.Add(indexVal, gameFilesSection.Keys.Select(kvp => kvp.Key).ToArray());
|
||||
games.Add(indexVal);
|
||||
}
|
||||
}
|
||||
|
||||
private string[][] GetTheaterInfo(INISection gameSection, string keyName, bool generateDummy)
|
||||
{
|
||||
string theaters = gameSection.TryGetValue(keyName);
|
||||
if (string.IsNullOrEmpty(theaters))
|
||||
{
|
||||
return !generateDummy ? null : new string[][] { new[] { string.Empty } };
|
||||
}
|
||||
string[] theatersList = theaters.Split(',');
|
||||
string[][] theaterInfos = new string[theatersList.Length][];
|
||||
for (int i = 0; i < theatersList.Length; ++i)
|
||||
{
|
||||
theaterInfos[i] = theatersList[i].Split(':');
|
||||
}
|
||||
return theaterInfos;
|
||||
}
|
||||
|
||||
public IEnumerable<KeyValuePair<uint, string>> GetAllNameIds()
|
||||
{
|
||||
foreach (string game in games)
|
||||
{
|
||||
foreach (KeyValuePair<uint, string> nameInfo in this.GetNameIds(game))
|
||||
{
|
||||
yield return nameInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<KeyValuePair<uint, string>> GetNameIds(string game)
|
||||
{
|
||||
if (!games.Contains(game))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
string[][] theaterInfo;
|
||||
if (!gameTheaterInfo.TryGetValue(game, out theaterInfo))
|
||||
{
|
||||
theaterInfo = new string[][] { new[] { string.Empty } };
|
||||
}
|
||||
string[][] theaterInfomod;
|
||||
modTheaterInfo.TryGetValue(game, out theaterInfomod);
|
||||
string[] filenames;
|
||||
if (!gameFiles.TryGetValue(game, out filenames))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
foreach (KeyValuePair<uint, string> fileInfo in GetHashInfo(filenames, theaterInfo, false))
|
||||
{
|
||||
yield return fileInfo;
|
||||
}
|
||||
if (theaterInfomod != null && theaterInfomod.Length > 0)
|
||||
{
|
||||
foreach (KeyValuePair<uint, string> fileInfo in GetHashInfo(filenames, theaterInfomod, true))
|
||||
{
|
||||
yield return fileInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<uint, string>> GetHashInfo(string[] filenames, string[][] theaterInfo, bool ignoreNonTheaterFiles)
|
||||
{
|
||||
foreach (string filename in filenames)
|
||||
{
|
||||
string[] fnParts = filename.Split(',');
|
||||
string name = fnParts[0].Trim();
|
||||
string hashMethod = fnParts.Length < 2 ? null : fnParts[1].Trim();
|
||||
string type = fnParts.Length < 3 ? null : fnParts[2].Trim();
|
||||
HashMethod method;
|
||||
if (!hashMethods.TryGetValue(hashMethod, out method))
|
||||
{
|
||||
throw new Exception("Error in filename data: hash method \"" + type + "\" is unknown.");
|
||||
}
|
||||
if (String.IsNullOrEmpty(type))
|
||||
{
|
||||
if (ignoreNonTheaterFiles)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
yield return new KeyValuePair<uint, string>(method.GetNameId(name), name);
|
||||
}
|
||||
else
|
||||
{
|
||||
FileNameGeneratorEntry[] generators = null;
|
||||
if (!typeDefinitions.TryGetValue(type, out generators))
|
||||
{
|
||||
throw new Exception("Error in filename data: no definition found for type \"" + type + "\"");
|
||||
}
|
||||
// Generate all normal filenames.
|
||||
foreach (FileNameGeneratorEntry generator in generators)
|
||||
{
|
||||
// if only running for addon-theaters, skip files that don't have theater info in them.
|
||||
if (ignoreNonTheaterFiles && !generator.IsTheaterDependent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (string generatedName in generator.GetNames(name, theaterInfo))
|
||||
{
|
||||
yield return new KeyValuePair<uint, string>(method.GetNameId(generatedName), generatedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class FileNameGeneratorEntry
|
||||
{
|
||||
private static readonly Regex FormatRegex = new Regex("{(\\d+)(?::\\d+(?:-\\d+)?)?}", RegexOptions.Compiled);
|
||||
private static readonly Regex IterateRegex = new Regex("\\[((?:[^\\[\\]\\(\\)])|(?:\\([^\\[\\]\\(\\))]+\\)))+\\]", RegexOptions.Compiled);
|
||||
public bool IsIterator { get; private set; }
|
||||
public bool IsTheaterDependent { get; private set; }
|
||||
public int HighestArg { get; private set; }
|
||||
private string[][] iterations;
|
||||
|
||||
public FileNameGeneratorEntry(string format)
|
||||
{
|
||||
Match formatMatch = FormatRegex.Match(format);
|
||||
int highestArg = -1;
|
||||
while (formatMatch.Success)
|
||||
{
|
||||
int argNumber = Int32.Parse(formatMatch.Groups[1].Value);
|
||||
highestArg = Math.Max(highestArg, argNumber);
|
||||
// if any groups beyond {0} are in there, it needs to be iterated over theaters.
|
||||
if (argNumber > 0)
|
||||
{
|
||||
IsTheaterDependent = true;
|
||||
}
|
||||
formatMatch = formatMatch.NextMatch();
|
||||
}
|
||||
HighestArg = highestArg;
|
||||
Match iteratorMatch = IterateRegex.Match(format);
|
||||
|
||||
List<string[]> iterationBlocks = new List<string[]>();
|
||||
// Chop that string up!
|
||||
int currentIndex = 0;
|
||||
while (iteratorMatch.Success)
|
||||
{
|
||||
// capture in-between chunks as a single-item list to 'iterate over'.
|
||||
if (iteratorMatch.Index > currentIndex)
|
||||
{
|
||||
iterationBlocks.Add(new[] { format.Substring(currentIndex, iteratorMatch.Index - currentIndex) });
|
||||
}
|
||||
List<string> iterationChunks = new List<string>();
|
||||
foreach (Capture capture in iteratorMatch.Groups[1].Captures)
|
||||
{
|
||||
string val = capture.Value;
|
||||
if (val.Length > 2)
|
||||
{
|
||||
// chop off the surrounding brackets
|
||||
val = val.Substring(1, val.Length - 2);
|
||||
}
|
||||
iterationChunks.Add(val);
|
||||
}
|
||||
iterationBlocks.Add(iterationChunks.ToArray());
|
||||
currentIndex = iteratorMatch.Index + iteratorMatch.Length;
|
||||
iteratorMatch = iteratorMatch.NextMatch();
|
||||
}
|
||||
if (currentIndex < format.Length)
|
||||
{
|
||||
iterationBlocks.Add(new[] { format.Substring(currentIndex, format.Length - currentIndex) });
|
||||
}
|
||||
iterations = iterationBlocks.ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetNames(string baseName, string[][] theaterInfo)
|
||||
{
|
||||
//foreach (string name in IterateName(baseName, new int[iterations.Length], iterations[0].Length, theaterInfo))
|
||||
//{
|
||||
// yield return name;
|
||||
//}
|
||||
foreach (string name in CreateNames(baseName, theaterInfo, 0, new int[iterations.Length], iterations.Length, iterations.Length - 1))
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the main workhorse, it creates new strings and formats them to output the final composed names.
|
||||
/// </summary>
|
||||
/// <param name="baseName">base name to format into the string as {0}</param>
|
||||
/// <param name="theaterInfo">Theater info, used to iterate over the names in case groups beyond {0} are used.</param>
|
||||
/// <param name="currentChunkPosition">The position of the entry which is replaced by new items currently.</param>
|
||||
/// <param name="chunkEntries">The current key represented as int array, to be filled ith the array of items to iterate.</param>
|
||||
/// <param name="chunkLength">The length of the full key, to know when to end.</param>
|
||||
/// <param name="indexOfLastChunk">The length of the full key minus one, to know when to end.</param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<string> CreateNames(string baseName, string[][] theaterInfo, int currentChunkPosition, int[] chunkEntries, Int32 chunkLength, Int32 indexOfLastChunk)
|
||||
{
|
||||
int nextCharPosition = currentChunkPosition + 1;
|
||||
int entriesLength = iterations[currentChunkPosition].Length;
|
||||
// We are looping through the full length of our entries-to-test array
|
||||
for (int i = 0; i < entriesLength; i++)
|
||||
{
|
||||
// The character at the currentCharPosition will be replaced by a new character
|
||||
// from the charactersToTest array => a new key combination will be created
|
||||
chunkEntries[currentChunkPosition] = i;
|
||||
|
||||
// The method calls itself recursively until all positions of the key char array have been replaced
|
||||
if (currentChunkPosition < indexOfLastChunk)
|
||||
{
|
||||
foreach (string name in this.CreateNames(baseName, theaterInfo, nextCharPosition, chunkEntries, chunkLength, indexOfLastChunk))
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
foreach (string generatedName in BuildString(baseName, theaterInfo, chunkEntries, chunkLength))
|
||||
{
|
||||
yield return generatedName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<string> BuildString(string baseName, string[][] theaterInfo, int[] keyEntries, int keyLength)
|
||||
{
|
||||
string[] chunks = new string[keyLength];
|
||||
for (int i = 0; i < keyLength; i++)
|
||||
{
|
||||
chunks[i] = iterations[i][keyEntries[i]];
|
||||
}
|
||||
string format = String.Join(String.Empty, chunks);
|
||||
if (!IsTheaterDependent)
|
||||
{
|
||||
yield return String.Format(format, (EnhFormatString)baseName);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < theaterInfo.Length; i++)
|
||||
{
|
||||
string[] thInfo = theaterInfo[i];
|
||||
int thInfoLen = thInfo.Length + 1;
|
||||
int arrLen = HighestArg + 1;
|
||||
object[] strings = new object[arrLen];
|
||||
strings[0] = (EnhFormatString)baseName;
|
||||
for (int j = 1; j < arrLen; j++)
|
||||
{
|
||||
strings[j] = new EnhFormatString(j >= thInfoLen ? String.Empty : thInfo[j - 1]);
|
||||
}
|
||||
yield return String.Format(format, strings);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.Remoting.Contexts;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MobiusEditor.Utility
|
||||
{
|
||||
@ -23,6 +25,8 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public class YesNoBooleanTypeConverter : TypeConverter
|
||||
{
|
||||
private static readonly Regex NumRegex = new Regex("^\\d+$", RegexOptions.Compiled);
|
||||
|
||||
public BooleanStringStyle BooleanStringStyle { get; set; } = BooleanStringStyle.YesNo;
|
||||
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
@ -61,8 +65,27 @@ namespace MobiusEditor.Utility
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return ConvertFrom(str);
|
||||
}
|
||||
|
||||
int first = (str.Length > 0) ? str.ToUpper()[0] : 0;
|
||||
public bool ConvertFrom(string value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
value = value.Trim();
|
||||
// If is numeric, any value higher than 0 us true.
|
||||
bool isNumeric = NumRegex.IsMatch(value);
|
||||
if (isNumeric)
|
||||
{
|
||||
value = value.TrimStart('0');
|
||||
}
|
||||
if (value.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
char first = (isNumeric && Int32.Parse(value) != 0) ? '1' : value.ToUpper()[0];
|
||||
return (first == 'T') || (first == 'Y') || (first == '1');
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ namespace MobiusEditor.Utility
|
||||
private const uint xccId = 0x54C2D545;
|
||||
private const uint maxProcessed = 0x500000;
|
||||
|
||||
public static List<MixEntry> AnalyseFiles(MixFile current, Dictionary<uint, string> encodedFilenames, bool preferMissions, Func<bool> checkAbort)
|
||||
public static List<MixEntry> AnalyseFiles(MixFile current, Dictionary<uint, MixEntry> encodedFilenames, bool preferMissions, Func<bool> checkAbort)
|
||||
{
|
||||
List<uint> filesList = current.GetFileIds();
|
||||
List<MixEntry> fileInfo = new List<MixEntry>();
|
||||
@ -99,17 +99,24 @@ namespace MobiusEditor.Utility
|
||||
}
|
||||
string name = null;
|
||||
//uint fileIdm1 = fileId == 0 ? 0 : fileId - 1;
|
||||
if (xccInfoFilenames == null || !xccInfoFilenames.TryGetValue(fileId, out name))
|
||||
{
|
||||
if (!encodedFilenames.TryGetValue(fileId, out name))
|
||||
{
|
||||
name = null;
|
||||
}
|
||||
}
|
||||
if (name != null)
|
||||
if (xccInfoFilenames != null && xccInfoFilenames.TryGetValue(fileId, out name))
|
||||
{
|
||||
mixInfo.Name = name;
|
||||
}
|
||||
MixEntry mi;
|
||||
if (encodedFilenames.TryGetValue(fileId, out mi))
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
mixInfo.Name = mi.Name;
|
||||
mixInfo.Description = mi.Description;
|
||||
}
|
||||
else if (name.Equals(mi.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Don't apply description if xcc info name doesn't match encodedFilenames entry.
|
||||
mixInfo.Description = mi.Description;
|
||||
}
|
||||
}
|
||||
fileInfo.Add(mixInfo);
|
||||
using (Stream file = current.OpenFile(fileId))
|
||||
{
|
||||
|
@ -455,13 +455,14 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public class MixEntry
|
||||
{
|
||||
public uint Id;
|
||||
public string Name;
|
||||
public int Duplicate;
|
||||
public uint Id;
|
||||
public uint Offset;
|
||||
public uint Length;
|
||||
public MixContentType Type = MixContentType.Unknown;
|
||||
public string Info;
|
||||
public string Description;
|
||||
|
||||
public string DisplayName => (Name ?? IdString) + (Duplicate == 0 ? string.Empty : " (" + Duplicate.ToString() + ")");
|
||||
public string SortName => Name ?? ("zzzzzzzzzzzz " + IdString);
|
||||
@ -472,13 +473,21 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public MixEntry(MixEntry orig)
|
||||
{
|
||||
this.Name = orig.Name;
|
||||
this.Duplicate = orig.Duplicate;
|
||||
this.Id = orig.Id;
|
||||
this.Offset = orig.Offset;
|
||||
this.Length = orig.Length;
|
||||
this.Type = orig.Type;
|
||||
this.Info = orig.Info;
|
||||
Id = orig.Id;
|
||||
Name = orig.Name;
|
||||
Duplicate = orig.Duplicate;
|
||||
Offset = orig.Offset;
|
||||
Length = orig.Length;
|
||||
Type = orig.Type;
|
||||
Info = orig.Info;
|
||||
Description = orig.Description;
|
||||
}
|
||||
|
||||
public MixEntry(uint id, string name, string description)
|
||||
{
|
||||
Name = name;
|
||||
Id = id;
|
||||
Description= description;
|
||||
}
|
||||
|
||||
public MixEntry(string filename)
|
||||
|
563
CnCTDRAMapEditor/Utility/MixFileNameGenerator.cs
Normal file
563
CnCTDRAMapEditor/Utility/MixFileNameGenerator.cs
Normal file
@ -0,0 +1,563 @@
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyright (C) 2004 Sam Hocevar<sam@hocevar.net>
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
using MobiusEditor.Utility.Hashing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MobiusEditor.Utility
|
||||
{
|
||||
public class MixFileNameGenerator
|
||||
{
|
||||
[Flags]
|
||||
private enum ConstrArgs
|
||||
{
|
||||
None /**/ = 0,
|
||||
IniObj /**/ = 1 << 0,
|
||||
IniPath /**/ = 1 << 1,
|
||||
SideInis /**/ = 1 << 2,
|
||||
}
|
||||
|
||||
private class GameDefinition
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public Dictionary<string, FileNameGeneratorEntry[]> TypeDefinitions { get; set; }
|
||||
public string[] Files { get; set; }
|
||||
public Dictionary<string, string> FileDescriptions { get; set; }
|
||||
public HashMethod Hasher { get; set; }
|
||||
public string[][] TheaterInfo { get; set; }
|
||||
public string[][] ModTheaterInfo { get; set; }
|
||||
public bool HasMixNesting { get; set; }
|
||||
public bool NewMixFormat { get; set; }
|
||||
|
||||
public GameDefinition(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
private const string parseError = "Error parsing ini: section {0} not found.";
|
||||
private const string gamesHeader = "Games";
|
||||
|
||||
private static readonly Dictionary<string, HashMethod> hashMethods = HashMethod.GetRegisteredMethods().ToDictionary(m => m.GetSimpleName(), StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly HashMethod defaultHashMethod = HashMethod.GetRegisteredMethods().FirstOrDefault();
|
||||
|
||||
private List<string> games = new List<string>();
|
||||
private Dictionary<string, GameDefinition> gameInfo = new Dictionary<string, GameDefinition>();
|
||||
|
||||
public List<string> Games => games.ToList();
|
||||
|
||||
public MixFileNameGenerator(string iniPath)
|
||||
: this(null, iniPath, null, ConstrArgs.IniPath)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="iniFile">Ini file to open. If additional inis need to be read, they will be looked up in the current working directory.</param>
|
||||
public MixFileNameGenerator(INI iniFile)
|
||||
: this(iniFile, Path.Combine(Path.GetDirectoryName("."), "dummy.ini"), null, ConstrArgs.IniObj)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="iniFile">Main ini file to open.</param>
|
||||
/// <param name="iniPath">Source path of <paramref name="iniFile"/>, Is needed if side inis need to be read.</param>
|
||||
public MixFileNameGenerator(INI iniFile, string iniPath)
|
||||
: this(iniFile, iniPath, null, ConstrArgs.IniObj | ConstrArgs.IniPath)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make filename generator from ini objects, with possible additional ini objects given
|
||||
/// to read the file lists of specific games. This overload can be used to load the strings
|
||||
/// from embedded resources in the project.
|
||||
/// </summary>
|
||||
/// <param name="iniFile">Main ini file to open.</param>
|
||||
/// <param name="additionalInis">Dictionary of additional ini files that can be used to read the file lists of specific games.</param>
|
||||
public MixFileNameGenerator(INI iniFile, Dictionary<string, INI> additionalInis)
|
||||
: this(iniFile, null, additionalInis, ConstrArgs.IniObj | ConstrArgs.SideInis)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Full constructor; not public because all specific cases are handled in the overloads.
|
||||
/// </summary>
|
||||
/// <param name="iniFile">Main ini file to open. Can be null if <paramref name="iniPath"/> is given.</param>
|
||||
/// <param name="iniPath">Source path of <paramref name="iniFile"/>. Is needed if side inis need to be read, and <paramref name="additionalInis"/> is not supplied.</param>
|
||||
/// <param name="additionalInis">Dictionary of additional ini files that can be used to read the file lists of specific games.</param>
|
||||
/// <param name="originArgs">Origin args, to know what to give exceptions on when data is missing.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
private MixFileNameGenerator(INI iniFile, string iniPath, Dictionary<string, INI> additionalInis, ConstrArgs originArgs)
|
||||
{
|
||||
bool hasIni = (originArgs & ConstrArgs.IniObj) != 0;
|
||||
bool hasPath = (originArgs & ConstrArgs.IniPath) != 0;
|
||||
bool hasSide = (originArgs & ConstrArgs.SideInis) != 0;
|
||||
bool validPath = File.Exists(iniPath);
|
||||
// If given, ini obj needs to be valid.
|
||||
if (hasIni && iniFile == null)
|
||||
{
|
||||
throw new ArgumentNullException("iniFile");
|
||||
}
|
||||
// If path is given and no ini object, path needs to exist.
|
||||
if (!hasIni && hasPath && !validPath)
|
||||
{
|
||||
throw new ArgumentNullException("readPath");
|
||||
}
|
||||
bool validFolder = Directory.Exists(Path.GetDirectoryName(iniPath));
|
||||
if (iniFile == null && validPath)
|
||||
{
|
||||
iniFile = new INI();
|
||||
using (TextReader reader = new StreamReader(iniPath, Encoding.GetEncoding(437)))
|
||||
{
|
||||
iniFile.Parse(reader);
|
||||
}
|
||||
}
|
||||
if (iniFile == null && hasIni)
|
||||
{
|
||||
throw new ArgumentNullException("iniFile");
|
||||
}
|
||||
INISection gamesSection = iniFile.Sections[gamesHeader];
|
||||
if (gamesSection == null)
|
||||
{
|
||||
throw new ArgumentException(String.Format(parseError, gamesHeader), "iniFile");
|
||||
}
|
||||
// Iterate over games
|
||||
int gameIndex = 0;
|
||||
string gameString;
|
||||
while (!String.IsNullOrEmpty(gameString = gamesSection.TryGetValue(gameIndex.ToString())))
|
||||
{
|
||||
gameIndex++;
|
||||
INISection gameSection = iniFile.Sections[gameString];
|
||||
if (gameSection == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Read game info
|
||||
string[] externalFiles = (gameSection.TryGetValue("ContentIni") ?? String.Empty).Split(',');
|
||||
string[] typesSections = (gameSection.TryGetValue("FileTypes") ?? String.Empty).Split(',');
|
||||
string filesList = gameSection.TryGetValue("FilesSection");
|
||||
string[][] theaterInfos = GetTheaterInfo(gameSection, "Theaters", true);
|
||||
string[][] modTheaterInfos = GetTheaterInfo(gameSection, "ModTheaters", false);
|
||||
string hasher = gameSection.TryGetValue("Hasher");
|
||||
YesNoBooleanTypeConverter boolConv = new YesNoBooleanTypeConverter();
|
||||
bool newMixFormat = boolConv.ConvertFrom(gameSection.TryGetValue("NewMixFormat"));
|
||||
bool hasMixNesting = boolConv.ConvertFrom(gameSection.TryGetValue("HasMixNesting"));
|
||||
HashMethod hashMethod;
|
||||
hashMethods.TryGetValue(hasher, out hashMethod);
|
||||
if (String.IsNullOrEmpty(filesList))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Read game inis
|
||||
List<INI> gameIniFiles = new List<INI>();
|
||||
foreach (string ini in externalFiles)
|
||||
{
|
||||
INI extraIni;
|
||||
if (additionalInis != null && additionalInis.TryGetValue(ini, out extraIni))
|
||||
{
|
||||
gameIniFiles.Add(extraIni);
|
||||
}
|
||||
if (validFolder)
|
||||
{
|
||||
string filesListPath = Path.Combine(Path.GetDirectoryName(iniPath), ini);
|
||||
if (File.Exists(filesListPath))
|
||||
{
|
||||
extraIni = new INI();
|
||||
try
|
||||
{
|
||||
using (TextReader reader = new StreamReader(filesListPath, Encoding.GetEncoding(437)))
|
||||
{
|
||||
extraIni.Parse(reader);
|
||||
}
|
||||
// If anything fails in this, the ini is not added.
|
||||
gameIniFiles.Add(extraIni);
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add main ini as final one to read from.
|
||||
gameIniFiles.Add(iniFile);
|
||||
// Get type definitions for game
|
||||
Dictionary<string, FileNameGeneratorEntry[]> typeDefsForGame = GetTypeDefinitions(typesSections, gameIniFiles);
|
||||
INISection gameFilesSection = null;
|
||||
foreach (INI ini in gameIniFiles)
|
||||
{
|
||||
gameFilesSection = ini.Sections[filesList];
|
||||
if (gameFilesSection != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (gameFilesSection == null || gameFilesSection.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
GameDefinition gd = new GameDefinition(gameString);
|
||||
gd.TypeDefinitions = typeDefsForGame;
|
||||
gd.Files = gameFilesSection.Keys.Select(kvp => kvp.Key).ToArray();
|
||||
gd.FileDescriptions = gameFilesSection.Keys.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
gd.Hasher = hashMethod ?? defaultHashMethod;
|
||||
gd.NewMixFormat = newMixFormat;
|
||||
gd.HasMixNesting = hasMixNesting;
|
||||
gd.TheaterInfo = theaterInfos;
|
||||
if (modTheaterInfos != null && modTheaterInfos.Length > 0)
|
||||
{
|
||||
gd.ModTheaterInfo = modTheaterInfos;
|
||||
}
|
||||
gameInfo.Add(gameString, gd);
|
||||
games.Add(gameString);
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, FileNameGeneratorEntry[]> GetTypeDefinitions(String[] typesSections, List<INI> toScan)
|
||||
{
|
||||
Dictionary<string, FileNameGeneratorEntry[]> typeDefinitions = new Dictionary<string, FileNameGeneratorEntry[]>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (string sectionName in typesSections)
|
||||
{
|
||||
INISection typesSection = null;
|
||||
foreach (INI ini in toScan)
|
||||
{
|
||||
typesSection = ini.Sections[sectionName];
|
||||
if (typesSection != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (typesSection == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int index = 0;
|
||||
string typeString;
|
||||
while (!String.IsNullOrEmpty(typeString = typesSection.TryGetValue(index.ToString())))
|
||||
{
|
||||
// Read first encountered one only.
|
||||
if (typeDefinitions.ContainsKey(typeString))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
index++;
|
||||
INISection typeSection = null;
|
||||
foreach (INI iniFile in toScan)
|
||||
{
|
||||
typeSection = iniFile.Sections[typeString];
|
||||
if (typeSection != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (typeSection == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int nameIndex = 0;
|
||||
string nameVal;
|
||||
List<FileNameGeneratorEntry> generators = new List<FileNameGeneratorEntry>();
|
||||
while (!string.IsNullOrEmpty(nameVal = typeSection.TryGetValue(nameIndex.ToString())))
|
||||
{
|
||||
string info = typeSection.TryGetValue(nameIndex.ToString() + "Info");
|
||||
nameIndex++;
|
||||
generators.Add(new FileNameGeneratorEntry(nameVal, info));
|
||||
}
|
||||
if (generators.Count > 0)
|
||||
{
|
||||
typeDefinitions.Add(typeString, generators.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
return typeDefinitions;
|
||||
}
|
||||
|
||||
private string[][] GetTheaterInfo(INISection gameSection, string keyName, bool generateDummy)
|
||||
{
|
||||
string theaters = gameSection.TryGetValue(keyName);
|
||||
if (string.IsNullOrEmpty(theaters))
|
||||
{
|
||||
return !generateDummy ? null : new string[][] { new[] { string.Empty } };
|
||||
}
|
||||
string[] theatersList = theaters.Split(',');
|
||||
string[][] theaterInfos = new string[theatersList.Length][];
|
||||
for (int i = 0; i < theatersList.Length; ++i)
|
||||
{
|
||||
theaterInfos[i] = theatersList[i].Split(':');
|
||||
}
|
||||
return theaterInfos;
|
||||
}
|
||||
|
||||
public IEnumerable<MixEntry> GetAllNameIds()
|
||||
{
|
||||
foreach (string game in games)
|
||||
{
|
||||
foreach (MixEntry nameInfo in this.GetNameIds(game))
|
||||
{
|
||||
yield return nameInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<MixEntry> GetAllNameIds(string preferred)
|
||||
{
|
||||
List<string> gameNames = Games;
|
||||
gameNames.Remove(preferred);
|
||||
gameNames.Insert(0, preferred);
|
||||
foreach (string game in gameNames)
|
||||
{
|
||||
foreach (MixEntry nameInfo in this.GetNameIds(game))
|
||||
{
|
||||
yield return nameInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<MixEntry> GetNameIds(string game)
|
||||
{
|
||||
if (!games.Contains(game))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
if (!gameInfo.TryGetValue(game, out GameDefinition gameDef))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
string[][] theaterInfo = gameDef.TheaterInfo;
|
||||
if (theaterInfo == null)
|
||||
{
|
||||
theaterInfo = new string[][] { new[] { string.Empty } };
|
||||
}
|
||||
string[][] theaterInfomod = gameDef.ModTheaterInfo;
|
||||
string[] filenames = gameDef.Files;
|
||||
if (filenames == null || filenames.Length == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
Dictionary<string, string> filenameInfo = gameDef.FileDescriptions;
|
||||
HashMethod hashMethod = gameDef.Hasher ?? defaultHashMethod;
|
||||
Dictionary<string, FileNameGeneratorEntry[]> typeDefs = gameDef.TypeDefinitions;
|
||||
foreach (MixEntry fileInfo in GetHashInfo(filenames, filenameInfo, typeDefs, theaterInfo, hashMethod, false))
|
||||
{
|
||||
yield return fileInfo;
|
||||
}
|
||||
if (theaterInfomod != null && theaterInfomod.Length > 0)
|
||||
{
|
||||
foreach (MixEntry fileInfo in GetHashInfo(filenames, filenameInfo, typeDefs, theaterInfomod, hashMethod, true))
|
||||
{
|
||||
yield return fileInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<MixEntry> GetHashInfo(string[] filenames, Dictionary<string, string> filenameInfo, Dictionary<string, FileNameGeneratorEntry[]> typeDefinitions,
|
||||
string[][] theaterInfo, HashMethod hashMethod, bool ignoreNonTheaterFiles)
|
||||
{
|
||||
foreach (string filename in filenames)
|
||||
{
|
||||
string info = filenameInfo == null || !filenameInfo.ContainsKey(filename) ? null : filenameInfo[filename];
|
||||
// Ignore 1-character dummy strings.
|
||||
if (info.Trim().Length <= 1)
|
||||
{
|
||||
info = null;
|
||||
}
|
||||
string[] fnParts = filename.Split(',');
|
||||
string name = fnParts[0].Trim();
|
||||
string type = fnParts.Length < 2 ? null : fnParts[1].Trim();
|
||||
if (String.IsNullOrEmpty(type))
|
||||
{
|
||||
if (ignoreNonTheaterFiles)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
yield return new MixEntry(hashMethod.GetNameId(name), name, info);
|
||||
}
|
||||
else
|
||||
{
|
||||
FileNameGeneratorEntry[] generators = null;
|
||||
if (!typeDefinitions.TryGetValue(type, out generators))
|
||||
{
|
||||
throw new Exception("Error in filename data: no definition found for type \"" + type + "\"");
|
||||
}
|
||||
// Generate all normal filenames.
|
||||
foreach (FileNameGeneratorEntry generator in generators)
|
||||
{
|
||||
// if only running for addon-theaters, skip files that don't have theater info in them.
|
||||
if (ignoreNonTheaterFiles && !generator.IsTheaterDependent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string fileInfo = info;
|
||||
if (!String.IsNullOrEmpty(generator.ExtraInfo))
|
||||
{
|
||||
fileInfo = (String.IsNullOrEmpty(info) ? string.Empty : (info + " ")) + generator.ExtraInfo;
|
||||
}
|
||||
foreach ((string nameStr, string infoStr) in generator.GetNames(name, fileInfo, theaterInfo))
|
||||
{
|
||||
yield return new MixEntry(hashMethod.GetNameId(nameStr), nameStr, infoStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class FileNameGeneratorEntry
|
||||
{
|
||||
private static readonly Regex IterateRegex = new Regex("\\[((?:[^\\[\\]\\(\\)])|(?:\\([^\\[\\]\\(\\))]+\\)))+\\]", RegexOptions.Compiled);
|
||||
public bool IsTheaterDependent { get; private set; }
|
||||
public int HighestArg { get; private set; }
|
||||
public string ExtraInfo { get; set; }
|
||||
private string[][] iterations;
|
||||
|
||||
public FileNameGeneratorEntry(string format)
|
||||
: this(format, null)
|
||||
{
|
||||
}
|
||||
|
||||
public FileNameGeneratorEntry(string format, string extraInfo)
|
||||
{
|
||||
ExtraInfo = extraInfo;
|
||||
int highestArgFormat = EnhFormatString.GetHighestArg(format);
|
||||
int highestArgInfo = EnhFormatString.GetHighestArg(extraInfo);
|
||||
// This ignores highest arg in info.
|
||||
IsTheaterDependent = highestArgFormat > 0;
|
||||
HighestArg = Math.Max(highestArgFormat, highestArgInfo);
|
||||
Match iteratorMatch = IterateRegex.Match(format);
|
||||
List<string[]> iterationBlocks = new List<string[]>();
|
||||
// Chop that string up!
|
||||
int currentIndex = 0;
|
||||
while (iteratorMatch.Success)
|
||||
{
|
||||
// capture in-between chunks as a single-item list to 'iterate over'.
|
||||
if (iteratorMatch.Index > currentIndex)
|
||||
{
|
||||
iterationBlocks.Add(new[] { format.Substring(currentIndex, iteratorMatch.Index - currentIndex) });
|
||||
}
|
||||
List<string> iterationChunks = new List<string>();
|
||||
foreach (Capture capture in iteratorMatch.Groups[1].Captures)
|
||||
{
|
||||
string val = capture.Value;
|
||||
if (val.Length > 2)
|
||||
{
|
||||
// chop off the surrounding brackets
|
||||
val = val.Substring(1, val.Length - 2);
|
||||
}
|
||||
iterationChunks.Add(val);
|
||||
}
|
||||
iterationBlocks.Add(iterationChunks.ToArray());
|
||||
currentIndex = iteratorMatch.Index + iteratorMatch.Length;
|
||||
iteratorMatch = iteratorMatch.NextMatch();
|
||||
}
|
||||
if (currentIndex < format.Length)
|
||||
{
|
||||
iterationBlocks.Add(new[] { format.Substring(currentIndex, format.Length - currentIndex) });
|
||||
}
|
||||
iterations = iterationBlocks.ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<(string, string)> GetNames(string baseName, string extraInfo, string[][] theaterInfo)
|
||||
{
|
||||
foreach ((string, string) name in CreateNames(baseName, extraInfo, theaterInfo, 0, new int[iterations.Length], iterations.Length, iterations.Length - 1))
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the main workhorse, it creates new strings and formats them to output the final composed names.
|
||||
/// </summary>
|
||||
/// <param name="baseName">base name to format into the string as {0}</param>
|
||||
/// <param name="theaterInfo">Theater info, used to iterate over the names in case groups beyond {0} are used.</param>
|
||||
/// <param name="currentChunkPosition">The position of the entry which is replaced by new items currently.</param>
|
||||
/// <param name="chunkEntries">The current key represented as int array, to be filled ith the array of items to iterate.</param>
|
||||
/// <param name="chunkLength">The length of the full key, to know when to end.</param>
|
||||
/// <param name="indexOfLastChunk">The length of the full key minus one, to know when to end.</param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<(string, string)> CreateNames(string baseName, string extraInfo, string[][] theaterInfo, int currentChunkPosition, int[] chunkEntries, Int32 chunkLength, Int32 indexOfLastChunk)
|
||||
{
|
||||
int nextCharPosition = currentChunkPosition + 1;
|
||||
int entriesLength = iterations[currentChunkPosition].Length;
|
||||
// We are looping through the full length of our entries-to-test array
|
||||
for (int i = 0; i < entriesLength; i++)
|
||||
{
|
||||
// The character at the currentCharPosition will be replaced by a new character
|
||||
// from the charactersToTest array => a new key combination will be created
|
||||
chunkEntries[currentChunkPosition] = i;
|
||||
|
||||
// The method calls itself recursively until all positions of the key char array have been replaced
|
||||
if (currentChunkPosition < indexOfLastChunk)
|
||||
{
|
||||
foreach ((string, string) nameInfo in this.CreateNames(baseName, extraInfo, theaterInfo, nextCharPosition, chunkEntries, chunkLength, indexOfLastChunk))
|
||||
{
|
||||
yield return nameInfo;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
foreach ((string, string) generatedName in BuildString(baseName, extraInfo, theaterInfo, chunkEntries, chunkLength))
|
||||
{
|
||||
yield return generatedName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<(string, string)> BuildString(string baseName, string extraInfo, string[][] theaterInfo, int[] keyEntries, int keyLength)
|
||||
{
|
||||
string[] chunks = new string[keyLength];
|
||||
for (int i = 0; i < keyLength; i++)
|
||||
{
|
||||
chunks[i] = iterations[i][keyEntries[i]];
|
||||
}
|
||||
string format = String.Join(String.Empty, chunks);
|
||||
int arrLen = HighestArg + 1;
|
||||
object[] strings = new object[arrLen];
|
||||
if (arrLen > 0)
|
||||
{
|
||||
strings[0] = (EnhFormatString)baseName;
|
||||
}
|
||||
if (!IsTheaterDependent)
|
||||
{
|
||||
for (int j = 1; j < arrLen; j++)
|
||||
{
|
||||
// If the description uses theater args, ignore them.
|
||||
strings[j] = String.Empty;
|
||||
}
|
||||
string name = String.Format(format, strings);
|
||||
string info = extraInfo == null ? null : String.Format(extraInfo, strings);
|
||||
yield return (name, info);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < theaterInfo.Length; i++)
|
||||
{
|
||||
string[] thInfo = theaterInfo[i];
|
||||
int thInfoLen = thInfo.Length + 1;
|
||||
for (int j = 1; j < arrLen; j++)
|
||||
{
|
||||
strings[j] = new EnhFormatString(j >= thInfoLen ? String.Empty : thInfo[j - 1]);
|
||||
}
|
||||
string name = String.Format(format, strings);
|
||||
string info = extraInfo == null ? null : String.Format(extraInfo, strings);
|
||||
yield return (name, info);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user