From 2d178b5e55be5ab69a2a0aa488c5421299ce105d Mon Sep 17 00:00:00 2001 From: Nyerguds Date: Tue, 30 Apr 2024 10:29:42 +0200 Subject: [PATCH] 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. --- .gitattributes | 2 + CnCTDRAMapEditor/CnCTDRAMapEditor.csproj | 11 +- .../Dialogs/OpenFromMixDialog.Designer.cs | 21 +- CnCTDRAMapEditor/Dialogs/OpenFromMixDialog.cs | 12 +- CnCTDRAMapEditor/MainForm.cs | 11 +- CnCTDRAMapEditor/Model/GameInfo.cs | 1 + CnCTDRAMapEditor/RedAlert/GameInfoRedAlert.cs | 1 + CnCTDRAMapEditor/Resources/mixcontent.ini | 1218 +---------------- CnCTDRAMapEditor/SoleSurvivor/GameInfoSole.cs | 1 + .../TiberianDawn/GameInfoTibDawn.cs | 1 + CnCTDRAMapEditor/Utility/EnhFormatString.cs | 22 + CnCTDRAMapEditor/Utility/FileNameGenerator.cs | 400 ------ .../Utility/GenericBooleanTypeConverter.cs | 25 +- .../Utility/MixContentAnalysis.cs | 25 +- CnCTDRAMapEditor/Utility/MixFile.cs | 25 +- .../Utility/MixFileNameGenerator.cs | 563 ++++++++ 16 files changed, 748 insertions(+), 1591 deletions(-) delete mode 100644 CnCTDRAMapEditor/Utility/FileNameGenerator.cs create mode 100644 CnCTDRAMapEditor/Utility/MixFileNameGenerator.cs diff --git a/.gitattributes b/.gitattributes index a7320d0..221548c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/CnCTDRAMapEditor/CnCTDRAMapEditor.csproj b/CnCTDRAMapEditor/CnCTDRAMapEditor.csproj index 085728e..315cb89 100644 --- a/CnCTDRAMapEditor/CnCTDRAMapEditor.csproj +++ b/CnCTDRAMapEditor/CnCTDRAMapEditor.csproj @@ -622,7 +622,7 @@ - + @@ -810,6 +810,15 @@ Always + + Always + + + Always + + + Always + Always diff --git a/CnCTDRAMapEditor/Dialogs/OpenFromMixDialog.Designer.cs b/CnCTDRAMapEditor/Dialogs/OpenFromMixDialog.Designer.cs index 2842507..3d4b627 100644 --- a/CnCTDRAMapEditor/Dialogs/OpenFromMixDialog.Designer.cs +++ b/CnCTDRAMapEditor/Dialogs/OpenFromMixDialog.Designer.cs @@ -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; } } \ No newline at end of file diff --git a/CnCTDRAMapEditor/Dialogs/OpenFromMixDialog.cs b/CnCTDRAMapEditor/Dialogs/OpenFromMixDialog.cs index 3483bb1..7a37e92 100644 --- a/CnCTDRAMapEditor/Dialogs/OpenFromMixDialog.cs +++ b/CnCTDRAMapEditor/Dialogs/OpenFromMixDialog.cs @@ -13,7 +13,7 @@ namespace MobiusEditor.Dialogs { private bool resizing = false; private List openedMixFiles = new List(); - private Dictionary encodedFilenames; + private Dictionary encodedFilenames; private SimpleMultiThreading analysisMultiThreader; List currentMixInfo; private string titleMain; @@ -23,7 +23,12 @@ namespace MobiusEditor.Dialogs public Label StatusLabel { get; set; } - public OpenFromMixDialog(MixFile baseMix, Dictionary encodedFilenames) + public OpenFromMixDialog(MixFile baseMix, Dictionary encodedFilenames) + :this(baseMix, encodedFilenames == null ? null : encodedFilenames.ToDictionary(kvp => kvp.Key, kvp => new MixEntry(kvp.Key, kvp.Value, null))) + { + + } + public OpenFromMixDialog(MixFile baseMix, Dictionary 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]; diff --git a/CnCTDRAMapEditor/MainForm.cs b/CnCTDRAMapEditor/MainForm.cs index 14a557e..1be9c0e 100644 --- a/CnCTDRAMapEditor/MainForm.cs +++ b/CnCTDRAMapEditor/MainForm.cs @@ -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 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)); } } diff --git a/CnCTDRAMapEditor/Model/GameInfo.cs b/CnCTDRAMapEditor/Model/GameInfo.cs index a2575a9..7b5c9f7 100644 --- a/CnCTDRAMapEditor/Model/GameInfo.cs +++ b/CnCTDRAMapEditor/Model/GameInfo.cs @@ -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; } diff --git a/CnCTDRAMapEditor/RedAlert/GameInfoRedAlert.cs b/CnCTDRAMapEditor/RedAlert/GameInfoRedAlert.cs index 66ec7df..f8fcbae 100644 --- a/CnCTDRAMapEditor/RedAlert/GameInfoRedAlert.cs +++ b/CnCTDRAMapEditor/RedAlert/GameInfoRedAlert.cs @@ -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; diff --git a/CnCTDRAMapEditor/Resources/mixcontent.ini b/CnCTDRAMapEditor/Resources/mixcontent.ini index bfbf431..4b06233 100644 --- a/CnCTDRAMapEditor/Resources/mixcontent.ini +++ b/CnCTDRAMapEditor/Resources/mixcontent.ini @@ -1,4 +1,5 @@ ; This file defines all files that are automatically identified when opening a .mix archive. +; The definitions can be split into separate files, per game. ; Games list. Items in this list require consecutive numbers starting from 0. [Games] @@ -6,1195 +7,98 @@ 1=RedAlert 2=SoleSurvivor -; Game definitions. Each section can define three things: -; - FilesSection: Name of the ini section to read to find this game's files. Required. -; - Theaters: Comma-separated list of theater info. Each theater info block is a colon-separated list, -; normally containing the extension without dot as first item and the base filename as -; second item. Needed if theater data {1} and {2} are used in the FileTypes definitions. -; Using more than two items of theater data is technically possible. -; - ModTheaters: Secondary list of theaters. This list is handled after all filenames are generated with -; the standard theaters, because id collisions on them are treated as less important. -; This second iteration of all files only handles files whose type uses the theater info. -; - FilesListIni: Optional; if given, specifies a different ini file to read the files section from. - +; Game definitions. Each section can define these things: +; ContentIni: Optional. Extra inis (comma-separated) to read to find the data for this game. +; All data will be looked up in these files first, and only then in this base file. +; Note that each section name will be read only from the first-found occurrence. +; FilesSection: Required. Name of the ini section to read to find this game's files. +; Theaters: Comma-separated list of theater info, usable as extra arguments in the file names. +; Each info block is a colon-separated list, normally containing +; "fullname:filename:extension" Adding more than these three items is possible. +; ModTheaters: Secondary list of theaters. This list is handled after all filenames are generated +; with the standard theaters, because id collisions on them are treated as less +; important. This second iteration of all files only handles files whose type uses +; the theater info. +; Hasher: Hashing algorithm used to encode all filenames used by this game. +; Choices are ROL1, ROL3, CRC32, ROL, Obscure, Obfuscate. Defaults to ROL1. +; NewMixFormat: Default is false. True if this game can use the newer mix format with encryption. +; HasMixNesting: Default: false. True if mix archives of this game can occur inside mix archives. +; This will make detection scans go inside any deeper mix archives to determine +; which game the mix file is for. [TiberianDawn] +ContentIni=mixcontent_td.ini +FileTypes=GeneralTypes,TypesTd FilesSection=FilesTd -Theaters=des:desert,tem:temperat,win:winter -ModTheaters=jun:jungle,sno:snow,car:caribbea +Theaters=Desert:desert:des,Temperate:temperat:tem,Winter:winter:win +ModTheaters=Jungle:jungle:jun,Snow:snow:sno,Caribbean:caribbea:car +Hasher=ROL1 +NewMixFormat=false +HasMixNesting=false [RedAlert] +ContentIni=mixcontent_ra1.ini +FileTypes=GeneralTypes,TypesRa FilesSection=FilesRa -Theaters=tem:temperat,sno:snow,int:interior -ModTheaters=win:winter,des:desert,jun:jungle,bar:barren,cav:cave +Theaters=Temperate:temperat:tem,Snow:snow:sno,Interior:interior:int +ModTheaters=Winter:winter:win,Desert:desert:des,Jungle:jungle:jun,Barren:barren:bar,Cave:cave:cav +Hasher=ROL1 +NewMixFormat=true +HasMixNesting=true [SoleSurvivor] +ContentIni=mixcontent_td.ini,mixcontent_sole.ini +FileTypes=GeneralTypes,TypesTd FilesSection=FilesSole -Theaters=des:desert,tem:temperat,win:winter +Theaters=Desert:desert:des,Temperate:temperat:tem,Winter:winter:win +Hasher=ROL1 +NewMixFormat=false +HasMixNesting=false -; All filetype formats. These are used to simplify and compact the files lists to common formats. -; Files that don't have multiple names generated from the same base name (such as animation .shp files) -; don't have any benefit from using these types, so single-extension types are not defined. +; Filetype formats. This system compacts the lists to entries generated from the same base name. +; Files that don't have multiple names generated from one base name (such as animation .shp files) +; don't have any benefit from using these types, so single-extension types are not defined here. ; Items in this list require consecutive numbers starting from 0. -[FileTypes] +[GeneralTypes] 0=TheaterExt ; Any file with a theater extension; e.g. "cr1" -> "cr1.tem" -1=TheaterFile ; Main palette / mixfile for a theater; give just the extension as filename. -2=FadingTable ; TD fading tables; get a single prefix letter derived from the theater name, and a .mrf extension. -3=Video ; vqa/vqp pairs. -4=ThemeVar ; Themes that have .var remix variants. -5=AudioJuv ; Audio files that have .juv juvenile variants. -6=MissionTd ; All TD mission formats. -7=BuildingTd ; TD building, potentially theater sensitive, with build-up. -8=BuildableTd ; TD buildable object without special logic or extra files: vehicles, walls. -9=SuperTd ; Superweapons are only an icon and theater-specific icon, but no basic shp file. -10=InfantryTd ; TD infantry that has extra ??rot.shp anims. -11=AudioVarTd ; Audio varying for units (v00/v01 etc) -12=MissionRa ; All RA mission formats. -14=BuildableRa ; RA buildable; generates a basic ????.shp and an ????icon.shp -13=AudioVarRa ; Audio varying for units (v00/v01/r00/r01 etc) +1=TheaterFile ; Main file for a theater, with the given part as extension, e.g. "pal" -> "temperat.pal" +2=Video320 ; 320x200 video, expanded to a vqa/vqp pair. -; Type definitions +; Type definitions. ; general rules: -; -Lines have consecutive keys starting from 0. +; - Lines have consecutive keys starting from 0. ; - Blocks with "[]" iterate over the items inside. The items to iterate are either ; single characters, or blocks inside ( ) brackets. ; - {0} is the basic filename. This can be empty. -; - {1} is the first theater data; normally the theater extension without the dot. (e.g. "tem") +; - {1} is the first theater data; normally the full theater name (e.g. "Temperate") ; - {2} is the second theater data; normally the theater base name (e.g. "temperat") +; - {3} is the third theater data; normally the theater extension without the dot. (e.g. "tem") ; Further theater data arguments ({3}, {4}, etc.) are possible if they are added to the theaters. -; If no arguments higher than 0 are present, no iteration over theaters will happen. +; - Extra info can be added per line as "0Info=". The {0}/{1}/etc arguments can also be used in this, but +; iteration can't. If the filename contains info already, this is put behind it with a space in between. +; If no arguments higher than 0 are present in the basic name string, no iteration over theaters will happen. ; Custom sub-string formatting is supported: ; - A string like {2:1} uses the added number (in this case, "1") as cutoff length. ; - More detailed substring cutting can be done with {2:1-3}, specifying a start and end index. -[MissionTd] -; Standard campaigns. Just roughly going over 01-19, with only A-B-C suffixes. -; The cps files are for japanese briefings. -0=sc[gb]0[123456789][ew][abc].[(ini)(bin)(cps)] -1=sc[gb]1[0123456789][ew][abc].[(ini)(bin)(cps)] -; Dino campaign. -2=scj0[12345]ea.[(ini)(bin)(cps)] -; Addon missions start range 20-99, with minicampaign support. -3=sc[gb][23456789][0123456789][ewx][abcde].[(ini)(bin)(cps)] -; Middle range 100-899 unusable for minicampaigns. -4=sc[gb][12345678][0123456789][0123456789]ea.[(ini)(bin)(cps)] -; 900-999 minicampaigns-capable range. -5=sc[gb]9[0123456789][0123456789][ewx][abcde].[(ini)(bin)(cps)] -; Multiplay -6=scm[0123456789][0123456789]ea.[(ini)(bin)] -7=scm[123456789][0123456789][0123456789]ea.[(ini)(bin)] - -[BuildingTd] -0={0}.shp -1={0:4}make.shp -2={0:4}icon.shp -3={0}.{1} -4={0:4}icnh.{1} -; Theater-specific build-up animation. Normally not used in TD. -5={0:4}make.{1} - -[BuildableTd] -0={0}.shp -1={0:4}icon.shp -2={0:4}icnh.{1} - -[SuperTd] -0={0:4}icon.shp -1={0:4}icnh.{1} - -[InfantryTd] -0={0}.shp -1={0}rot.shp -2={0:4}icon.shp -3={0:4}icnh.{1} - [TheaterExt] -0={0}.{1} +0={0}.{3} +0Info=({1}) [TheaterFile] 0={2}.{0} -[FadingTable] -0={2:1}{0}.mrf - -[Video] +[Video320] 0={0}.vqa +0Info=Video 1={0}.vqp +1Info=Video stretch table -[ThemeVar] -0={0}.aud -1={0}.var - -[AudioJuv] -0={0}.aud -1={0}.juv - -[AudioVarTd] -0={0}.v00 -1={0}.v01 -2={0}.v01 -3={0}.v03 - -[MissionRa] -; Standard campaigns. Just roughly going over 01-19, with only EA-EB-EC suffixes. -0=sc[gu]0[123456789]e[abc].ini -1=sc[gu]1[0123456789]e[abc].ini -; Ants campaign. -2=sca0[1234]ea.ini -; Addon missions start range 20-99 -3=sc[gu][23456789][0123456789]ea.ini -; Multiplay -4=scm[0123456789][0123456789]ea.ini -; Aftermath maps -5=scm[defghijklm][0123456789]ea.ini - -[BuildableRa] -0={0}.shp -1={0:4}icon.shp - -[AudioVarRa] -0={0}.v00 -1={0}.v01 -2={0}.v02 -3={0}.v03 -4={0}.r00 -5={0}.r01 -6={0}.r02 -7={0}.r03 - -; Files sections for all games - -; Files. Format is "NAME,HASH,TYPE=1". Content behind = is irrelevant but needed for ini format. -; This format is used because keys need to be unique and 0=, 1= etc are awkward when editing, -; and a definition should not appear in the same section twice anyway. -;The text behind the "=" could be used as description I suppose? +; Files sections for all games: migrated to their respective inis. +; Files. Format is "NAME,TYPE=DESC". ; NAME: the filename part that becomes {0} in the type definition. -; HASH: Hashing algorithm to use. Can be ROL1, ROL3, CRC32, ROL, Obscure, Obfuscate. ; TYPE: One of the formats listed in FileTypes to produce multiple filenames from one input. ; This is optional; if not given, the full name is used unmodified. -[FilesTd] -;Generates all mission names. The actual given filename is empty; it's all generated by the iterator. -,ROL1,MissionTd=1 -; Generates all theater palettes. -pal,ROL1,TheaterFile=1 -; Templates; simple theater-iterating entries. -clear1,ROL1,TheaterExt=1 -w1,ROL1,TheaterExt=1 -w2,ROL1,TheaterExt=1 -sh1,ROL1,TheaterExt=1 -sh2,ROL1,TheaterExt=1 -sh3,ROL1,TheaterExt=1 -sh4,ROL1,TheaterExt=1 -sh5,ROL1,TheaterExt=1 -sh11,ROL1,TheaterExt=1 -sh12,ROL1,TheaterExt=1 -sh13,ROL1,TheaterExt=1 -sh14,ROL1,TheaterExt=1 -sh15,ROL1,TheaterExt=1 -s01,ROL1,TheaterExt=1 -s02,ROL1,TheaterExt=1 -s03,ROL1,TheaterExt=1 -s04,ROL1,TheaterExt=1 -s05,ROL1,TheaterExt=1 -s06,ROL1,TheaterExt=1 -s07,ROL1,TheaterExt=1 -s08,ROL1,TheaterExt=1 -s09,ROL1,TheaterExt=1 -s10,ROL1,TheaterExt=1 -s11,ROL1,TheaterExt=1 -s12,ROL1,TheaterExt=1 -s13,ROL1,TheaterExt=1 -s14,ROL1,TheaterExt=1 -s15,ROL1,TheaterExt=1 -s16,ROL1,TheaterExt=1 -s17,ROL1,TheaterExt=1 -s18,ROL1,TheaterExt=1 -s19,ROL1,TheaterExt=1 -s20,ROL1,TheaterExt=1 -s21,ROL1,TheaterExt=1 -s22,ROL1,TheaterExt=1 -s23,ROL1,TheaterExt=1 -s24,ROL1,TheaterExt=1 -s25,ROL1,TheaterExt=1 -s26,ROL1,TheaterExt=1 -s27,ROL1,TheaterExt=1 -s28,ROL1,TheaterExt=1 -s29,ROL1,TheaterExt=1 -s30,ROL1,TheaterExt=1 -s31,ROL1,TheaterExt=1 -s32,ROL1,TheaterExt=1 -s33,ROL1,TheaterExt=1 -s34,ROL1,TheaterExt=1 -s35,ROL1,TheaterExt=1 -s36,ROL1,TheaterExt=1 -s37,ROL1,TheaterExt=1 -s38,ROL1,TheaterExt=1 -sh32,ROL1,TheaterExt=1 -sh33,ROL1,TheaterExt=1 -sh20,ROL1,TheaterExt=1 -sh21,ROL1,TheaterExt=1 -sh22,ROL1,TheaterExt=1 -sh23,ROL1,TheaterExt=1 -br1,ROL1,TheaterExt=1 -br2,ROL1,TheaterExt=1 -br3,ROL1,TheaterExt=1 -br4,ROL1,TheaterExt=1 -br5,ROL1,TheaterExt=1 -br6,ROL1,TheaterExt=1 -br7,ROL1,TheaterExt=1 -br8,ROL1,TheaterExt=1 -br9,ROL1,TheaterExt=1 -br10,ROL1,TheaterExt=1 -p01,ROL1,TheaterExt=1 -p02,ROL1,TheaterExt=1 -p03,ROL1,TheaterExt=1 -p04,ROL1,TheaterExt=1 -p05,ROL1,TheaterExt=1 -p06,ROL1,TheaterExt=1 -p07,ROL1,TheaterExt=1 -p08,ROL1,TheaterExt=1 -sh16,ROL1,TheaterExt=1 -sh17,ROL1,TheaterExt=1 -sh18,ROL1,TheaterExt=1 -sh19,ROL1,TheaterExt=1 -p13,ROL1,TheaterExt=1 -p14,ROL1,TheaterExt=1 -p15,ROL1,TheaterExt=1 -b1,ROL1,TheaterExt=1 -b2,ROL1,TheaterExt=1 -b3,ROL1,TheaterExt=1 -b4,ROL1,TheaterExt=1 -b5,ROL1,TheaterExt=1 -b6,ROL1,TheaterExt=1 -sh6,ROL1,TheaterExt=1 -sh7,ROL1,TheaterExt=1 -sh8,ROL1,TheaterExt=1 -sh9,ROL1,TheaterExt=1 -sh10,ROL1,TheaterExt=1 -d01,ROL1,TheaterExt=1 -d02,ROL1,TheaterExt=1 -d03,ROL1,TheaterExt=1 -d04,ROL1,TheaterExt=1 -d05,ROL1,TheaterExt=1 -d06,ROL1,TheaterExt=1 -d07,ROL1,TheaterExt=1 -d08,ROL1,TheaterExt=1 -d09,ROL1,TheaterExt=1 -d10,ROL1,TheaterExt=1 -d11,ROL1,TheaterExt=1 -d12,ROL1,TheaterExt=1 -d13,ROL1,TheaterExt=1 -d14,ROL1,TheaterExt=1 -d15,ROL1,TheaterExt=1 -d16,ROL1,TheaterExt=1 -d17,ROL1,TheaterExt=1 -d18,ROL1,TheaterExt=1 -d19,ROL1,TheaterExt=1 -d20,ROL1,TheaterExt=1 -d21,ROL1,TheaterExt=1 -d22,ROL1,TheaterExt=1 -d23,ROL1,TheaterExt=1 -d24,ROL1,TheaterExt=1 -d25,ROL1,TheaterExt=1 -d26,ROL1,TheaterExt=1 -d27,ROL1,TheaterExt=1 -d28,ROL1,TheaterExt=1 -d29,ROL1,TheaterExt=1 -d30,ROL1,TheaterExt=1 -d31,ROL1,TheaterExt=1 -d32,ROL1,TheaterExt=1 -d33,ROL1,TheaterExt=1 -d34,ROL1,TheaterExt=1 -d35,ROL1,TheaterExt=1 -d36,ROL1,TheaterExt=1 -d37,ROL1,TheaterExt=1 -d38,ROL1,TheaterExt=1 -d39,ROL1,TheaterExt=1 -d40,ROL1,TheaterExt=1 -d41,ROL1,TheaterExt=1 -d42,ROL1,TheaterExt=1 -d43,ROL1,TheaterExt=1 -rv01,ROL1,TheaterExt=1 -rv02,ROL1,TheaterExt=1 -rv03,ROL1,TheaterExt=1 -rv04,ROL1,TheaterExt=1 -rv05,ROL1,TheaterExt=1 -rv06,ROL1,TheaterExt=1 -rv07,ROL1,TheaterExt=1 -rv08,ROL1,TheaterExt=1 -rv09,ROL1,TheaterExt=1 -rv10,ROL1,TheaterExt=1 -rv11,ROL1,TheaterExt=1 -rv12,ROL1,TheaterExt=1 -rv13,ROL1,TheaterExt=1 -rv14,ROL1,TheaterExt=1 -rv15,ROL1,TheaterExt=1 -rv16,ROL1,TheaterExt=1 -rv17,ROL1,TheaterExt=1 -rv18,ROL1,TheaterExt=1 -rv19,ROL1,TheaterExt=1 -rv20,ROL1,TheaterExt=1 -rv21,ROL1,TheaterExt=1 -rv22,ROL1,TheaterExt=1 -rv23,ROL1,TheaterExt=1 -rv24,ROL1,TheaterExt=1 -rv25,ROL1,TheaterExt=1 -ford1,ROL1,TheaterExt=1 -ford2,ROL1,TheaterExt=1 -falls1,ROL1,TheaterExt=1 -falls2,ROL1,TheaterExt=1 -bridge1,ROL1,TheaterExt=1 -bridge1d,ROL1,TheaterExt=1 -bridge2,ROL1,TheaterExt=1 -bridge2d,ROL1,TheaterExt=1 -bridge3,ROL1,TheaterExt=1 -bridge3d,ROL1,TheaterExt=1 -bridge4,ROL1,TheaterExt=1 -bridge4d,ROL1,TheaterExt=1 -sh24,ROL1,TheaterExt=1 -sh25,ROL1,TheaterExt=1 -sh26,ROL1,TheaterExt=1 -sh27,ROL1,TheaterExt=1 -sh28,ROL1,TheaterExt=1 -sh29,ROL1,TheaterExt=1 -sh30,ROL1,TheaterExt=1 -sh31,ROL1,TheaterExt=1 -p16,ROL1,TheaterExt=1 -p17,ROL1,TheaterExt=1 -p18,ROL1,TheaterExt=1 -p19,ROL1,TheaterExt=1 -p20,ROL1,TheaterExt=1 -sh34,ROL1,TheaterExt=1 -sh35,ROL1,TheaterExt=1 -sh36,ROL1,TheaterExt=1 -sh37,ROL1,TheaterExt=1 -sh38,ROL1,TheaterExt=1 -sh39,ROL1,TheaterExt=1 -sh40,ROL1,TheaterExt=1 -sh41,ROL1,TheaterExt=1 -sh42,ROL1,TheaterExt=1 -sh43,ROL1,TheaterExt=1 -sh44,ROL1,TheaterExt=1 -sh45,ROL1,TheaterExt=1 -sh46,ROL1,TheaterExt=1 -sh47,ROL1,TheaterExt=1 -sh48,ROL1,TheaterExt=1 -sh49,ROL1,TheaterExt=1 -sh50,ROL1,TheaterExt=1 -sh51,ROL1,TheaterExt=1 -sh52,ROL1,TheaterExt=1 -sh53,ROL1,TheaterExt=1 -sh54,ROL1,TheaterExt=1 -sh55,ROL1,TheaterExt=1 -sh56,ROL1,TheaterExt=1 -sh57,ROL1,TheaterExt=1 -sh58,ROL1,TheaterExt=1 -sh59,ROL1,TheaterExt=1 -sh60,ROL1,TheaterExt=1 -sh61,ROL1,TheaterExt=1 -sh62,ROL1,TheaterExt=1 -sh63,ROL1,TheaterExt=1 -; no theater extensions; these only exist for Temperate. -sr1.tem,ROL1=1 -sr2.tem,ROL1=1 -; buildings -weap,ROL1,BuildingTd=1 -gtwr,ROL1,BuildingTd=1 -atwr,ROL1,BuildingTd=1 -obli,ROL1,BuildingTd=1 -hq,ROL1,BuildingTd=1 -gun,ROL1,BuildingTd=1 -fact,ROL1,BuildingTd=1 -proc,ROL1,BuildingTd=1 -silo,ROL1,BuildingTd=1 -hpad,ROL1,BuildingTd=1 -sam,ROL1,BuildingTd=1 -afld,ROL1,BuildingTd=1 -nuke,ROL1,BuildingTd=1 -nuk2,ROL1,BuildingTd=1 -hosp,ROL1,BuildingTd=1 -pyle,ROL1,BuildingTd=1 -arco,ROL1,BuildingTd=1 -fix,ROL1,BuildingTd=1 -bio,ROL1,BuildingTd=1 -hand,ROL1,BuildingTd=1 -tmpl,ROL1,BuildingTd=1 -eye,ROL1,BuildingTd=1 -miss,ROL1,BuildingTd=1 -v01,ROL1,BuildingTd=1 -v02,ROL1,BuildingTd=1 -v03,ROL1,BuildingTd=1 -v04,ROL1,BuildingTd=1 -v05,ROL1,BuildingTd=1 -v06,ROL1,BuildingTd=1 -v07,ROL1,BuildingTd=1 -v08,ROL1,BuildingTd=1 -v09,ROL1,BuildingTd=1 -v10,ROL1,BuildingTd=1 -v11,ROL1,BuildingTd=1 -v12,ROL1,BuildingTd=1 -v13,ROL1,BuildingTd=1 -v14,ROL1,BuildingTd=1 -v15,ROL1,BuildingTd=1 -v16,ROL1,BuildingTd=1 -v17,ROL1,BuildingTd=1 -v18,ROL1,BuildingTd=1 -v19,ROL1,BuildingTd=1 -v20,ROL1,BuildingTd=1 -v21,ROL1,BuildingTd=1 -v22,ROL1,BuildingTd=1 -v23,ROL1,BuildingTd=1 -v24,ROL1,BuildingTd=1 -v25,ROL1,BuildingTd=1 -v26,ROL1,BuildingTd=1 -v27,ROL1,BuildingTd=1 -v28,ROL1,BuildingTd=1 -v29,ROL1,BuildingTd=1 -v30,ROL1,BuildingTd=1 -v31,ROL1,BuildingTd=1 -v32,ROL1,BuildingTd=1 -v33,ROL1,BuildingTd=1 -v34,ROL1,BuildingTd=1 -v35,ROL1,BuildingTd=1 -v36,ROL1,BuildingTd=1 -v37,ROL1,BuildingTd=1 -sbag,ROL1,BuildingTd=1 -cycl,ROL1,BuildingTd=1 -brik,ROL1,BuildingTd=1 -barb,ROL1,BuildingTd=1 -wood,ROL1,BuildingTd=1 -; Superweapons -ion,ROL1,SuperTd=1 -atom,ROL1,SuperTd=1 -bomb,ROL1,SuperTd=1 -; Smudge -cr1,ROL1,TheaterExt=1 -cr2,ROL1,TheaterExt=1 -cr3,ROL1,TheaterExt=1 -cr4,ROL1,TheaterExt=1 -cr5,ROL1,TheaterExt=1 -cr6,ROL1,TheaterExt=1 -sc1,ROL1,TheaterExt=1 -sc2,ROL1,TheaterExt=1 -sc3,ROL1,TheaterExt=1 -sc4,ROL1,TheaterExt=1 -sc5,ROL1,TheaterExt=1 -sc6,ROL1,TheaterExt=1 -bib1,ROL1,TheaterExt=1 -bib2,ROL1,TheaterExt=1 -bib3,ROL1,TheaterExt=1 -; Terrain -t01,ROL1,TheaterExt=1 -t02,ROL1,TheaterExt=1 -t03,ROL1,TheaterExt=1 -t04,ROL1,TheaterExt=1 -t05,ROL1,TheaterExt=1 -t06,ROL1,TheaterExt=1 -t07,ROL1,TheaterExt=1 -t08,ROL1,TheaterExt=1 -t09,ROL1,TheaterExt=1 -t10,ROL1,TheaterExt=1 -t11,ROL1,TheaterExt=1 -t12,ROL1,TheaterExt=1 -t13,ROL1,TheaterExt=1 -t14,ROL1,TheaterExt=1 -t15,ROL1,TheaterExt=1 -t16,ROL1,TheaterExt=1 -t17,ROL1,TheaterExt=1 -t18,ROL1,TheaterExt=1 -split2,ROL1,TheaterExt=1 -split3,ROL1,TheaterExt=1 -tc01,ROL1,TheaterExt=1 -tc02,ROL1,TheaterExt=1 -tc03,ROL1,TheaterExt=1 -tc04,ROL1,TheaterExt=1 -tc05,ROL1,TheaterExt=1 -rock1,ROL1,TheaterExt=1 -rock2,ROL1,TheaterExt=1 -rock3,ROL1,TheaterExt=1 -rock4,ROL1,TheaterExt=1 -rock5,ROL1,TheaterExt=1 -rock6,ROL1,TheaterExt=1 -rock7,ROL1,TheaterExt=1 -; infantry -e1,ROL1,InfantryTd=1 ; These have "rot" animations. -e2,ROL1,InfantryTd=1 ; These have "rot" animations. -e3,ROL1,InfantryTd=1 ; These have "rot" animations. -e4,ROL1,InfantryTd=1 ; These have "rot" animations. -e5,ROL1,BuildableTd=1 -e6,ROL1,BuildableTd=1 -rmbo,ROL1,BuildableTd=1 -c1,ROL1,BuildableTd=1 -c2,ROL1,BuildableTd=1 -c3,ROL1,BuildableTd=1 -c4,ROL1,BuildableTd=1 -c5,ROL1,BuildableTd=1 -c6,ROL1,BuildableTd=1 -c7,ROL1,BuildableTd=1 -c8,ROL1,BuildableTd=1 -c9,ROL1,BuildableTd=1 -c10,ROL1,BuildableTd=1 -moebius,ROL1,BuildableTd=1 -delphi,ROL1,BuildableTd=1 -chan,ROL1,BuildableTd=1 -; Units -htnk,ROL1,BuildableTd=1 -mtnk,ROL1,BuildableTd=1 -ltnk,ROL1,BuildableTd=1 -stnk,ROL1,BuildableTd=1 -ftnk,ROL1,BuildableTd=1 -vice,ROL1,BuildableTd=1 -apc,ROL1,BuildableTd=1 -msam,ROL1,BuildableTd=1 -jeep,ROL1,BuildableTd=1 -bggy,ROL1,BuildableTd=1 -harv,ROL1,BuildableTd=1 -arty,ROL1,BuildableTd=1 -mlrs,ROL1,BuildableTd=1 -lst,ROL1,BuildableTd=1 -mhq,ROL1,BuildableTd=1 -boat,ROL1,BuildableTd=1 -mcv,ROL1,BuildableTd=1 -bike,ROL1,BuildableTd=1 -tric,ROL1,BuildableTd=1 -trex,ROL1,BuildableTd=1 -rapt,ROL1,BuildableTd=1 -steg,ROL1,BuildableTd=1 -; Aircraft -tran,ROL1,BuildableTd=1 -a10,ROL1,BuildableTd=1 -heli,ROL1,BuildableTd=1 -c17,ROL1,BuildableTd=1 -orca,ROL1,BuildableTd=1 -; Overlay: tiberium -ti1,ROL1,TheaterExt=1 -ti2,ROL1,TheaterExt=1 -ti3,ROL1,TheaterExt=1 -ti4,ROL1,TheaterExt=1 -ti5,ROL1,TheaterExt=1 -ti6,ROL1,TheaterExt=1 -ti7,ROL1,TheaterExt=1 -ti8,ROL1,TheaterExt=1 -ti9,ROL1,TheaterExt=1 -ti10,ROL1,TheaterExt=1 -ti11,ROL1,TheaterExt=1 -ti12,ROL1,TheaterExt=1 -; Overlay: pavements and crates. -road.shp,ROL1=1 -squish.shp,ROL1=1 -fpls.shp,ROL1=1 -wcrate.shp,ROL1=1 -scrate.shp,ROL1=1 -; animations -samfire.shp,ROL1=1 -smokland.shp,ROL1=1 -; Flammable object burning animations. Primarily used on trees and buildings. -burn-s.shp,ROL1=1 -burn-m.shp,ROL1=1 -burn-l.shp,ROL1=1 -; Flame thrower animations. These are direction specific. -flame-n.shp,ROL1=1 -flame-nw.shp,ROL1=1 -flame-w.shp,ROL1=1 -flame-sw.shp,ROL1=1 -flame-s.shp,ROL1=1 -flame-se.shp,ROL1=1 -flame-e.shp,ROL1=1 -flame-ne.shp,ROL1=1 -; Chem sprayer animations. These are direction specific. -chem-n.shp,ROL1=1 -chem-nw.shp,ROL1=1 -chem-w.shp,ROL1=1 -chem-sw.shp,ROL1=1 -chem-s.shp,ROL1=1 -chem-se.shp,ROL1=1 -chem-e.shp,ROL1=1 -chem-ne.shp,ROL1=1 -veh-hit2.shp,ROL1=1 -fball1.shp,ROL1=1 -frag1.shp,ROL1=1 -frag3.shp,ROL1=1 -veh-hit1.shp,ROL1=1 -veh-hit2.shp,ROL1=1 -veh-hit3.shp,ROL1=1 -art-exp1.shp,ROL1=1 -napalm1.shp,ROL1=1 -napalm2.shp,ROL1=1 -napalm3.shp,ROL1=1 -smokey.shp,ROL1=1 -piff.shp,ROL1=1 -piffpiff.shp,ROL1=1 -fire3.shp,ROL1=1 -fire1.shp,ROL1=1 -fire4.shp,ROL1=1 -fire2.shp,ROL1=1 -flmspt.shp,ROL1=1 -gunfire.shp,ROL1=1 -smoke_m.shp,ROL1=1 -; Mini-gun fire effect -- used by guard towers. -minigun.shp,ROL1=1 -; Superweapons -ionsfx.shp,ROL1=1 -atomsfx.shp,ROL1=1 -atomdoor.shp,ROL1=1 -; Crates -deviator.shp,ROL1=1 -dollar.shp,ROL1=1 -earth.shp,ROL1=1 -empulse.shp,ROL1=1 -invun.shp,ROL1=1 -mine.shp,ROL1=1 -rapid.shp,ROL1=1 -stealth2.shp,ROL1=1 -missile2.shp,ROL1=1 -; Movement flash -moveflsh.shp,ROL1=1 -chemball.shp,ROL1=1 -; Flag animation -flagfly.shp,ROL1=1 -; bullet type graphics -50cal.shp,ROL1=1 -120mm.shp,ROL1=1 -dragon.shp,ROL1=1 -missile.shp,ROL1=1 -flame.shp,ROL1=1 -bomblet.shp,ROL1=1 -bomb.shp,ROL1=1 -laser.shp,ROL1=1 ; Doesn't actually exist as graphics -atomicup.shp,ROL1=1 -atomicdn.shp,ROL1=1 -patriot.shp,ROL1=1 -gore.shp,ROL1=1 ; Doesn't actually exist as graphics -chew.shp,ROL1=1 ; Doesn't actually exist as graphics -; additional unit/building anims -select.shp,ROL1=1 ; Repairing animation -rrotor.shp,ROL1=1 -lrotor.shp,ROL1=1 -wake.shp,ROL1=1 -; shroud edges -shadow.shp,ROL1=1 -; UI shapes -clock.shp,ROL1=1 -pips.shp,ROL1=1 -power.shp,ROL1=1 -tabs.shp,ROL1=1 -strip.shp,ROL1=1 -stripup.shp,ROL1=1 -stripdn.shp,ROL1=1 -btn-pl.shp,ROL1=1 -btn-st.shp,ROL1=1 -btn-up.shp,ROL1=1 -btn-dn.shp,ROL1=1 -; Hi-res UI shapes -hclock.shp,ROL1=1 -hmap.shp,ROL1=1 -hmapf.shp,ROL1=1 ; French equivalent in C&C95 v1.06 -hmapg.shp,ROL1=1 ; German equivalent in C&C95 v1.06 -hsell.shp,ROL1=1 -hsellf.shp,ROL1=1 ; French equivalent in C&C95 v1.06 -hsellg.shp,ROL1=1 ; German equivalent in C&C95 v1.06 -hrepair.shp,ROL1=1 -hrepairf.shp,ROL1=1 ; French equivalent in C&C95 v1.06 -hrepairg.shp,ROL1=1 ; German equivalent in C&C95 v1.06 -hpips.shp,ROL1=1 -hpips_f.shp,ROL1=1 ; French equivalent in C&C95 v1.06 -hpips_g.shp,ROL1=1 ; German equivalent in C&C95 v1.06 -hside1.shp,ROL1=1 -hside2.shp,ROL1=1 -hpower.shp,ROL1=1 -hpwrbar.shp,ROL1=1 -htabs.shp,ROL1=1 -hstrip.shp,ROL1=1 -hstripup.shp,ROL1=1 -hstripdn.shp,ROL1=1 -btn-plh.shp,ROL1=1 -btn-sth.shp,ROL1=1 -hbtn-up.shp,ROL1=1 -hbtn-up2.shp,ROL1=1 -hbtn-dn.shp,ROL1=1 -hbtn-dn2.shp,ROL1=1 -options.shp,ROL1=1 ; Various decorations for options screens -btexture.shp,ROL1=1 -; Score screen anims -creds.shp,ROL1=1 -time.shp,ROL1=1 -logos.shp,ROL1=1 -hiscore1.shp,ROL1=1 -hiscore2.shp,ROL1=1 -bar3ylw.shp,ROL1=1 -bar3red.shp,ROL1=1 -; Fading tables -green,ROL1,FadingTable=1 -yellow,ROL1,FadingTable=1 -red,ROL1,FadingTable=1 -mouse,ROL1,FadingTable=1 -trans,ROL1,FadingTable=1 -white,ROL1,FadingTable=1 -shadow,ROL1,FadingTable=1 -units,ROL1,FadingTable=1 -shade,ROL1,FadingTable=1 -light,ROL1,FadingTable=1 -clock,ROL1,FadingTable=1 -; videos -airstrk,ROL1,Video=1 -akira,ROL1,Video=1 -banner,ROL1,Video=1 -banr_nod,ROL1,Video=1 -bcanyon,ROL1,Video=1 -bkground,ROL1,Video=1 -blackout,ROL1,Video=1 -bodybags,ROL1,Video=1 -bombaway,ROL1,Video=1 -bombflee,ROL1,Video=1 -burdet1,ROL1,Video=1 -burdet2,ROL1,Video=1 -cc2tease,ROL1,Video=1 -consyard,ROL1,Video=1 -desflees,ROL1,Video=1 -deskill,ROL1,Video=1 -desolat,ROL1,Video=1 -dessweep,ROL1,Video=1 -dino,ROL1,Video=1 -flag,ROL1,Video=1 -flyy,ROL1,Video=1 -forestkl,ROL1,Video=1 -gameover,ROL1,Video=1 -gdi1,ROL1,Video=1 -gdi2,ROL1,Video=1 -gdi3,ROL1,Video=1 -gdi4a,ROL1,Video=1 -gdi4b,ROL1,Video=1 -gdi5,ROL1,Video=1 -gdi6,ROL1,Video=1 -gdi7,ROL1,Video=1 -gdi8a,ROL1,Video=1 -gdi8b,ROL1,Video=1 -gdi9,ROL1,Video=1 -gdi10,ROL1,Video=1 -gdi11,ROL1,Video=1 -gdi12,ROL1,Video=1 -gdi13,ROL1,Video=1 -gdi14,ROL1,Video=1 -gdi15,ROL1,Video=1 -gdi3lose,ROL1,Video=1 -gdiend1,ROL1,Video=1 -gdiend2,ROL1,Video=1 -gdifina,ROL1,Video=1 -gdifinb,ROL1,Video=1 -gdilose,ROL1,Video=1 -generic,ROL1,Video=1 -gunboat,ROL1,Video=1 -hellvaly,ROL1,Video=1 -inferno,ROL1,Video=1 -insites,ROL1,Video=1 -intro2,ROL1,Video=1 -iontest,ROL1,Video=1 -kanepre,ROL1,Video=1 -landing,ROL1,Video=1 -logo,ROL1,Video=1 -napalm,ROL1,Video=1 -nitejump,ROL1,Video=1 -nod1,ROL1,Video=1 -nod2,ROL1,Video=1 -nod3,ROL1,Video=1 -nod4a,ROL1,Video=1 -nod4b,ROL1,Video=1 -nod5,ROL1,Video=1 -nod6,ROL1,Video=1 -nod7a,ROL1,Video=1 -nod7b,ROL1,Video=1 -nod8,ROL1,Video=1 -nod9,ROL1,Video=1 -nod10a,ROL1,Video=1 -nod10b,ROL1,Video=1 -nod11,ROL1,Video=1 -nod12,ROL1,Video=1 -nod13,ROL1,Video=1 -nod1pre,ROL1,Video=1 -nodend1,ROL1,Video=1 -nodend2,ROL1,Video=1 -nodend3,ROL1,Video=1 -nodend4,ROL1,Video=1 -nodfinal,ROL1,Video=1 -nodflees,ROL1,Video=1 -nodlose,ROL1,Video=1 -nodsweep,ROL1,Video=1 -nuke,ROL1,Video=1 -obel,ROL1,Video=1 -paratrop,ROL1,Video=1 -pintle,ROL1,Video=1 -planecra,ROL1,Video=1 -podium,ROL1,Video=1 -refint,ROL1,Video=1 -refinery,ROL1,Video=1 -retro,ROL1,Video=1 -sabotage,ROL1,Video=1 -samdie,ROL1,Video=1 -samsite,ROL1,Video=1 -seige,ROL1,Video=1 -sethpre,ROL1,Video=1 -sizzle,ROL1,Video=1 -sizzle2,ROL1,Video=1 -spycrash,ROL1,Video=1 -stealth,ROL1,Video=1 -sundial,ROL1,Video=1 -tankgo,ROL1,Video=1 -tankkill,ROL1,Video=1 -tbrinfo1,ROL1,Video=1 -tbrinfo2,ROL1,Video=1 -tbrinfo3,ROL1,Video=1 -tiberfx,ROL1,Video=1 -trailer,ROL1,Video=1 -trtkil_d,ROL1,Video=1 -turtkill,ROL1,Video=1 -visor,ROL1,Video=1 -; music -airstrik,ROL1,ThemeVar=1 -80mx226m,ROL1,ThemeVar=1 -chrg226m,ROL1,ThemeVar=1 -crep226m,ROL1,ThemeVar=1 -dril226m,ROL1,ThemeVar=1 -dron226m,ROL1,ThemeVar=1 -fist226m,ROL1,ThemeVar=1 -recn226m,ROL1,ThemeVar=1 -voic226m,ROL1,ThemeVar=1 -heavyg,ROL1,ThemeVar=1 -j1,ROL1,ThemeVar=1 -jdi_v2,ROL1,ThemeVar=1 -radio,ROL1,ThemeVar=1 -rain,ROL1,ThemeVar=1 -aoi,ROL1,ThemeVar=1 -ccthang,ROL1,ThemeVar=1 -die,ROL1,ThemeVar=1 -fwp,ROL1,ThemeVar=1 -ind,ROL1,ThemeVar=1 -ind2,ROL1,ThemeVar=1 -justdoit,ROL1,ThemeVar=1 -linefire,ROL1,ThemeVar=1 -march,ROL1,ThemeVar=1 -target,ROL1,ThemeVar=1 -nomercy,ROL1,ThemeVar=1 -otp,ROL1,ThemeVar=1 -prp,ROL1,ThemeVar=1 -rout,ROL1,ThemeVar=1 -heart,ROL1,ThemeVar=1 -stopthem,ROL1,ThemeVar=1 -trouble,ROL1,ThemeVar=1 -warfare,ROL1,ThemeVar=1 -befeared,ROL1,ThemeVar=1 -i_am,ROL1,ThemeVar=1 -win1,ROL1,ThemeVar=1 -map1,ROL1,ThemeVar=1 -valkyrie,ROL1,ThemeVar=1 -nod_win1,ROL1,ThemeVar=1 -nod_map1,ROL1,ThemeVar=1 -outtakes,ROL1,ThemeVar=1 -; WSA animations -choose.wsa,ROL1=1 -greyerth.wsa,ROL1=1 -e-bwtocl.wsa,ROL1=1 -earth_e.wsa,ROL1=1 -earth_a.wsa,ROL1=1 -europe.wsa,ROL1=1 -bosnia.wsa,ROL1=1 -africa.wsa,ROL1=1 -s_africa.wsa,ROL1=1 -hearth_e.wsa,ROL1=1 -hearth_a.wsa,ROL1=1 -hbosnia.wsa,ROL1=1 -hsafrica.wsa,ROL1=1 -s-gdiin2.wsa,ROL1=1 -scrscn1.wsa,ROL1=1 -mltiplyr.wsa,ROL1=1 -mltsceng.wsa,ROL1=1 ; Added by the v1.06 patch -mltscfre.wsa,ROL1=1 ; Added by the v1.06 patch -mltscger.wsa,ROL1=1 ; Added by the v1.06 patch -; Sound files -struggle.aud,ROL1=1 ; Intro static -loopie6m.aud,ROL1=1 ; Nod end background loop -; Special voices (typically associated with the commando). -bombit1.aud,ROL1=1 ; VOC_RAMBO_PRESENT "I've got a present for ya" -cmon1.aud,ROL1=1 ; VOC_RAMBO_CMON "c'mon" -gotit1.aud,ROL1=1 ; VOC_RAMBO_UGOTIT "you got it" -keepem1.aud,ROL1=1 ; VOC_RAMBO_COMIN "keep 'em commin'" -laugh1.aud,ROL1=1 ; VOC_RAMBO_LAUGH "hahaha" -lefty1.aud,ROL1=1 ; VOC_RAMBO_LEFTY "that was left handed" -noprblm1.aud,ROL1=1 ; VOC_RAMBO_NOPROB "no problem" -ohsh1.aud,ROL1=1 ; VOC_RAMBO_OHSH "oh shiiiiii...." -onit1.aud,ROL1=1 ; VOC_RAMBO_ONIT "I'm on it" -ramyell1.aud,ROL1=1 ; VOC_RAMBO_YELL "ahhhhhhh" -rokroll1.aud,ROL1=1 ; VOC_RAMBO_ROCK "time to rock and roll" -tuffguy1.aud,ROL1=1 ; VOC_RAMBO_TUFF "real tuff guy" -yeah1.aud,ROL1=1 ; VOC_RAMBO_YEA "yea" -yes1.aud,ROL1=1 ; VOC_RAMBO_YES "yes" -yo1.aud,ROL1=1 ; VOC_RAMBO_YO "yo" -; Civilian voices (technicians too). -girlokay.aud,ROL1=1 ; VOC_GIRL_OKAY "Okay" -girlyeah.aud,ROL1=1 ; VOC_GIRL_YEAH "Yeah?" -guyokay1.aud,ROL1=1 ; VOC_GUY_OKAY "Okay" -guyyeah1.aud,ROL1=1 ; VOC_GUY_YEAH "Yeah?" -; Sound effects that have a juvenile counterpart. -bazook1.aud,ROL1=1 ; VOC_BAZOOKA Gunfire -bleep2.aud,ROL1=1 ; VOC_BLEEP Clean metal bing -bomb1.aud,ROL1=1 ; VOC_BOMB1 Crunchy parachute bomb type explosion -button.aud,ROL1=1 ; VOC_BUTTON Dungeon Master button click -comcntr1.aud,ROL1=1 ; VOC_RADAR_ON Elecronic static with beeps -constru2.aud,ROL1=1 ; VOC_CONSTRUCTION construction sounds -crumble.aud,ROL1=1 ; VOC_CRUMBLE muffled crumble sound -flamer2.aud,ROL1=1 ; VOC_FLAMER1 flame thrower -gun18.aud,ROL1=1 ; VOC_RIFLE rifle shot -gun19.aud,ROL1=1 ; VOC_M60 machine gun burst -- 6 rounds -gun20.aud,ROL1=1 ; VOC_GUN20 bat hitting heavy metal door -gun5.aud,ROL1=1 ; VOC_M60A medium machine gun burst -gun8.aud,ROL1=1 ; VOC_MINI mini gun burst -gunclip1.aud,ROL1=1 ; VOC_RELOAD gun clip reload -hvydoor1.aud,ROL1=1 ; VOC_SLAM metal plates slamming together -hvygun10.aud,ROL1=1 ; VOC_HVYGUN10 loud sharp cannon -ion1.aud,ROL1=1 ; VOC_ION_CANNON partical beam -mgun11.aud,ROL1=1 ; VOC_MGUN11 alternate tripple burst -mgun2.aud,ROL1=1 ; VOC_MGUN2 M-16 tripple burst -nukemisl.aud,ROL1=1 ; VOC_NUKE_FIRE long missile sound -nukexplo.aud,ROL1=1 ; VOC_NUKE_EXPLODE long but not loud explosion -obelray1.aud,ROL1=1 ; VOC_LASER humming laser beam -obelpowr.aud,ROL1=1 ; VOC_LASER_POWER warming-up sound of laser beam -powrdn1.aud,ROL1=1 ; VOC_RADAR_OFF doom door slide -ramgun2.aud,ROL1=1 ; VOC_SNIPER silenced rifle fire -rocket1.aud,ROL1=1 ; VOC_ROCKET1 rocket launch variation #1 -rocket2.aud,ROL1=1 ; VOC_ROCKET2 rocket launch variation #2 -sammotr2.aud,ROL1=1 ; VOC_MOTOR dentists drill -scold2.aud,ROL1=1 ; VOC_SCOLD cannot perform action feedback tone -sidbar1c.aud,ROL1=1 ; VOC_SIDEBAR_OPEN xylophone clink -sidbar2c.aud,ROL1=1 ; VOC_SIDEBAR_CLOSE xylophone clink -squish2.aud,ROL1=1 ; VOC_SQUISH2 crushing infantry -tnkfire2.aud,ROL1=1 ; VOC_TANK1 sharp tank fire with recoil -tnkfire3.aud,ROL1=1 ; VOC_TANK2 sharp tank fire -tnkfire4.aud,ROL1=1 ; VOC_TANK3 sharp tank fire -tnkfire6.aud,ROL1=1 ; VOC_TANK4 big gun tank fire -tone15.aud,ROL1=1 ; VOC_UP credits counting up -tone16.aud,ROL1=1 ; VOC_DOWN credits counting down -tone2.aud,ROL1=1 ; VOC_TARGET target sound -tone5.aud,ROL1=1 ; VOC_SONAR sonar echo -toss.aud,ROL1=1 ; VOC_TOSS air swish -trans1.aud,ROL1=1 ; VOC_CLOAK stealth tank -treebrn1.aud,ROL1=1 ; VOC_BURN burning crackle -turrfir5.aud,ROL1=1 ; VOC_TURRET muffled gunfire -xplobig4.aud,ROL1=1 ; VOC_XPLOBIG4 very long muffled explosion -xplobig6.aud,ROL1=1 ; VOC_XPLOBIG6 very long muffled explosion -xplobig7.aud,ROL1=1 ; VOC_XPLOBIG7 very long muffled explosion -xplode.aud,ROL1=1 ; VOC_XPLODE long soft muffled explosion -xplos.aud,ROL1=1 ; VOC_XPLOS short crunchy explosion -xplosml2.aud,ROL1=1 ; VOC_XPLOSML2 muffled mechanical explosion -; Generic sound effects (no variations). -nuyell1.aud,ROL1=1 ; VOC_SCREAM1 short infantry scream -nuyell3.aud,ROL1=1 ; VOC_SCREAM3 short infantry scream -nuyell4.aud,ROL1=1 ; VOC_SCREAM4 short infantry scream -nuyell5.aud,ROL1=1 ; VOC_SCREAM5 short infantry scream -nuyell6.aud,ROL1=1 ; VOC_SCREAM6 short infantry scream -nuyell7.aud,ROL1=1 ; VOC_SCREAM7 short infantry scream -nuyell10.aud,ROL1=1 ; VOC_SCREAM10 short infantry scream -nuyell11.aud,ROL1=1 ; VOC_SCREAM11 short infantry scream -nuyell12.aud,ROL1=1 ; VOC_SCREAM12 short infantry scream -yell1.aud,ROL1=1 ; VOC_YELL1 long infantry scream -myes1.aud,ROL1=1 ; VOC_YES "Yes?" -mcomnd1.aud,ROL1=1 ; VOC_COMMANDER "Commander?" -mhello1.aud,ROL1=1 ; VOC_HELLO "Hello?" -mhmmm1.aud,ROL1=1 ; VOC_HMMM "Hmmm?" -mhaste1.aud,ROL1=1 ; VOC_PROCEED1 "I will proceed, post haste." -monce1.aud,ROL1=1 ; VOC_PROCEED2 "I will proceed, at once." -mimmd1.aud,ROL1=1 ; VOC_PROCEED3 "I will proceed, immediately." -mplan1.aud,ROL1=1 ; VOC_EXCELLENT1 "That is an excellent plan." -mplan2.aud,ROL1=1 ; VOC_EXCELLENT2 "Yes, that is an excellent plan." -mplan3.aud,ROL1=1 ; VOC_EXCELLENT3 "A wonderful plan." -maction1.aud,ROL1=1 ; VOC_EXCELLENT4 "Astounding plan of action commander." -mremark1.aud,ROL1=1 ; VOC_EXCELLENT5 "Remarkable contrivance." -mcourse1.aud,ROL1=1 ; VOC_OF_COURSE "Of course." -myesyes1.aud,ROL1=1 ; VOC_YESYES "Yes yes yes." -mtiber1.aud,ROL1=1 ; VOC_QUIP1 "Mind the Tiberium." -mmg1.aud,ROL1=1 ; VOC_QUIP2 "A most remarkable Metasequoia Glyptostroboides." -mthanks1.aud,ROL1=1 ; VOC_THANKS "Thank you." -cashturn.aud,ROL1=1 ; VOC_CASHTURN Sound of money being piled up. -bleep2.aud,ROL1=1 ; VOC_BLEEPY3 Clean computer bleep sound. -dinomout.aud,ROL1=1 ; VOC_DINOMOUT Movin' out in dino-speak. -dinoyes.aud,ROL1=1 ; VOC_DINOYES Yes Sir in dino-speak. -dinoatk1.aud,ROL1=1 ; VOC_DINOATK1 Dino attack sound. -dinodie1.aud,ROL1=1 ; VOC_DINODIE1 Dino die sound. -; Generic infantry/vehicle responses -2dangr1,ROL1,AudioVarTd=1 ; VOC_2DANGER "negative, too dangerous" -ackno,ROL1,AudioVarTd=1 ; VOC_ACKNOWL "acknowledged" -affirm1,ROL1,AudioVarTd=1 ; VOC_AFFIRM "affirmative" -await1,ROL1,AudioVarTd=1 ; VOC_AWAIT1 "awaiting orders" -backup,ROL1,AudioVarTd=1 ; VOC_BACKUP "send backup" -help,ROL1,AudioVarTd=1 ; VOC_HELP "send help" -movout1,ROL1,AudioVarTd=1 ; VOC_MOVEOUT "movin' out" -negatv1,ROL1,AudioVarTd=1 ; VOC_NEGATIVE "negative" -noprob,ROL1,AudioVarTd=1 ; VOC_NO_PROB "not a problem" -ready,ROL1,AudioVarTd=1 ; VOC_READY "ready and waiting" -report1,ROL1,AudioVarTd=1 ; VOC_REPORT "reporting" -ritaway,ROL1,AudioVarTd=1 ; VOC_RIGHT_AWAY "right away sir" -roger,ROL1,AudioVarTd=1 ; VOC_ROGER "roger" -sir1,ROL1,AudioVarTd=1 ; VOC_SIR1 "sir?" -squad1,ROL1,AudioVarTd=1 ; VOC_SQUAD1 "squad reporting" -target1,ROL1,AudioVarTd=1 ; VOC_PRACTICE "target practice" -ugotit,ROL1,AudioVarTd=1 ; VOC_UGOTIT "you got it" -unit1,ROL1,AudioVarTd=1 ; VOC_UNIT1 "unit reporting" -vehic1,ROL1,AudioVarTd=1 ; VOC_VEHIC1 "vehicle reporting" -yessir1,ROL1,AudioVarTd=1 ; VOC_YESSIR "yes sir" -; EVA voices -accom1.aud,ROL1=1 ; mission accomplished -fail1.aud,ROL1=1 ; your mission has failed -bldg1.aud,ROL1=1 ; unable to comply, building in progress -constru1.aud,ROL1=1 ; construction complete -unitredy.aud,ROL1=1 ; unit ready -newopt1.aud,ROL1=1 ; new construction options -deploy1.aud,ROL1=1 ; cannot deploy here -gdidead1.aud,ROL1=1 ; GDI unit destroyed -noddead1.aud,ROL1=1 ; Nod unit destroyed -civdead1.aud,ROL1=1 ; civilian killed -evayes1.aud,ROL1=1 ; affirmative -evano1.aud,ROL1=1 ; negative -upunit1.aud,ROL1=1 ; upgrade complete, new unit available -upstruc1.aud,ROL1=1 ; upgrade complete, new structure available -nocash1.aud,ROL1=1 ; insufficient funds -batlcon1.aud,ROL1=1 ; battle control terminated -reinfor1.aud,ROL1=1 ; reinforcements have arrived -cancel1.aud,ROL1=1 ; canceled -bldging1.aud,ROL1=1 ; building -lopower1.aud,ROL1=1 ; low power -nopower1.aud,ROL1=1 ; insufficient power -mocash1.aud,ROL1=1 ; need more funds -baseatk1.aud,ROL1=1 ; our base is under attack -income1.aud,ROL1=1 ; incoming missile -enemya.aud,ROL1=1 ; enemy planes approaching -nuke1.aud,ROL1=1 ; nuclear warhead approaching - VOX_INCOMING_NUKE -radok1.aud,ROL1=1 ; radiation levels are acceptable -radfatl1.aud,ROL1=1 ; radiation levels are fatal -nobuild1.aud,ROL1=1 ; unable to build more -pribldg1.aud,ROL1=1 ; primary building selected -repdone1.aud,ROL1=1 ; repairs completed -nodcapt1.aud,ROL1=1 ; Nod building captured -gdicapt1.aud,ROL1=1 ; GDI building captured -sold1.aud,ROL1=1 ; structure sold -ionchrg1.aud,ROL1=1 ; ion cannon charging -ionredy1.aud,ROL1=1 ; ion cannon ready -nukavail.aud,ROL1=1 ; nuclear weapon available -nuklnch1.aud,ROL1=1 ; nuclear weapon launched - VOX_NUKE_LAUNCHED -unitlost.aud,ROL1=1 ; unit lost -strclost.aud,ROL1=1 ; structure lost -needharv.aud,ROL1=1 ; need harvester -select1.aud,ROL1=1 ; select target -airredy1.aud,ROL1=1 ; airstrike ready -noredy1.aud,ROL1=1 ; not ready -transsee.aud,ROL1=1 ; Nod transport sighted -tranload.aud,ROL1=1 ; Nod transport loaded -enmyapp1.aud,ROL1=1 ; enemy approaching -silos1.aud,ROL1=1 ; silos needed -onhold1.aud,ROL1=1 ; on hold -repair1.aud,ROL1=1 ; repairing -estrucx.aud,ROL1=1 ; enemy structure destroyed -gstruc1.aud,ROL1=1 ; GDI structure destroyed -nstruc1.aud,ROL1=1 ; NOD structure destroyed -enmyunit.aud,ROL1=1 ; Enemy unit destroyed -; fonts -3point.fnt,ROL1=1 -6point.fnt,ROL1=1 -8fat.fnt,ROL1=1 -8point.fnt,ROL1=1 -12green.fnt,ROL1=1 -12grngrd.fnt,ROL1=1 -font6.fnt,ROL1=1 -grad6fnt.fnt,ROL1=1 -grad12fn.fnt,ROL1=1 -hfnt-d.fnt,ROL1=1 -led.fnt,ROL1=1 -scorefnt.fnt,ROL1=1 -vcr.fnt,ROL1=1 -; additional misc files -record.bin -title.cps,ROL1=1 -attract2.cps,ROL1=1 ; Added by C&C95 v1.06 -attrceng.cps,ROL1=1 ; Added by C&C95 v1.06 -attrcger.cps,ROL1=1 ; Added by C&C95 v1.06 -attrcfre.cps,ROL1=1 ; Added by C&C95 v1.06 -mouse.shp,ROL1=1 -htitle.pcx,ROL1=1 -mtitle.pcx,ROL1=1 ; Added by C&C95 v1.06 -xtitle.pcx,ROL1=1 ; Added by C&C95 v1.06 -ccmenu.pal,ROL1=1 ; Added by C&C95 v1.06 -x.vqp ; Added by C&C95 v1.06 -conquer.eng,ROL1=1 -conquer.fre,ROL1=1 ; Added by C&C95 v1.06 -conquer.ger,ROL1=1 ; Added by C&C95 v1.06 -sides.pal,ROL1=1 -map1.pal,ROL1=1 -map_locl.pal,ROL1=1 -map_gry2.pal,ROL1=1 -map_prog.pal,ROL1=1 -map_prob.pal,ROL1=1 ; Missing stretch table for africa.wsa, added by v1.06 -lastscng.pal,ROL1=1 -lastscnb.pal,ROL1=1 -dark_e.pal,ROL1=1 ; Dark palette for Europe and Africa maps -map_loc2.pal,ROL1=1 ; stretch table for dark_e.pal -dark_b.pal,ROL1=1 ; Dark palette for Bosnia map -map_loc3.pal,ROL1=1 ; stretch table for dark_b.pal -dark_sa.pal,ROL1=1 ; Dark palette for South-Africa map -map_loc4.pal,ROL1=1 ; Missing stretch table for dark_sa.pal; Added by v1.06 -satsel.cps,ROL1=1 -satsel.pal,ROL1=1 ; Palette, not stretch table. Unsure if used. -satselin.pal,ROL1=1 ; stretch table for satsel -scorpal1.pal,ROL1=1 -snodpal1.pal,ROL1=1 -multscor.pal,ROL1=1 -countrye.shp,ROL1=1 -countrya.shp,ROL1=1 -pump.shp,ROL1=1 -pumpicon.shp,ROL1=1 -pumpmake.shp,ROL1=1 -roadicon.shp,ROL1=1 -; Placement grids -trans.icn,ROL1=1 - ; Radar logos -radar.gdi,ROL1=1 -radar.nod,ROL1=1 -radar.jp,ROL1=1 -hradar.gdi,ROL1=1 -hradar.nod,ROL1=1 -hradar.jp,ROL1=1 -hradar.ww,ROL1=1 ; Added by C&C95 v1.06 -hradar.ea,ROL1=1 ; Added by C&C95 v1.06 -; C&C95 v1.06 inis -lang_eng.ini,ROL1=1 -lang_fre.ini,ROL1=1 -lang_ger.ini,ROL1=1 -lang_jap.ini,ROL1=1 -rules.ini,ROL1=1 -themes.ini,ROL1=1 +; DESC: Description. Is required to be valid ini format, but is ignored if it is only one + ; character long. The 'x' character is generally used as placeholder. -[FilesRA] -;embedded mix files -allies.mix,ROL1=1 -russian.mix,ROL1=1 -conquer.mix,ROL1=1 -edhi.mix,ROL1=1 -edlo.mix,ROL1=1 -general.mix,ROL1=1 -movies1.mix,ROL1=1 -movies2.mix,ROL1=1 -scores.mix,ROL1=1 -sounds.mix,ROL1=1 -editor.mix,ROL1=1 -local.mix,ROL1=1 -lores.mix,ROL1=1 -lores1.mix,ROL1=1 -hires.mix,ROL1=1 -hires1.mix,ROL1=1 -nchires.mix,ROL1=1 -speech.mix,ROL1=1 -; Generates all theater mixfile names. -mix,ROL1,TheaterFile=1 -;Generates all mission names. The actual given filename is empty; it's all generated by the iterator. -,ROL1,MissionRa=1 -; Generates all theater palettes. -pal,ROL1,TheaterFile=1 -; Templates; simple theater-iterating entries. - -[FilesSole] diff --git a/CnCTDRAMapEditor/SoleSurvivor/GameInfoSole.cs b/CnCTDRAMapEditor/SoleSurvivor/GameInfoSole.cs index a350e17..959641e 100644 --- a/CnCTDRAMapEditor/SoleSurvivor/GameInfoSole.cs +++ b/CnCTDRAMapEditor/SoleSurvivor/GameInfoSole.cs @@ -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; diff --git a/CnCTDRAMapEditor/TiberianDawn/GameInfoTibDawn.cs b/CnCTDRAMapEditor/TiberianDawn/GameInfoTibDawn.cs index dd56b94..9d4f90f 100644 --- a/CnCTDRAMapEditor/TiberianDawn/GameInfoTibDawn.cs +++ b/CnCTDRAMapEditor/TiberianDawn/GameInfoTibDawn.cs @@ -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; diff --git a/CnCTDRAMapEditor/Utility/EnhFormatString.cs b/CnCTDRAMapEditor/Utility/EnhFormatString.cs index 720f7d5..f76ef86 100644 --- a/CnCTDRAMapEditor/Utility/EnhFormatString.cs +++ b/CnCTDRAMapEditor/Utility/EnhFormatString.cs @@ -24,6 +24,7 @@ namespace MobiusEditor.Utility /// 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; + + /// + /// Finds the highest {#} argument inside the string that matches the EnhFormatString format. + /// + /// String to find arguments in. + /// The highest argument number found in , or -1 if none were found. + 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; + } } } diff --git a/CnCTDRAMapEditor/Utility/FileNameGenerator.cs b/CnCTDRAMapEditor/Utility/FileNameGenerator.cs deleted file mode 100644 index 31763be..0000000 --- a/CnCTDRAMapEditor/Utility/FileNameGenerator.cs +++ /dev/null @@ -1,400 +0,0 @@ -// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -// Version 2, December 2004 -// -// Copyright (C) 2004 Sam Hocevar -// -// 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 hashMethods = HashMethod.GetRegisteredMethods().ToDictionary(m => m.GetSimpleName(), StringComparer.OrdinalIgnoreCase); - private static readonly HashMethod defaultHashMethod = HashMethod.GetRegisteredMethods().FirstOrDefault(); - - private List games; - private Dictionary gameFiles; - private Dictionary gameTheaterInfo; - private Dictionary modTheaterInfo; - private Dictionary typeDefinitions; - - public List 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(); - gameFiles = new Dictionary(); - gameTheaterInfo = new Dictionary(); - modTheaterInfo = new Dictionary(); - typeDefinitions = new Dictionary(); - - 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 generators = new List(); - 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> GetAllNameIds() - { - foreach (string game in games) - { - foreach (KeyValuePair nameInfo in this.GetNameIds(game)) - { - yield return nameInfo; - } - } - } - - public IEnumerable> 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 fileInfo in GetHashInfo(filenames, theaterInfo, false)) - { - yield return fileInfo; - } - if (theaterInfomod != null && theaterInfomod.Length > 0) - { - foreach (KeyValuePair fileInfo in GetHashInfo(filenames, theaterInfomod, true)) - { - yield return fileInfo; - } - } - } - - private IEnumerable> 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(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(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 iterationBlocks = new List(); - // 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 iterationChunks = new List(); - 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 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; - } - } - - /// - /// This is the main workhorse, it creates new strings and formats them to output the final composed names. - /// - /// base name to format into the string as {0} - /// Theater info, used to iterate over the names in case groups beyond {0} are used. - /// The position of the entry which is replaced by new items currently. - /// The current key represented as int array, to be filled ith the array of items to iterate. - /// The length of the full key, to know when to end. - /// The length of the full key minus one, to know when to end. - /// - private IEnumerable 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 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); - - } - } - - } - } - } -} diff --git a/CnCTDRAMapEditor/Utility/GenericBooleanTypeConverter.cs b/CnCTDRAMapEditor/Utility/GenericBooleanTypeConverter.cs index f37e344..8043281 100644 --- a/CnCTDRAMapEditor/Utility/GenericBooleanTypeConverter.cs +++ b/CnCTDRAMapEditor/Utility/GenericBooleanTypeConverter.cs @@ -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'); } } diff --git a/CnCTDRAMapEditor/Utility/MixContentAnalysis.cs b/CnCTDRAMapEditor/Utility/MixContentAnalysis.cs index 367385f..c3cdb77 100644 --- a/CnCTDRAMapEditor/Utility/MixContentAnalysis.cs +++ b/CnCTDRAMapEditor/Utility/MixContentAnalysis.cs @@ -18,7 +18,7 @@ namespace MobiusEditor.Utility private const uint xccId = 0x54C2D545; private const uint maxProcessed = 0x500000; - public static List AnalyseFiles(MixFile current, Dictionary encodedFilenames, bool preferMissions, Func checkAbort) + public static List AnalyseFiles(MixFile current, Dictionary encodedFilenames, bool preferMissions, Func checkAbort) { List filesList = current.GetFileIds(); List fileInfo = new List(); @@ -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)) { diff --git a/CnCTDRAMapEditor/Utility/MixFile.cs b/CnCTDRAMapEditor/Utility/MixFile.cs index 377dc42..8be5e8f 100644 --- a/CnCTDRAMapEditor/Utility/MixFile.cs +++ b/CnCTDRAMapEditor/Utility/MixFile.cs @@ -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) diff --git a/CnCTDRAMapEditor/Utility/MixFileNameGenerator.cs b/CnCTDRAMapEditor/Utility/MixFileNameGenerator.cs new file mode 100644 index 0000000..7f0decf --- /dev/null +++ b/CnCTDRAMapEditor/Utility/MixFileNameGenerator.cs @@ -0,0 +1,563 @@ +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyright (C) 2004 Sam Hocevar +// +// 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 TypeDefinitions { get; set; } + public string[] Files { get; set; } + public Dictionary 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 hashMethods = HashMethod.GetRegisteredMethods().ToDictionary(m => m.GetSimpleName(), StringComparer.OrdinalIgnoreCase); + private static readonly HashMethod defaultHashMethod = HashMethod.GetRegisteredMethods().FirstOrDefault(); + + private List games = new List(); + private Dictionary gameInfo = new Dictionary(); + + public List Games => games.ToList(); + + public MixFileNameGenerator(string iniPath) + : this(null, iniPath, null, ConstrArgs.IniPath) + { + } + + /// + /// + /// + /// Ini file to open. If additional inis need to be read, they will be looked up in the current working directory. + public MixFileNameGenerator(INI iniFile) + : this(iniFile, Path.Combine(Path.GetDirectoryName("."), "dummy.ini"), null, ConstrArgs.IniObj) + { + } + + /// + /// + /// + /// Main ini file to open. + /// Source path of , Is needed if side inis need to be read. + public MixFileNameGenerator(INI iniFile, string iniPath) + : this(iniFile, iniPath, null, ConstrArgs.IniObj | ConstrArgs.IniPath) + { + } + + /// + /// 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. + /// + /// Main ini file to open. + /// Dictionary of additional ini files that can be used to read the file lists of specific games. + public MixFileNameGenerator(INI iniFile, Dictionary additionalInis) + : this(iniFile, null, additionalInis, ConstrArgs.IniObj | ConstrArgs.SideInis) + { + } + + /// + /// Full constructor; not public because all specific cases are handled in the overloads. + /// + /// Main ini file to open. Can be null if is given. + /// Source path of . Is needed if side inis need to be read, and is not supplied. + /// Dictionary of additional ini files that can be used to read the file lists of specific games. + /// Origin args, to know what to give exceptions on when data is missing. + /// + private MixFileNameGenerator(INI iniFile, string iniPath, Dictionary 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 gameIniFiles = new List(); + 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 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 GetTypeDefinitions(String[] typesSections, List toScan) + { + Dictionary typeDefinitions = new Dictionary(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 generators = new List(); + 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 GetAllNameIds() + { + foreach (string game in games) + { + foreach (MixEntry nameInfo in this.GetNameIds(game)) + { + yield return nameInfo; + } + } + } + + public IEnumerable GetAllNameIds(string preferred) + { + List 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 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 filenameInfo = gameDef.FileDescriptions; + HashMethod hashMethod = gameDef.Hasher ?? defaultHashMethod; + Dictionary 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 GetHashInfo(string[] filenames, Dictionary filenameInfo, Dictionary 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 iterationBlocks = new List(); + // 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 iterationChunks = new List(); + 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; + } + } + + /// + /// This is the main workhorse, it creates new strings and formats them to output the final composed names. + /// + /// base name to format into the string as {0} + /// Theater info, used to iterate over the names in case groups beyond {0} are used. + /// The position of the entry which is replaced by new items currently. + /// The current key represented as int array, to be filled ith the array of items to iterate. + /// The length of the full key, to know when to end. + /// The length of the full key minus one, to know when to end. + /// + 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); + + } + } + + } + } + } +}