Split off all operations related to the new mix path format in a new MixPath tool class.
This commit is contained in:
parent
fee6c00a31
commit
3af809b2af
@ -639,6 +639,7 @@
|
||||
<Compile Include="Utility\MixFile.cs" />
|
||||
<Compile Include="Utility\MixFileCrypto.cs" />
|
||||
<Compile Include="Utility\MixfileManager.cs" />
|
||||
<Compile Include="Utility\MixPath.cs" />
|
||||
<Compile Include="Utility\MRU.cs" />
|
||||
<Compile Include="Utility\PropertyTracker.cs" />
|
||||
<Compile Include="Event\MapRefreshEventArgs.cs" />
|
||||
|
@ -157,49 +157,49 @@ namespace MobiusEditor.Dialogs
|
||||
else if (type == MixContentType.MapRa)
|
||||
{
|
||||
// Open that mofo!
|
||||
SelectedFile = GeneralUtils.BuildMixPath(openedMixFiles, selected.Name ?? selected.IdString);
|
||||
SelectedFile = MixPath.BuildMixPath(openedMixFiles, selected);
|
||||
this.DialogResult = DialogResult.OK;
|
||||
}
|
||||
else if (type == MixContentType.MapTd || type == MixContentType.MapSole)
|
||||
{
|
||||
String iniName = name;
|
||||
string binName;
|
||||
MixEntry iniEntry = selected;
|
||||
MixEntry binEntry;
|
||||
if (name == null)
|
||||
{
|
||||
// Inform user that accompanying bin is impossible to find without name, and ask if user wants to open it on a blank terrain map.
|
||||
binName = String.Empty;
|
||||
// TODO Inform user that accompanying bin is impossible to find without name, and ask if user wants to open it on a blank terrain map.
|
||||
binEntry = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// try to find accompanying .bin file
|
||||
binName = Path.GetFileNameWithoutExtension(iniName) + ".bin";
|
||||
MixEntry binEntry = currentMixInfo.FirstOrDefault(me => binName.Equals(me.Name, StringComparison.OrdinalIgnoreCase));
|
||||
string binName = Path.GetFileNameWithoutExtension(name) + ".bin";
|
||||
binEntry = currentMixInfo.FirstOrDefault(me => binName.Equals(me.Name, StringComparison.OrdinalIgnoreCase));
|
||||
if (binEntry == null)
|
||||
{
|
||||
// Inform user that accompanying bin was not found, and ask if user wants to open it on a blank terrain map.
|
||||
binName = String.Empty;
|
||||
// TODO Inform user that accompanying bin was not found, and ask if user wants to open it on a blank terrain map.
|
||||
}
|
||||
}
|
||||
SelectedFile = GeneralUtils.BuildMixPath(openedMixFiles, iniName, binName);
|
||||
SelectedFile = MixPath.BuildMixPath(openedMixFiles, iniEntry, binEntry);
|
||||
this.DialogResult = DialogResult.OK;
|
||||
}
|
||||
else if (type == MixContentType.Bin || type == MixContentType.BinSole)
|
||||
{
|
||||
String binName = name;
|
||||
MixEntry iniEntry;
|
||||
MixEntry binEntry = selected;
|
||||
if (name == null)
|
||||
{
|
||||
// Inform user that accompanying ini is impossible to find without name, and that a map can't be opened without ini.
|
||||
// TODO Inform user that accompanying ini is impossible to find without name, and that a map can't be opened without ini.
|
||||
return;
|
||||
}
|
||||
// try to find accompanying .ini file
|
||||
string iniName = Path.GetFileNameWithoutExtension(binName) + ".ini";
|
||||
MixEntry iniEntry = currentMixInfo.FirstOrDefault(me => iniName.Equals(me.Name, StringComparison.OrdinalIgnoreCase));
|
||||
string iniName = Path.GetFileNameWithoutExtension(name) + ".ini";
|
||||
iniEntry = currentMixInfo.FirstOrDefault(me => iniName.Equals(me.Name, StringComparison.OrdinalIgnoreCase));
|
||||
if (iniEntry == null)
|
||||
{
|
||||
// Inform user that accompanying bin was not found, and that a map can't be opened without ini.
|
||||
// TODO Inform user that accompanying bin was not found, and that a map can't be opened without ini.
|
||||
return;
|
||||
}
|
||||
SelectedFile = GeneralUtils.BuildMixPath(openedMixFiles, iniName, binName);
|
||||
SelectedFile = MixPath.BuildMixPath(openedMixFiles, iniEntry, binEntry);
|
||||
this.DialogResult = DialogResult.OK;
|
||||
}
|
||||
}
|
||||
|
@ -33,19 +33,17 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MobiusEditor
|
||||
{
|
||||
public partial class MainForm : Form, IFeedBackHandler, IHasStatusLabel
|
||||
{
|
||||
|
||||
const string noname = "Untitled";
|
||||
const string MAP_UNTITLED = "Untitled";
|
||||
private Dictionary<string, Bitmap> theaterIcons = new Dictionary<string, Bitmap>();
|
||||
private Dictionary<uint, string> generatedMixIds = null;
|
||||
|
||||
@ -206,7 +204,7 @@ namespace MobiusEditor
|
||||
bool mapNameEmpty = gi.MapNameIsEmpty(mapName);
|
||||
bool fileNameEmpty = filename == null;
|
||||
string mapFilename = "\""
|
||||
+ (fileNameEmpty ? noname + (loadedFileType == FileType.MIX ? gi.DefaultExtensionFromMix : gi.DefaultExtension) : Path.GetFileName(filename))
|
||||
+ (fileNameEmpty ? MAP_UNTITLED + (loadedFileType == FileType.MIX ? gi.DefaultExtensionFromMix : gi.DefaultExtension) : Path.GetFileName(filename))
|
||||
+ "\"";
|
||||
string mapShowName;
|
||||
if (!mapNameEmpty && !fileNameEmpty)
|
||||
@ -658,7 +656,7 @@ namespace MobiusEditor
|
||||
else
|
||||
{
|
||||
string name = gi.MapNameIsEmpty(plugin.Map.BasicSection.Name)
|
||||
? noname
|
||||
? MAP_UNTITLED
|
||||
: string.Join("_", plugin.Map.BasicSection.Name.Split(Path.GetInvalidFileNameChars()));
|
||||
sfd.FileName = name + (loadedFileType == FileType.MIX ? gi.DefaultExtensionFromMix : gi.DefaultExtension);
|
||||
}
|
||||
@ -1301,33 +1299,23 @@ namespace MobiusEditor
|
||||
{
|
||||
ClearActiveTool();
|
||||
bool isMix = !String.IsNullOrEmpty(fileName) && fileName.Contains('?');
|
||||
String loadName = fileName;
|
||||
String fullname = fileName;
|
||||
String shortName;
|
||||
string loadName = fileName;
|
||||
string feedbackName = fileName;
|
||||
bool nameIsId = false;
|
||||
if (!isMix)
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(fileName);
|
||||
loadName = fileInfo.FullName;
|
||||
fullname = fileInfo.FullName;
|
||||
shortName = fileInfo.Name;
|
||||
feedbackName = fileInfo.FullName;
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] nameParts = fileName.Split('?');
|
||||
string[] mixparts = nameParts[0].Split(';');
|
||||
FileInfo fileInfo = new FileInfo(mixparts[0]);
|
||||
string mixParts = String.Join(" -> ", mixparts.Skip(1));
|
||||
string iniName = nameParts.Length > 1 ? " -> " + nameParts[1].Split(';')[0] : null;
|
||||
loadName = fileInfo.FullName + " -> " + mixParts;
|
||||
fullname = fileInfo.FullName + " -> " + mixParts;
|
||||
shortName = fileInfo.Name + " -> " + mixParts;
|
||||
if (iniName != null)
|
||||
{
|
||||
fullname += iniName;
|
||||
shortName += iniName;
|
||||
}
|
||||
MixPath.GetComponentsViewable(fileName, out string[] mixParts, out _);
|
||||
FileInfo fileInfo = new FileInfo(mixParts[0]);
|
||||
loadName = fileInfo.FullName;
|
||||
feedbackName = MixPath.GetFileNameReadable(fileName, false, out nameIsId);
|
||||
}
|
||||
if (!IdentifyMap(fileName, out FileType fileType, out GameType gameType, out bool isMegaMap, out string theater))
|
||||
if (!IdentifyMap(fileName, out FileType fileType, out GameType gameType, out bool isMegaMap, out string theater) && !isMix)
|
||||
{
|
||||
string extension = Path.GetExtension(loadName).TrimStart('.');
|
||||
// No point in supporting jpeg here; the mapping needs distinct colours without fades.
|
||||
@ -1351,7 +1339,7 @@ namespace MobiusEditor
|
||||
// Ignore and just fall through.
|
||||
}
|
||||
}
|
||||
MessageBox.Show(string.Format("Error loading {0}: Could not identify map type.", shortName), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
MessageBox.Show(string.Format("Error loading {0}: Could not identify map type.", feedbackName), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
RefreshActiveTool();
|
||||
return;
|
||||
}
|
||||
@ -1368,7 +1356,7 @@ namespace MobiusEditor
|
||||
{
|
||||
string graphicsMode = Globals.UseClassicFiles ? "Classic" : "Remastered";
|
||||
string message = string.Format("Error loading {0}: No assets found for {1} theater \"{2}\" in {3} graphics mode.",
|
||||
fullname, gType.Name, theaterObj.Name, graphicsMode);
|
||||
feedbackName, gType.Name, theaterObj.Name, graphicsMode);
|
||||
if (Globals.UseClassicFiles)
|
||||
{
|
||||
message += String.Format("\n\nYou may need to adjust the \"{0}\" setting to point to a game folder containing {1}, or add {1} to the configured folder.",
|
||||
@ -1444,9 +1432,8 @@ namespace MobiusEditor
|
||||
if (isMixFile)
|
||||
{
|
||||
fileType = FileType.MIX;
|
||||
string[] pathparts = fullFilename.Split('?');
|
||||
string[] mixparts = pathparts[0].Split(';');
|
||||
loadFilename = mixparts[0];
|
||||
MixPath.GetComponents(fullFilename, out string[] mixParts, out string[] filenameParts);
|
||||
loadFilename = mixParts[0];
|
||||
}
|
||||
try
|
||||
{
|
||||
@ -1834,33 +1821,35 @@ namespace MobiusEditor
|
||||
bool isMix = loadInfo.FileName.Contains('?');
|
||||
IGamePlugin oldPlugin = this.plugin;
|
||||
string[] errors = loadInfo.Errors ?? new string[0];
|
||||
// Plugin set to null indicates a fatal processing error where no map was loaded at all.
|
||||
string feedbackPath = loadInfo.FileName;
|
||||
string feedbackName;
|
||||
string reportName = loadInfo.FileName;
|
||||
string feedbackNameShort;
|
||||
bool regenerateSaveName = false;
|
||||
string resaveName = loadInfo.FileName;
|
||||
FileType resaveType = loadInfo.FileType;
|
||||
if (isMix)
|
||||
{
|
||||
string[] parts = feedbackPath.Split('?');
|
||||
string[] mixParts = parts[0].Split(';');
|
||||
string mixName = mixParts[0];
|
||||
reportName = mixName;
|
||||
feedbackPath = String.Join(" -> ", mixParts);
|
||||
mixParts[0] = Path.GetFileName(mixName);
|
||||
feedbackName = String.Join(" -> ", mixParts);
|
||||
if (parts.Length > 1)
|
||||
feedbackPath = MixPath.GetFileNameReadable(loadInfo.FileName, false, out regenerateSaveName);
|
||||
feedbackNameShort = MixPath.GetFileNameReadable(loadInfo.FileName, true, out _);
|
||||
MixPath.GetComponentsViewable(feedbackPath, out string[] mixParts, out string[] filenameParts);
|
||||
FileInfo fileInfo = new FileInfo(mixParts[0]);
|
||||
string mixName = fileInfo.FullName;
|
||||
string loadedName = filenameParts[0];
|
||||
if (String.IsNullOrEmpty(loadedName) && filenameParts.Length > 1 && !String.IsNullOrEmpty(filenameParts[1]))
|
||||
{
|
||||
reportName = parts[1].Split(';')[0];
|
||||
feedbackName += " -> " + reportName;
|
||||
feedbackPath += " -> " + reportName;
|
||||
resaveName = Path.Combine(Path.GetDirectoryName(mixName), reportName);
|
||||
// Use the .bin file.
|
||||
loadedName = filenameParts[1];
|
||||
}
|
||||
String resavePath = Path.GetDirectoryName(mixName);
|
||||
if (String.IsNullOrEmpty(loadedName))
|
||||
regenerateSaveName = true;
|
||||
// If the name gets regenerated from map name, add a dummy name for now so it can extract the path at least.
|
||||
resaveName = Path.Combine(resavePath, regenerateSaveName ? MAP_UNTITLED + ".ini" : loadedName);
|
||||
}
|
||||
else
|
||||
{
|
||||
feedbackName = Path.GetFileName(feedbackPath);
|
||||
feedbackNameShort = Path.GetFileName(feedbackPath);
|
||||
}
|
||||
// Plugin set to null indicates a fatal processing error where no map was loaded at all.
|
||||
if (loadInfo.Plugin == null || (loadInfo.Plugin != null && !loadInfo.MapLoaded))
|
||||
{
|
||||
// Attempted to load file, loading went OK, but map was not loaded.
|
||||
@ -1878,15 +1867,12 @@ namespace MobiusEditor
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isMix)
|
||||
if (isMix && regenerateSaveName)
|
||||
{
|
||||
GameInfo gi = loadInfo.Plugin.GameInfo;
|
||||
if (GeneralUtils.IdCheck.IsMatch(reportName))
|
||||
{
|
||||
string mapName = loadInfo.Plugin.Map.BasicSection.Name;
|
||||
mapName = gi.MapNameIsEmpty(mapName) ? noname : string.Join("_", mapName.Split(Path.GetInvalidFileNameChars()));
|
||||
resaveName = Path.Combine(Path.GetDirectoryName(resaveName), mapName + gi.DefaultExtensionFromMix);
|
||||
}
|
||||
string mapName = loadInfo.Plugin.Map.BasicSection.Name;
|
||||
mapName = gi.MapNameIsEmpty(mapName) ? MAP_UNTITLED : string.Join("_", mapName.Split(Path.GetInvalidFileNameChars()));
|
||||
resaveName = Path.Combine(Path.GetDirectoryName(resaveName), mapName + gi.DefaultExtensionFromMix);
|
||||
}
|
||||
this.plugin = loadInfo.Plugin;
|
||||
plugin.FeedBackHandler = this;
|
||||
@ -1895,7 +1881,7 @@ namespace MobiusEditor
|
||||
{
|
||||
using (ErrorMessageBox emb = new ErrorMessageBox())
|
||||
{
|
||||
emb.Title = "Error Report - " + feedbackName;
|
||||
emb.Title = "Error Report - " + feedbackNameShort;
|
||||
emb.Errors = errors;
|
||||
emb.StartPosition = FormStartPosition.CenterParent;
|
||||
emb.ShowDialog(this);
|
||||
|
@ -1154,5 +1154,23 @@ namespace MobiusEditor.RedAlert
|
||||
QUARRY_POWER, // Attack power facilities.
|
||||
QUARRY_FAKES, // Prefer to attack fake buildings.
|
||||
};
|
||||
|
||||
public static readonly string[] UnitVocNames = new[]
|
||||
{
|
||||
"ACKNO",
|
||||
"AFFIRM1",
|
||||
"AWAIT1",
|
||||
"EAFFIRM1",
|
||||
"EENGIN1",
|
||||
"NOPROB",
|
||||
"OVEROUT",
|
||||
"READY",
|
||||
"REPORT1",
|
||||
"RITAWAY",
|
||||
"ROGER",
|
||||
"UGOTIT",
|
||||
"VEHIC1",
|
||||
"YESSIR1",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using MobiusEditor.Interface;
|
||||
using MobiusEditor.Model;
|
||||
using MobiusEditor.Utility;
|
||||
@ -252,25 +253,32 @@ namespace MobiusEditor.RedAlert
|
||||
"nchires.mix",
|
||||
"speech.mix",
|
||||
};
|
||||
private static readonly string[] additionalFiles = new string[]
|
||||
|
||||
private static readonly string[] additionalTheaterFiles = new string[]
|
||||
{
|
||||
"moveflsh",
|
||||
"corpse1",
|
||||
"corpse2",
|
||||
"corpse3",
|
||||
"electro",
|
||||
};
|
||||
|
||||
// Mostly animations
|
||||
private static readonly string[] additionalShpFiles = new string[]
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
//These are usually unused files. All the rest ends up in ActionDataTypes (though it's also not a great place for that).
|
||||
private static readonly string[] additionalFiles = new string[]
|
||||
{
|
||||
"dogw6.aud",
|
||||
"await_r.aud",
|
||||
"araziod.aud",
|
||||
};
|
||||
|
||||
public override IEnumerable<string> GetGameFiles()
|
||||
{
|
||||
foreach (string name in GetMissionFiles())
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
foreach (string name in GetGraphicsFiles(TheaterTypes.GetAllTypes()))
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
foreach (string name in GetMediaFiles())
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
foreach (string name in embeddedMixFiles)
|
||||
{
|
||||
yield return name;
|
||||
@ -280,6 +288,26 @@ namespace MobiusEditor.RedAlert
|
||||
{
|
||||
yield return theater.ClassicTileset + mixExt;
|
||||
}
|
||||
foreach (string name in GetMissionFiles())
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
foreach (string name in GetGraphicsFiles(TheaterTypes.GetAllTypes()))
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
foreach (string name in GetAudioFiles())
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
foreach (string name in GetMediaFiles())
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
foreach (string name in additionalFiles)
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetMissionFiles()
|
||||
@ -331,7 +359,6 @@ namespace MobiusEditor.RedAlert
|
||||
|
||||
const string shpExt = ".shp";
|
||||
string[] theaterExts = theaterTypes.Where(th => !th.IsModTheater).Select(tt => "." + tt.ClassicExtension.Trim('.')).ToArray();
|
||||
string[] extraThExts = theaterTypes.Where(th => th.IsModTheater).Select(tt => "." + tt.ClassicExtension.Trim('.')).ToArray();
|
||||
// Templates
|
||||
foreach (TemplateType tmp in TemplateTypes.GetTypes())
|
||||
{
|
||||
@ -341,14 +368,6 @@ namespace MobiusEditor.RedAlert
|
||||
yield return name + theaterExts[i];
|
||||
}
|
||||
}
|
||||
foreach (TemplateType tmp in TemplateTypes.GetTypes())
|
||||
{
|
||||
string name = tmp.Name;
|
||||
for (int i = 0; i < extraThExts.Length; ++i)
|
||||
{
|
||||
yield return name + extraThExts[i];
|
||||
}
|
||||
}
|
||||
// Buildings, with icons and build-up animations
|
||||
foreach (BuildingType bt in BuildingTypes.GetTypes())
|
||||
{
|
||||
@ -363,16 +382,6 @@ namespace MobiusEditor.RedAlert
|
||||
yield return name + "make" + thExt;
|
||||
}
|
||||
}
|
||||
foreach (BuildingType bt in BuildingTypes.GetTypes())
|
||||
{
|
||||
string name = bt.Name;
|
||||
for (int i = 0; i < extraThExts.Length; ++i)
|
||||
{
|
||||
string thExt = extraThExts[i];
|
||||
yield return name + thExt;
|
||||
yield return name + "make" + thExt;
|
||||
}
|
||||
}
|
||||
// Smudge
|
||||
foreach (SmudgeType sm in SmudgeTypes.GetTypes(false))
|
||||
{
|
||||
@ -382,14 +391,6 @@ namespace MobiusEditor.RedAlert
|
||||
yield return name + theaterExts[i];
|
||||
}
|
||||
}
|
||||
foreach (SmudgeType sm in SmudgeTypes.GetTypes(false))
|
||||
{
|
||||
string name = sm.Name;
|
||||
for (int i = 0; i < extraThExts.Length; ++i)
|
||||
{
|
||||
yield return name + extraThExts[i];
|
||||
}
|
||||
}
|
||||
// Terrain
|
||||
foreach (TerrainType tr in TerrainTypes.GetTypes())
|
||||
{
|
||||
@ -399,14 +400,6 @@ namespace MobiusEditor.RedAlert
|
||||
yield return name + theaterExts[i];
|
||||
}
|
||||
}
|
||||
foreach (TerrainType tr in TerrainTypes.GetTypes())
|
||||
{
|
||||
string name = tr.Name;
|
||||
for (int i = 0; i < extraThExts.Length; ++i)
|
||||
{
|
||||
yield return name + extraThExts[i];
|
||||
}
|
||||
}
|
||||
// Infantry
|
||||
foreach (InfantryType it in InfantryTypes.GetTypes())
|
||||
{
|
||||
@ -421,10 +414,111 @@ namespace MobiusEditor.RedAlert
|
||||
yield return name + shpExt;
|
||||
yield return name + "icon" + shpExt;
|
||||
}
|
||||
// Overlay: can be both shp and theater-dependent.
|
||||
foreach (OverlayType ov in OverlayTypes.GetTypes())
|
||||
{
|
||||
string name = ov.Name;
|
||||
yield return name + shpExt;
|
||||
for (int i = 0; i < theaterExts.Length; ++i)
|
||||
{
|
||||
yield return name + theaterExts[i];
|
||||
}
|
||||
}
|
||||
// Additional .shp graphics
|
||||
foreach (string gfx in additionalShpFiles)
|
||||
{
|
||||
yield return gfx + shpExt;
|
||||
}
|
||||
// Additional theater-specific files
|
||||
foreach (string gfx in additionalTheaterFiles)
|
||||
{
|
||||
for (int i = 0; i < theaterExts.Length; ++i)
|
||||
{
|
||||
yield return gfx + theaterExts[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Extra theaters are loaded last; when it comes to collisions the official theaters come first.
|
||||
|
||||
string[] extraThExts = theaterTypes.Where(th => th.IsModTheater).Select(tt => "." + tt.ClassicExtension.Trim('.')).ToArray();
|
||||
// Templates
|
||||
foreach (TemplateType tmp in TemplateTypes.GetTypes())
|
||||
{
|
||||
string name = tmp.Name;
|
||||
for (int i = 0; i < extraThExts.Length; ++i)
|
||||
{
|
||||
yield return name + extraThExts[i];
|
||||
}
|
||||
}
|
||||
// Buildings, with icons and build-up animations
|
||||
foreach (BuildingType bt in BuildingTypes.GetTypes())
|
||||
{
|
||||
string name = bt.Name;
|
||||
for (int i = 0; i < extraThExts.Length; ++i)
|
||||
{
|
||||
string thExt = extraThExts[i];
|
||||
yield return name + thExt;
|
||||
yield return name + "make" + thExt;
|
||||
}
|
||||
}
|
||||
// Smudge
|
||||
foreach (SmudgeType sm in SmudgeTypes.GetTypes(false))
|
||||
{
|
||||
string name = sm.Name;
|
||||
for (int i = 0; i < extraThExts.Length; ++i)
|
||||
{
|
||||
yield return name + extraThExts[i];
|
||||
}
|
||||
}
|
||||
// Terrain
|
||||
foreach (TerrainType tr in TerrainTypes.GetTypes())
|
||||
{
|
||||
string name = tr.Name;
|
||||
for (int i = 0; i < extraThExts.Length; ++i)
|
||||
{
|
||||
yield return name + extraThExts[i];
|
||||
}
|
||||
}
|
||||
// Overlay
|
||||
foreach (OverlayType ov in OverlayTypes.GetTypes())
|
||||
{
|
||||
yield return ov.Name + shpExt;
|
||||
string name = ov.Name;
|
||||
for (int i = 0; i < extraThExts.Length; ++i)
|
||||
{
|
||||
yield return name + extraThExts[i];
|
||||
}
|
||||
}
|
||||
// Additional theater-specific files
|
||||
foreach (string gfx in additionalTheaterFiles)
|
||||
{
|
||||
for (int i = 0; i < extraThExts.Length; ++i)
|
||||
{
|
||||
yield return gfx + extraThExts[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetAudioFiles()
|
||||
{
|
||||
const string audExt = ".aud";
|
||||
foreach (string voc in ActionDataTypes.VocNames)
|
||||
{
|
||||
yield return voc + audExt;
|
||||
}
|
||||
foreach (string vox in ActionDataTypes.UnitVocNames)
|
||||
{
|
||||
yield return vox + ".v00";
|
||||
yield return vox + ".v01";
|
||||
yield return vox + ".v02";
|
||||
yield return vox + ".v03";
|
||||
yield return vox + ".r00";
|
||||
yield return vox + ".r01";
|
||||
yield return vox + ".r02";
|
||||
yield return vox + ".r03";
|
||||
}
|
||||
foreach (string vox in ActionDataTypes.VoxNames)
|
||||
{
|
||||
yield return vox + audExt;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -644,9 +644,9 @@ namespace MobiusEditor.RedAlert
|
||||
}
|
||||
break;
|
||||
case FileType.MIX:
|
||||
iniBytes = GeneralUtils.GetFileFromMixPath(path, FileType.INI, out string iniFileName);
|
||||
iniBytes = MixPath.ReadFile(path, FileType.INI, out MixEntry iniFile);
|
||||
ParseIniContent(ini, iniBytes, errors);
|
||||
tryCheckSingle = singlePlayRegex.IsMatch(Path.GetFileNameWithoutExtension(iniFileName)) || GeneralUtils.IdCheck.IsMatch(iniFileName);
|
||||
tryCheckSingle = iniFile.Name == null || singlePlayRegex.IsMatch(Path.GetFileNameWithoutExtension(iniFile.Name));
|
||||
errors.AddRange(LoadINI(ini, tryCheckSingle, ref modified));
|
||||
break;
|
||||
default:
|
||||
|
@ -135,6 +135,11 @@ namespace MobiusEditor.SoleSurvivor
|
||||
return GetClassicFontRemapSimple(ClassicFontTriggers, tsmc, textColor);
|
||||
}
|
||||
|
||||
private static readonly string[] additionalFiles = new string[]
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
public override IEnumerable<string> GetGameFiles()
|
||||
{
|
||||
foreach (string name in GetMissionFiles())
|
||||
@ -145,6 +150,10 @@ namespace MobiusEditor.SoleSurvivor
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
foreach (string name in additionalFiles)
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetMissionFiles()
|
||||
|
@ -142,6 +142,11 @@ namespace MobiusEditor.TiberianDawn
|
||||
return GetClassicFontRemapSimple(ClassicFontTriggers, tsmc, textColor);
|
||||
}
|
||||
|
||||
private static readonly string[] additionalFiles = new string[]
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
public override IEnumerable<string> GetGameFiles()
|
||||
{
|
||||
foreach (string name in GetMissionFiles())
|
||||
@ -156,6 +161,10 @@ namespace MobiusEditor.TiberianDawn
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
foreach (string name in additionalFiles)
|
||||
{
|
||||
yield return name;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetMissionFiles()
|
||||
|
@ -516,6 +516,7 @@ namespace MobiusEditor.TiberianDawn
|
||||
if (!File.Exists(iniPath))
|
||||
{
|
||||
// Should never happen; this gets filtered out in the game type detection.
|
||||
// todo maybe allow this to open a map only?
|
||||
throw new ApplicationException("Cannot find an ini file to load for " + Path.GetFileName(path) + ".");
|
||||
}
|
||||
iniBytes = File.ReadAllBytes(iniPath);
|
||||
@ -540,35 +541,49 @@ namespace MobiusEditor.TiberianDawn
|
||||
case FileType.PGM:
|
||||
using (Megafile megafile = new Megafile(path))
|
||||
{
|
||||
string iniFile = megafile.Where(p => Path.GetExtension(p).ToLower() == ".ini").FirstOrDefault();
|
||||
string binFile = megafile.Where(p => Path.GetExtension(p).ToLower() == ".bin").FirstOrDefault();
|
||||
if (iniFile == null || binFile == null)
|
||||
string iniFileName = megafile.Where(p => Path.GetExtension(p).ToLower() == ".ini").FirstOrDefault();
|
||||
string binFileName = megafile.Where(p => Path.GetExtension(p).ToLower() == ".bin").FirstOrDefault();
|
||||
if (iniFileName == null || binFileName == null)
|
||||
{
|
||||
throw new ApplicationException("Cannot find the necessary files inside the " + Path.GetFileName(path) + " archive.");
|
||||
}
|
||||
using (Stream iniStream = megafile.OpenFile(iniFile))
|
||||
using (Stream binStream = megafile.OpenFile(binFile))
|
||||
using (Stream iniStream = megafile.OpenFile(iniFileName))
|
||||
using (Stream binStream = megafile.OpenFile(binFileName))
|
||||
{
|
||||
iniBytes = iniStream.ReadAllBytes();
|
||||
ParseIniContent(ini, iniBytes, forSole);
|
||||
errors.AddRange(LoadINI(ini, false, ref modified));
|
||||
ReadBinFromStream(binStream, Path.GetFileName(binFile), errors, ref modified);
|
||||
ReadBinFromStream(binStream, Path.GetFileName(binFileName), errors, ref modified);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FileType.MIX:
|
||||
// uses combined path of "c:\mixfile.mix;submix1.mix;submix2.mix?file.ini;file.bin"
|
||||
// If bin is missing its filename is simply empty or missing.
|
||||
iniBytes = GeneralUtils.GetFileFromMixPath(path, FileType.INI, out string iniFileName);
|
||||
ParseIniContent(ini, iniBytes, forSole);
|
||||
tryCheckSingle = !forSole && (singlePlayRegex.IsMatch(Path.GetFileNameWithoutExtension(iniFileName)) || GeneralUtils.IdCheck.IsMatch(iniFileName));
|
||||
errors.AddRange(LoadINI(ini, tryCheckSingle, ref modified));
|
||||
byte[] binBytes = GeneralUtils.GetFileFromMixPath(path, FileType.BIN, out string binFilename);
|
||||
if (binBytes != null)
|
||||
MixPath.GetComponentsViewable(path, out string[] mixParts, out string[] filenameParts);
|
||||
iniBytes = MixPath.ReadFile(path, FileType.INI, out MixEntry iniFileEntry);
|
||||
if (iniBytes == null)
|
||||
{
|
||||
using (MemoryStream binStream = new MemoryStream(binBytes))
|
||||
// todo maybe allow this to open a map only?
|
||||
throw new ApplicationException("Cannot find the necessary files inside the archive " + Path.GetFileName(mixParts[0]) + ".");
|
||||
}
|
||||
ParseIniContent(ini, iniBytes, forSole);
|
||||
tryCheckSingle = !forSole && (iniFileEntry.Name == null || singlePlayRegex.IsMatch(Path.GetFileNameWithoutExtension(iniFileEntry.Name)));
|
||||
errors.AddRange(LoadINI(ini, tryCheckSingle, ref modified));
|
||||
using (MixFile mainMix = MixPath.OpenMixPath(path, FileType.BIN, out MixFile contentMix, out MixEntry fileEntry))
|
||||
{
|
||||
if (mainMix != null)
|
||||
{
|
||||
ReadBinFromStream(binStream, binFilename, errors, ref modified);
|
||||
using (Stream binStream = contentMix.OpenFile(fileEntry))
|
||||
{
|
||||
ReadBinFromStream(binStream, fileEntry.Name ?? fileEntry.IdString, errors, ref modified);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.Add(String.Format("No .bin file found for file '{0}'. Using empty map.", iniFileEntry.Name ?? iniFileEntry.IdString));
|
||||
modified = true;
|
||||
Map.Templates.Clear();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -50,18 +50,6 @@ namespace MobiusEditor.Utility
|
||||
|
||||
public static class GeneralUtils
|
||||
{
|
||||
public static readonly Regex IdCheck = new Regex("\\[([0-9A-F]{8})\\]");
|
||||
|
||||
public static string BuildMixPath(List<MixFile> mixFiles, params string[] files)
|
||||
{
|
||||
string[] mixArr = new string[mixFiles.Count];
|
||||
for (int i = 0; i < mixFiles.Count; ++i)
|
||||
{
|
||||
mixArr[i] = mixFiles[i].FilePath;
|
||||
}
|
||||
return String.Join(";", mixArr) + "?" + String.Join(";", files);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the contents of the ini, or null if no ini content could be found in the file.
|
||||
/// </summary>
|
||||
@ -98,7 +86,7 @@ namespace MobiusEditor.Utility
|
||||
}
|
||||
break;
|
||||
case FileType.MIX:
|
||||
byte[] iniBytes = GetFileFromMixPath(path, FileType.INI, out _);
|
||||
byte[] iniBytes = MixPath.ReadFile(path, FileType.INI, out _);
|
||||
if (iniBytes != null)
|
||||
{
|
||||
iniContents = encDOS.GetString(iniBytes);
|
||||
@ -119,62 +107,6 @@ namespace MobiusEditor.Utility
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] GetFileFromMixPath(string path, FileType fileType, out string filename)
|
||||
{
|
||||
filename = null;
|
||||
string[] pathparts = path.Split('?');
|
||||
if (pathparts.Length < 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string[] mixparts = pathparts[0].Split(';');
|
||||
string[] fileparts = pathparts[1].Split(';');
|
||||
switch (fileType)
|
||||
{
|
||||
case FileType.INI:
|
||||
filename = fileparts[0];
|
||||
break;
|
||||
case FileType.BIN:
|
||||
filename = fileparts.Length < 2 ? String.Empty : fileparts[1];
|
||||
break;
|
||||
}
|
||||
if (String.IsNullOrEmpty(filename))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
using (MixFile mainMix = new MixFile(mixparts[0]))
|
||||
{
|
||||
MixFile openMix = mainMix;
|
||||
int len = mixparts.Length;
|
||||
for (int i = 1; i < len; ++i)
|
||||
{
|
||||
string subMix = mixparts[i];
|
||||
Match mixIdMatch = IdCheck.Match(subMix);
|
||||
uint mixId = 0;
|
||||
bool mixIsId = mixIdMatch.Success;
|
||||
if (mixIsId)
|
||||
{
|
||||
mixId = UInt32.Parse(mixIdMatch.Groups[1].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
}
|
||||
MixEntry[] entries = mixIsId ? openMix.GetFullFileInfo(mixId) : openMix.GetFullFileInfo(subMix);
|
||||
if (entries != null && entries.Length != 0)
|
||||
{
|
||||
MixEntry newmixfile = entries[0];
|
||||
// no need to keep track of those; they don't need to get disposed anyway.
|
||||
openMix = new MixFile(openMix, newmixfile);
|
||||
}
|
||||
}
|
||||
Match idMatch = IdCheck.Match(filename);
|
||||
uint id = 0;
|
||||
bool isId = idMatch.Success;
|
||||
if (isId)
|
||||
{
|
||||
id = UInt32.Parse(idMatch.Groups[1].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
}
|
||||
return isId ? openMix.ReadFile(id) : openMix.ReadFile(filename);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all remaining bytes from a stream behind the current Position. This does not close the stream.
|
||||
/// </summary>
|
||||
|
@ -190,7 +190,7 @@ namespace MobiusEditor.Utility
|
||||
{
|
||||
Byte[][] shpData = ClassicSpriteLoader.GetCcShpData(fileContents, out int width, out int height);
|
||||
mixInfo.Type = MixContentType.ShpTd;
|
||||
mixInfo.Info = String.Format("C&C SHP; {0} frames, {1}x{2}", shpData.Length, width, height);
|
||||
mixInfo.Info = String.Format("C&C SHP; {0} frame{1}, {2}x{3}", shpData.Length, shpData.Length == 1? string.Empty : "s", width, height);
|
||||
return true;
|
||||
}
|
||||
catch (FileTypeLoadException) { /* ignore */ }
|
||||
@ -203,7 +203,7 @@ namespace MobiusEditor.Utility
|
||||
{
|
||||
Byte[][] shpData = ClassicSpriteLoader.GetD2ShpData(fileContents, out int[] widths, out int[] heights);
|
||||
mixInfo.Type = MixContentType.ShpD2;
|
||||
mixInfo.Info = String.Format("Dune II SHP; {0} frames, {1}x{2}", shpData.Length, widths.Max(), heights.Max());
|
||||
mixInfo.Info = String.Format("Dune II SHP; {0} frame{1}, {2}x{3}", shpData.Length, shpData.Length == 1 ? string.Empty : "s", widths.Max(), heights.Max());
|
||||
return true;
|
||||
}
|
||||
catch (FileTypeLoadException) { /* ignore */ }
|
||||
@ -229,7 +229,7 @@ namespace MobiusEditor.Utility
|
||||
{
|
||||
Byte[][] shpData = ClassicSpriteLoader.GetCcTmpData(fileContents, out int[] widths, out int[] heights);
|
||||
mixInfo.Type = MixContentType.TmpTd;
|
||||
mixInfo.Info = String.Format("C&C Template; {0} frames", shpData.Length);
|
||||
mixInfo.Info = String.Format("C&C Template; {0} frame{1}", shpData.Length, shpData.Length == 1 ? string.Empty : "s");
|
||||
return true;
|
||||
}
|
||||
catch (FileTypeLoadException) { /* ignore */ }
|
||||
|
@ -18,7 +18,6 @@ using System.IO;
|
||||
using System.IO.MemoryMappedFiles;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MobiusEditor.Utility
|
||||
{
|
||||
@ -30,9 +29,12 @@ namespace MobiusEditor.Utility
|
||||
private Dictionary<uint, MixEntry[]> mixFileContents = new Dictionary<uint, MixEntry[]>();
|
||||
private HashRol1 hashRol = new HashRol1();
|
||||
|
||||
public string MixFileName { get; private set; }
|
||||
/// <summary>Path the file was loaded from. For embedded mix files, this will be the original path with the deeper opened mix file(s) indicated behind " -> ".</summary>
|
||||
public string FilePath { get; private set; }
|
||||
/// <summary>Filename to display. Will be null if it is loaded by id from inside another mix archive and its name is not known.</summary>
|
||||
public string FileName { get; private set; }
|
||||
/// <summary>File ID in case <see href="FileName"/> is not available.</summary>
|
||||
public uint FileId { get; private set; }
|
||||
public int FileCount { get; private set; }
|
||||
public bool IsNewFormat { get; private set; }
|
||||
public bool IsEmbedded { get; private set; }
|
||||
@ -52,9 +54,9 @@ namespace MobiusEditor.Utility
|
||||
FileInfo mixFile = new FileInfo(mixPath);
|
||||
this.fileStart = 0;
|
||||
this.fileLength = mixFile.Length;
|
||||
this.MixFileName = mixPath;
|
||||
this.FilePath = mixPath;
|
||||
this.FileName = Path.GetFileName(mixPath);
|
||||
this.FileId = hashRol.GetNameId(FileName);
|
||||
this.mixFileMap = MemoryMappedFile.CreateFromFile(
|
||||
new FileStream(mixPath, FileMode.Open, FileAccess.Read, FileShare.Read),
|
||||
null, 0, MemoryMappedFileAccess.Read, HandleInheritability.None, false);
|
||||
@ -95,9 +97,9 @@ namespace MobiusEditor.Utility
|
||||
{
|
||||
throw new FileNotFoundException(name + " was not found inside this mix archive.");
|
||||
}
|
||||
this.MixFileName = container.MixFileName + " -> " + name;
|
||||
this.FilePath = name;
|
||||
this.FileName = name;
|
||||
this.FilePath = container.FilePath + " -> " + name;
|
||||
this.FileName = entry.Name;
|
||||
this.FileId = entry.Id;
|
||||
this.fileStart = actualEntry.Offset;
|
||||
this.fileLength = actualEntry.Length;
|
||||
// Copy reference to parent map. The "CreateViewStream" function takes care of reading the right parts from it.
|
||||
|
289
CnCTDRAMapEditor/Utility/MixPath.cs
Normal file
289
CnCTDRAMapEditor/Utility/MixPath.cs
Normal file
@ -0,0 +1,289 @@
|
||||
using MobiusEditor.Interface;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Remoting.Metadata.W3cXsd2001;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MobiusEditor.Utility
|
||||
{
|
||||
internal class MixPath
|
||||
{
|
||||
/// <summary>
|
||||
/// Mattern to identify a filename as file ID. This can be used to analyse the data returned by <see cref="GetComponents"/>.
|
||||
/// </summary>
|
||||
public static readonly Regex FilePathIdPattern = new Regex("^\\*([0-9A-F]{8})\\*$");
|
||||
|
||||
private static string GetMixFileName(MixFile mixFile)
|
||||
{
|
||||
if (mixFile == null)
|
||||
return String.Empty;
|
||||
if (!mixFile.IsEmbedded)
|
||||
return mixFile.FilePath;
|
||||
return mixFile.FileName ?? ('*' + mixFile.FileId.ToString("X4") + '*');
|
||||
}
|
||||
|
||||
private static string GetMixEntryName(MixEntry file)
|
||||
{
|
||||
return file == null ? String.Empty : file.Name ?? ('*' + file.Id.ToString("X4") + '*');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the mix file chain and mix content entries into the mix file path format "x:\path\mixfile.mix;submix1.mix;submix2.mix?file.ini;file.bin".
|
||||
/// Where names are unavailable, ids will be substituted in the format "*FFFFFFFF*".
|
||||
/// </summary>
|
||||
/// <param name="mixFiles">Chain of opened mix files, starting with the physical file on disk, and continuing with deeper mix files opened inside.</param>
|
||||
/// <param name="files">Files found in the deepest mix file in the chain.</param>
|
||||
/// <returns></returns>
|
||||
public static string BuildMixPath(IEnumerable<MixFile> mixFiles, params MixEntry[] files)
|
||||
{
|
||||
int mixCount = mixFiles.Count();
|
||||
string[] mixArr = new string[mixCount];
|
||||
int ind = 0;
|
||||
foreach (MixFile mixFile in mixFiles)
|
||||
{
|
||||
mixArr[ind++] = GetMixFileName(mixFile);
|
||||
}
|
||||
string[] filesArr = new string[files.Length];
|
||||
for (int i = 0; i < files.Length; i++)
|
||||
{
|
||||
MixEntry file = files[i];
|
||||
filesArr[i] = GetMixEntryName(file);
|
||||
}
|
||||
return String.Join(";", mixArr) + "?" + String.Join(";", filesArr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path components inside the given mix path. Any ids inside the components will be left as they are,
|
||||
/// in hexadecimal format, and surrounded by asterisks, to be easily identifiable with the <see cref="FilePathIdPattern"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">Mix file path block, in the format "x:\path\mixfile.mix;submix1.mix;submix2.mix?file.ini;file.bin"</param>
|
||||
/// <param name="mixParts">The mix files to open to get to the files, starting with the physical file on disk, and continuing with deeper mix files opened inside.</param>
|
||||
/// <param name="filenameParts">The files to open that can be found in the deepest mix file in the chain. Normally a .ini on the first index and a .bin on the second.</param>
|
||||
public static void GetComponents(string path, out string[] mixParts, out string[] filenameParts)
|
||||
{
|
||||
int index = path.IndexOf('?');
|
||||
string mixString = index == -1 ? String.Empty : path.Substring(0, index);
|
||||
mixParts = mixString.Split(';');
|
||||
string filenameString = index == -1 ? String.Empty : path.Substring(index + 1);
|
||||
filenameParts = filenameString.Split(';');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path components inside the given mix path. Any ids inside the components will be returned as UI-viewable strings,
|
||||
/// with the IDs in hexadecimal format, and enclosed in square brackets.
|
||||
/// </summary>
|
||||
/// <param name="path">Mix file path block, in the format "x:\path\mixfile.mix;submix1.mix;submix2.mix?file.ini;file.bin"</param>
|
||||
/// <param name="mixParts">The mix files to open to get to the files, starting with the physical file on disk, and continuing with deeper mix files opened inside.</param>
|
||||
/// <param name="filenameParts">The files to open that can be found in the deepest mix file in the chain. Normally a .ini on the first index and a .bin on the second.</param>
|
||||
/// <returns></returns>
|
||||
public static string GetComponentsViewable(string path, out string[] mixParts, out string[] filenameParts)
|
||||
{
|
||||
GetComponents(path, out mixParts, out filenameParts);
|
||||
// Only check on IDs starting from the second entry; first should be an absolute path.
|
||||
for (int i = 1; i < mixParts.Length; i++)
|
||||
{
|
||||
string mixPart = mixParts[i];
|
||||
Match mixId = FilePathIdPattern.Match(mixPart);
|
||||
if (mixId.Success)
|
||||
{
|
||||
mixParts[i] = "[" + mixId.Groups[1].Value + "]";
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < filenameParts.Length; i++)
|
||||
{
|
||||
string filenamePart = filenameParts[i];
|
||||
Match filenameId = FilePathIdPattern.Match(filenamePart);
|
||||
if (filenameId.Success)
|
||||
{
|
||||
filenameParts[i] = "[" + filenameId.Groups[1].Value + "]";
|
||||
}
|
||||
}
|
||||
return String.Join(";", mixParts) + "?" + String.Join(";", filenameParts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the mix path as a UI-viewable string, with " -> " arrows indicating the internal hierarchy, and any IDs
|
||||
/// enclosed in square brackets. As final component, it returns the first found filename in the filename parts of the mix path block.
|
||||
/// </summary>
|
||||
/// <param name="path">Mix file path block, in the format "x:\path\mixfile.mix;submix1.mix;submix2.mix?file.ini;file.bin"</param>
|
||||
/// <param name="shortPath">true if the path of the original mix file should not be included.</param>
|
||||
/// <param name="nameIsId">returns whether the returned filename is a file ID or a real identified filename.</param>
|
||||
/// <returns>The mix path as a UI-viewable string</returns>
|
||||
public static string GetFileNameReadable(string path, bool shortPath, out bool nameIsId)
|
||||
{
|
||||
nameIsId = false;
|
||||
GetComponents(path, out _, out string[] filenamePartsRaw);
|
||||
GetComponentsViewable(path, out string[] mixParts, out string[] filenameParts);
|
||||
FileInfo fileInfo = new FileInfo(mixParts[0]);
|
||||
string mixString = String.Join(String.Empty, mixParts.Skip(1).Select(mp => " -> " + mp).ToArray());
|
||||
string mixname = shortPath ? fileInfo.Name : fileInfo.FullName;
|
||||
string fullName = mixname + mixString;
|
||||
string loadedName = filenameParts[0];
|
||||
string loadedNameRaw = filenamePartsRaw[0];
|
||||
if (String.IsNullOrEmpty(loadedName) && filenameParts.Length > 1 && !String.IsNullOrEmpty(filenameParts[1]))
|
||||
{
|
||||
// Use the .bin file.
|
||||
loadedName = filenameParts[1];
|
||||
loadedNameRaw = filenamePartsRaw[1];
|
||||
}
|
||||
if (!String.IsNullOrEmpty(loadedName))
|
||||
{
|
||||
nameIsId = MixPath.FilePathIdPattern.IsMatch(loadedNameRaw);
|
||||
fullName += " -> " + loadedName;
|
||||
}
|
||||
return fullName;
|
||||
}
|
||||
|
||||
/// <summary>Opens a mix path and checks if the files involved are all present.</summary>
|
||||
/// <param name="path">Mix file path block, in the format "x:\path\mixfile.mix;submix1.mix;submix2.mix?file.ini;file.bin"</param>
|
||||
/// <param name="fileType">File type to open; INI or BIN, to refer to the first or second internal file (after the question mark in the path).</param>
|
||||
/// <returns>True if the mix file exists, and the internal file could be found inside.</returns>
|
||||
public static bool PathIsValid(string path, FileType fileType)
|
||||
{
|
||||
using (MixFile mainMix = OpenMixPath(path, fileType, out MixFile contentMix, out MixEntry fileEntry))
|
||||
{
|
||||
if (mainMix != null && contentMix != null && fileEntry != null)
|
||||
{
|
||||
// Don't even need to really open it; the fact fileEntry was found is enough.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Opens a mix path and reads the requested file to a byte array.</summary>
|
||||
/// <param name="path">Mix file path block, in the format "x:\path\mixfile.mix;submix1.mix;submix2.mix?file.ini;file.bin"</param>
|
||||
/// <param name="fileType">File type to open; INI or BIN, to refer to the first or second internal file (after the question mark in the path).</param>
|
||||
/// <param name="fileEntry"></param>
|
||||
/// <returns>A byte array containing the file contents of the target file, or null if some component in the chain could not be found.</returns>
|
||||
public static byte[] ReadFile(string path, FileType fileType, out MixEntry fileEntry)
|
||||
{
|
||||
using (MixFile mainMix = OpenMixPath(path, fileType, out MixFile contentMix, out fileEntry))
|
||||
{
|
||||
if (mainMix != null && contentMix != null && fileEntry != null)
|
||||
{
|
||||
return contentMix.ReadFile(fileEntry);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses and opens a mix path. Returns the main mix file that should be disposed after the operation, and has output parameters
|
||||
/// for the actual content mix to request the file on, and the mix content info to use to request the file.
|
||||
/// </summary>
|
||||
/// <param name="path">Mix file path block, in the format "x:\path\mixfile.mix;submix1.mix;submix2.mix?file.ini;file.bin"</param>
|
||||
/// <param name="fileType">File type to open; INI or BIN, to refer to the first or second internal file (after the question mark in the path).</param>
|
||||
/// <param name="contentMix">Output parameter for the actual mix file to request the <paramref name="fileEntry"/> on.</param>
|
||||
/// <param name="fileEntry">Output parameter for the mix entry file info to use to request access to read the file from <paramref name="contentMix"/>. If available as name and not as id, the filename from the parsed path is filled in on this.</param>
|
||||
/// <returns>The main mix file that should be disposed after the file contents have been read from the <paramref name="contentMix"/> file, or null if some component in the chain could not be found.</returns>
|
||||
public static MixFile OpenMixPath(string path, FileType fileType, out MixFile contentMix, out MixEntry fileEntry)
|
||||
{
|
||||
contentMix = null;
|
||||
fileEntry = null;
|
||||
string[] pathparts = path.Split('?');
|
||||
if (pathparts.Length < 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
GetComponents(path, out string[] mixParts, out string[] filenameParts);
|
||||
string baseMixFile = mixParts[0];
|
||||
string filename = null;
|
||||
switch (fileType)
|
||||
{
|
||||
case FileType.INI:
|
||||
filename = filenameParts[0];
|
||||
break;
|
||||
case FileType.BIN:
|
||||
filename = filenameParts.Length < 2 ? String.Empty : filenameParts[1];
|
||||
break;
|
||||
}
|
||||
if (String.IsNullOrEmpty(filename))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!File.Exists(baseMixFile))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
MixFile baseMix;
|
||||
try
|
||||
{
|
||||
baseMix = new MixFile(baseMixFile);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// Set to base mix at first, then the remaining mix file names are looped to find any deeper ones to open.
|
||||
contentMix = baseMix;
|
||||
// If anything goes wrong in the next part, the base mix will be disposed before exiting, so everything is cleaned up.
|
||||
int len = mixParts.Length;
|
||||
for (int i = 1; i < len; ++i)
|
||||
{
|
||||
string subMix = mixParts[i];
|
||||
if (subMix.Length == 0)
|
||||
{
|
||||
try { baseMix.Dispose(); }
|
||||
catch { /* ignore */ }
|
||||
contentMix = null;
|
||||
return null;
|
||||
}
|
||||
Match mixIdMatch = FilePathIdPattern.Match(subMix);
|
||||
uint mixId = 0;
|
||||
bool mixIsId = mixIdMatch.Success;
|
||||
if (mixIsId)
|
||||
{
|
||||
mixId = UInt32.Parse(mixIdMatch.Groups[1].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
}
|
||||
MixEntry[] entries = mixIsId ? contentMix.GetFullFileInfo(mixId) : contentMix.GetFullFileInfo(subMix);
|
||||
if (entries == null || entries.Length == 0)
|
||||
{
|
||||
try { baseMix.Dispose(); }
|
||||
catch { /* ignore */ }
|
||||
contentMix = null;
|
||||
return null;
|
||||
}
|
||||
MixEntry newmixfile = entries[0];
|
||||
// no need to keep track of those; they don't need to get disposed anyway.
|
||||
try
|
||||
{
|
||||
contentMix = new MixFile(contentMix, newmixfile);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try { baseMix.Dispose(); }
|
||||
catch { /* ignore */ }
|
||||
contentMix = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Match idMatch = FilePathIdPattern.Match(filename);
|
||||
uint id = 0;
|
||||
bool isId = idMatch.Success;
|
||||
if (isId)
|
||||
{
|
||||
id = UInt32.Parse(idMatch.Groups[1].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
}
|
||||
MixEntry[] fileEntries = isId ? contentMix.GetFullFileInfo(id) : contentMix.GetFullFileInfo(filename);
|
||||
if (fileEntries == null || fileEntries.Length == 0)
|
||||
{
|
||||
try { baseMix.Dispose(); }
|
||||
catch { /* ignore */ }
|
||||
contentMix = null;
|
||||
return null;
|
||||
}
|
||||
fileEntry = fileEntries[0];
|
||||
if (!isId)
|
||||
{
|
||||
fileEntry.Name = filename;
|
||||
}
|
||||
return baseMix;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user