public bool AddItem(string path, CacheMethod m = CacheMethod.Default) { // directory if (Directory.Exists(path)) { AllArchives.Add(new DirArchive(path)); Logger.Trace("Added <DirArchive> {0} to VFS", path); return(true); } // regular file else if (File.Exists(path)) { var fi = new FileInfo(path); // mix file if (FormatHelper.MixArchiveExtensions.Contains(fi.Extension, StringComparer.InvariantCultureIgnoreCase)) { var mf = new MixFile(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)); mf.FileName = path; AllArchives.Add(mf); Logger.Trace("Added <MixFile> {0} to VFS", path); return(true); } } // virtual mix file else if (FileExists(path)) { var mx = Open(path, FileFormat.Mix) as MixFile; AllArchives.Add(mx); Logger.Trace("Added <VirtualMixFile> {0} to VFS", path); return(true); } return(false); }
void IUtilityCommand.Run(Utility utility, string[] args) { var filename = Path.GetFileName(args[1]); var path = Path.GetDirectoryName(args[1]); var fs = new FileSystem.FileSystem(utility.Mods); // Needed to access the global mix database fs.LoadFromManifest(utility.ModData.Manifest); fs.Mount(path, "parent"); var package = new MixFile(fs, "parent|" + filename); foreach (var kv in package.Index.OrderBy(kv => kv.Value.Offset)) { Console.WriteLine("{0}:", kv.Key); Console.WriteLine("\tOffset: {0}", kv.Value.Offset); Console.WriteLine("\tLength: {0}", kv.Value.Length); } }
bool IPackageLoader.TryParsePackage(Stream s, string filename, FS context, out IReadOnlyPackage package) { if (!filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase)) { package = null; return(false); } // Load the global mix database if (globalFilenames == null) { if (context.TryOpen("global mix database.dat", out var mixDatabase)) { using (var db = new XccGlobalDatabase(mixDatabase)) globalFilenames = db.Entries.Distinct().ToArray(); } } package = new MixFile(s, filename, globalFilenames ?? Array.Empty <string>()); return(true); }
bool IPackageLoader.TryParsePackage(Stream s, string filename, FS context, out IReadOnlyPackage package) { if (!filename.EndsWith(".mix", StringComparison.InvariantCultureIgnoreCase)) { package = null; return(false); } // Load the global mix database var allPossibleFilenames = new HashSet <string>(); if (context.TryOpen("global mix database.dat", out var mixDatabase)) { using (var db = new XccGlobalDatabase(mixDatabase)) foreach (var e in db.Entries) { allPossibleFilenames.Add(e); } } package = new MixFile(s, filename, allPossibleFilenames); return(true); }
/// <summary>Gets the determine map name. </summary> /// <returns>The filename to save the map as</returns> public static string DetermineMapName(MapFile map, EngineType engine) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(map.FileName); IniFile.IniSection basic = map.GetSection("Basic"); if (basic.ReadBool("Official") == false) { return(StripPlayersFromName(basic.ReadString("Name", fileNameWithoutExtension))); } string mapExt = Path.GetExtension(Settings.InputFile); string missionName = ""; string mapName = ""; PktFile.PktMapEntry pktMapEntry = null; MissionsFile.MissionEntry missionEntry = null; // campaign mission if (!basic.ReadBool("MultiplayerOnly") && basic.ReadBool("Official")) { string missionsFile; switch (engine) { case EngineType.TiberianSun: case EngineType.RedAlert2: missionsFile = "mission.ini"; break; case EngineType.Firestorm: missionsFile = "mission1.ini"; break; case EngineType.YurisRevenge: missionsFile = "missionmd.ini"; break; default: throw new ArgumentOutOfRangeException("engine"); } var mf = VFS.Open <MissionsFile>(missionsFile); missionEntry = mf.GetMissionEntry(Path.GetFileName(map.FileName)); if (missionEntry != null) { missionName = (engine >= EngineType.RedAlert2) ? missionEntry.UIName : missionEntry.Name; } } else { // multiplayer map string pktEntryName = fileNameWithoutExtension; PktFile pkt = null; if (FormatHelper.MixArchiveExtensions.Contains(mapExt)) { // this is an 'official' map 'archive' containing a PKT file with its name try { var mix = new MixFile(File.Open(Settings.InputFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); pkt = mix.OpenFile(fileNameWithoutExtension + ".pkt", FileFormat.Pkt) as PktFile; // pkt file is cached by default, so we can close the handle to the file mix.Close(); if (pkt != null && pkt.MapEntries.Count > 0) { pktEntryName = pkt.MapEntries.First().Key; } } catch (ArgumentException) { } } else { // determine pkt file based on engine switch (engine) { case EngineType.TiberianSun: case EngineType.RedAlert2: pkt = VFS.Open <PktFile>("missions.pkt"); break; case EngineType.Firestorm: pkt = VFS.Open <PktFile>("multi01.pkt"); break; case EngineType.YurisRevenge: pkt = VFS.Open <PktFile>("missionsmd.pkt"); break; default: throw new ArgumentOutOfRangeException("engine"); } } // fallback for multiplayer maps with, .map extension, // no YR objects so assumed to be ra2, but actually meant to be used on yr if (mapExt == ".map" && pkt != null && !pkt.MapEntries.ContainsKey(pktEntryName) && engine >= EngineType.RedAlert2) { var vfs = new VFS(); vfs.AddFile(Settings.InputFile); pkt = vfs.OpenFile <PktFile>("missionsmd.pkt"); } if (pkt != null && !string.IsNullOrEmpty(pktEntryName)) { pktMapEntry = pkt.GetMapEntry(pktEntryName); } } // now, if we have a map entry from a PKT file, // for TS we are done, but for RA2 we need to look in the CSV file for the translated mapname if (engine <= EngineType.Firestorm) { if (pktMapEntry != null) { mapName = pktMapEntry.Description; } else if (missionEntry != null) { if (engine == EngineType.TiberianSun) { string campaignSide; string missionNumber; if (missionEntry.Briefing.Length >= 3) { campaignSide = missionEntry.Briefing.Substring(0, 3); missionNumber = missionEntry.Briefing.Length > 3 ? missionEntry.Briefing.Substring(3) : ""; missionName = ""; mapName = string.Format("{0} {1} - {2}", campaignSide, missionNumber.TrimEnd('A').PadLeft(2, '0'), missionName); } else if (missionEntry.Name.Length >= 10) { mapName = missionEntry.Name; } } else { // FS map names are constructed a bit easier mapName = missionName.Replace(":", " - "); } } else if (!string.IsNullOrEmpty(basic.ReadString("Name"))) { mapName = basic.ReadString("Name", fileNameWithoutExtension); } } // if this is a RA2/YR mission (csfEntry set) or official map with valid pktMapEntry else if (missionEntry != null || pktMapEntry != null) { string csfEntryName = missionEntry != null ? missionName : pktMapEntry.Description; string csfFile = engine == EngineType.YurisRevenge ? "ra2md.csf" : "ra2.csf"; Logger.Info("Loading csf file {0}", csfFile); var csf = VFS.Open <CsfFile>(csfFile); mapName = csf.GetValue(csfEntryName.ToLower()); if (missionEntry != null) { if (mapName.Contains("Operation: ")) { string missionMapName = Path.GetFileName(map.FileName); if (char.IsDigit(missionMapName[3]) && char.IsDigit(missionMapName[4])) { string missionNr = Path.GetFileName(map.FileName).Substring(3, 2); mapName = mapName.Substring(0, mapName.IndexOf(":")) + " " + missionNr + " -" + mapName.Substring(mapName.IndexOf(":") + 1); } } } else { // not standard map if ((pktMapEntry.GameModes & PktFile.GameMode.Standard) == 0) { if ((pktMapEntry.GameModes & PktFile.GameMode.Megawealth) == PktFile.GameMode.Megawealth) { mapName += " (Megawealth)"; } if ((pktMapEntry.GameModes & PktFile.GameMode.Duel) == PktFile.GameMode.Duel) { mapName += " (Land Rush)"; } if ((pktMapEntry.GameModes & PktFile.GameMode.NavalWar) == PktFile.GameMode.NavalWar) { mapName += " (Naval War)"; } } } } // not really used, likely empty, but if this is filled in it's probably better than guessing if (mapName == "" && basic.SortedEntries.ContainsKey("Name")) { mapName = basic.ReadString("Name"); } if (mapName == "") { Logger.Warn("No valid mapname given or found, reverting to default filename {0}", fileNameWithoutExtension); mapName = fileNameWithoutExtension; } else { Logger.Info("Mapname found: {0}", mapName); } mapName = StripPlayersFromName(MakeValidFileName(mapName)).Replace(" ", " "); return(mapName); }
public static int Main(string[] args) { InitLoggerConfig(); InitSettings(args); // DumpTileProperties(); if (!ValidateSettings()) { return(2); } try { Logger.Info("Initializing virtual filesystem"); var mapStream = File.Open(Settings.InputFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); VirtualFile vmapFile; var mixMap = new MixFile(mapStream, Settings.InputFile, 0, mapStream.Length, false, false); if (mixMap.IsValid()) // input max is a mix { var mapArchive = new MixFile(mapStream, Path.GetFileName(Settings.InputFile), true); // grab the largest file in the archive var mixEntry = mapArchive.Index.OrderByDescending(me => me.Value.Length).First(); vmapFile = mapArchive.OpenFile(mixEntry.Key); } else { vmapFile = new VirtualFile(mapStream, Path.GetFileName(Settings.InputFile), true); } var mapFile = new MapFile(vmapFile, Path.GetFileName(Settings.InputFile)); if (!string.IsNullOrEmpty(Settings.ModConfig)) { if (File.Exists(Settings.ModConfig)) { ModConfig cfg; try { using (FileStream f = File.OpenRead(Settings.ModConfig)) cfg = ModConfig.Deserialize(f); ModConfig.ActiveConfig = cfg; if (Settings.Engine != EngineType.AutoDetect) { if (Settings.Engine != cfg.Engine) { Logger.Warn("Provided engine override does not match mod config."); } } else { Settings.Engine = ModConfig.ActiveConfig.Engine; } } catch (IOException) { Logger.Error("IOException while loading mod config"); } catch (XmlException) { Logger.Error("XmlException while loading mod config"); } catch (SerializationException) { Logger.Error("Serialization exception while loading mod config"); } } else { Logger.Error("Invalid mod config file specified"); } } if (Settings.Engine == EngineType.AutoDetect) { Settings.Engine = EngineDetector.DetectEngineType(mapFile); Logger.Info("Engine autodetect result: {0}", Settings.Engine); } // --------------------------------------------------------------- // Code to organize moving of maps in a directory for themselves /* * string mapName = DetermineMapName(mapFile, Settings.Engine); * string ndir = Path.Combine(Path.GetDirectoryName(Settings.InputFile), mapName); * if (!Directory.Exists(ndir)) Directory.CreateDirectory(ndir); * mapFile.Close(); * mapFile.Dispose(); * File.Move(Settings.InputFile, Path.Combine(ndir, Path.GetFileName(mapFile.FileName))); * return 0;*/ // --------------------------------------------------------------- // enginetype is now definitive, load mod config if (ModConfig.ActiveConfig == null) { ModConfig.LoadDefaultConfig(Settings.Engine); } // first add the dirs, then load the extra mixes, then scan the dirs foreach (string modDir in ModConfig.ActiveConfig.Directories) { VFS.Add(modDir); } // add mixdir to VFS (if it's not included in the mod config) if (!ModConfig.ActiveConfig.Directories.Any()) { string mixDir = VFS.DetermineMixDir(Settings.MixFilesDirectory, Settings.Engine); VFS.Add(mixDir); } foreach (string mixFile in ModConfig.ActiveConfig.ExtraMixes) { VFS.Add(mixFile); } foreach (var dir in VFS.Instance.AllArchives.OfType <DirArchive>().Select(d => d.Directory).ToList()) { VFS.Instance.ScanMixDir(dir, Settings.Engine); } var map = new Map { IgnoreLighting = Settings.IgnoreLighting, StartPosMarking = Settings.StartPositionMarking, MarkOreFields = Settings.MarkOreFields }; if (!map.Initialize(mapFile, Settings.Engine)) { Logger.Error("Could not successfully load this map. Try specifying the engine type manually."); return(1); } if (!map.LoadTheater()) { Logger.Error("Could not successfully load all required components for this map. Aborting."); return(1); } if (Settings.StartPositionMarking == StartPositionMarking.Tiled) { map.MarkTiledStartPositions(); } if (Settings.MarkOreFields) { map.MarkOreAndGems(); } map.Draw(); #if DEBUG // ==================================================================================== using (var form = new DebugDrawingSurfaceWindow(map.GetDrawingSurface(), map.GetTiles(), map.GetTheater(), map)) { form.RequestTileEvaluate += map.DebugDrawTile; form.ShowDialog(); } // ==================================================================================== #endif if (Settings.StartPositionMarking == StartPositionMarking.Squared) { map.DrawSquaredStartPositions(); } if (Settings.OutputFile == "") { Settings.OutputFile = DetermineMapName(mapFile, Settings.Engine); } if (Settings.OutputDir == "") { Settings.OutputDir = Path.GetDirectoryName(Settings.InputFile); } // free up as much memory as possible before saving the large images Rectangle saveRect = map.GetSizePixels(Settings.SizeMode); DrawingSurface ds = map.GetDrawingSurface(); // if we don't need this data anymore, we can try to save some memory if (!Settings.GeneratePreviewPack) { ds.FreeNonBitmap(); map.FreeUseless(); GC.Collect(); } if (Settings.SaveJPEG) { ds.SaveJPEG(Path.Combine(Settings.OutputDir, Settings.OutputFile + ".jpg"), Settings.JPEGCompression, saveRect); } if (Settings.SavePNG) { ds.SavePNG(Path.Combine(Settings.OutputDir, Settings.OutputFile + ".png"), Settings.PNGQuality, saveRect); } Regex reThumb = new Regex(@"(\+)?\((\d+),(\d+)\)"); var match = reThumb.Match(Settings.ThumbnailSettings); if (match.Success) { Size dimensions = new Size( int.Parse(match.Groups[2].Captures[0].Value), int.Parse(match.Groups[3].Captures[0].Value)); var cutRect = map.GetSizePixels(Settings.SizeMode); if (match.Groups[1].Captures[0].Value == "+") { // + means maintain aspect ratio double aspectRatio = cutRect.Width / (double)cutRect.Height; if (dimensions.Width / (double)dimensions.Height > aspectRatio) { dimensions.Height = (int)(dimensions.Width / aspectRatio); } else { dimensions.Width = (int)(dimensions.Height / aspectRatio); } } Logger.Info("Saving thumbnail with dimensions {0}x{1}", dimensions.Width, dimensions.Height); ds.SaveThumb(dimensions, cutRect, Path.Combine(Settings.OutputDir, "thumb_" + Settings.OutputFile + ".jpg")); } if (Settings.GeneratePreviewPack) { if (mapFile.BaseStream is MixFile) { Logger.Error("Cannot inject thumbnail into an archive (.mmx/.yro/.mix)!"); } else { map.GeneratePreviewPack(Settings.OmitPreviewPackMarkers, Settings.SizeMode, mapFile); Logger.Info("Saving map"); mapFile.Save(Settings.InputFile); } } } catch (Exception exc) { Logger.Error(string.Format("An unknown fatal exception occured: {0}", exc), exc); #if DEBUG throw; #else return(1); #endif } LogManager.Configuration = null; // required for mono release to flush possible targets return(0); }
public bool AddMix(MixFile mix) { AllArchives.Add(mix); return(true); }
public EngineResult Execute() { try { _logger.Info("Initializing virtual filesystem"); var mapStream = File.Open(_settings.InputFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); VirtualFile vmapFile; var mixMap = new MixFile(mapStream, _settings.InputFile, 0, mapStream.Length, false, false); if (mixMap.IsValid()) // input max is a mix { var mapArchive = new MixFile(mapStream, Path.GetFileName(_settings.InputFile), true); // grab the largest file in the archive var mixEntry = mapArchive.Index.OrderByDescending(me => me.Value.Length).First(); vmapFile = mapArchive.OpenFile(mixEntry.Key); } else { vmapFile = new VirtualFile(mapStream, Path.GetFileName(_settings.InputFile), true); } var mapFile = new MapFile(vmapFile, Path.GetFileName(_settings.InputFile)); ModConfig modConfig = null; if (!string.IsNullOrEmpty(_settings.ModConfig)) { if (File.Exists(_settings.ModConfig)) { try { using (FileStream f = File.OpenRead(_settings.ModConfig)) modConfig = ModConfig.Deserialize(f); } catch (IOException) { _logger.Fatal("IOException while loading mod config"); } catch (XmlException) { _logger.Fatal("XmlException while loading mod config"); } catch (SerializationException) { _logger.Fatal("Serialization exception while loading mod config"); } } else { _logger.Fatal("Invalid mod config file specified"); } } if (_settings.Engine == EngineType.AutoDetect) { _settings.Engine = EngineDetector.DetectEngineType(mapFile); _logger.Info("Engine autodetect result: {0}", _settings.Engine); } // Engine type is now definitive, load mod config if (modConfig == null) { modConfig = ModConfig.GetDefaultConfig(_settings.Engine); } var map = new Map.Map { IgnoreLighting = _settings.IgnoreLighting, StartPosMarking = _settings.StartPositionMarking, StartMarkerSize = _settings.MarkerStartSize, MarkOreFields = _settings.MarkOreFields }; using (var vfs = new VirtualFileSystem()) { // first add the dirs, then load the extra mixes, then scan the dirs foreach (string modDir in modConfig.Directories) { vfs.Add(modDir); } // add mixdir to VFS (if it's not included in the mod config) if (!modConfig.Directories.Any()) { string mixDir = VirtualFileSystem.DetermineMixDir(_settings.MixFilesDirectory, _settings.Engine); vfs.Add(mixDir); } foreach (string mixFile in modConfig.ExtraMixes) { vfs.Add(mixFile); } vfs.LoadMixes(_settings.Engine); if (!map.Initialize(mapFile, modConfig, vfs)) { _logger.Error("Could not successfully load this map. Try specifying the engine type manually."); return(EngineResult.LoadRulesFailed); } if (!map.LoadTheater()) { _logger.Error("Could not successfully load all required components for this map. Aborting."); return(EngineResult.LoadTheaterFailed); } if (_settings.MarkStartPos && _settings.StartPositionMarking == StartPositionMarking.Tiled) { map.MarkTiledStartPositions(); } if (_settings.MarkOreFields) { map.MarkOreAndGems(); } if ((_settings.GeneratePreviewPack || _settings.FixupTiles || _settings.FixOverlays || _settings.CompressTiles) && _settings.Backup) { if (mapFile.BaseStream is MixFile) { _logger.Error("Cannot generate a map file backup into an archive (.mmx/.yro/.mix)!"); } else { try { string timestamp = DateTime.Now.ToString("yyyyMMddHHmmss"); string fileInput = Path.Combine(Path.GetDirectoryName(_settings.InputFile), Path.GetFileName(_settings.InputFile)); fileInput = fileInput.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string fileInputNoExtn = Path.Combine(Path.GetDirectoryName(_settings.InputFile), Path.GetFileNameWithoutExtension(_settings.InputFile)); fileInputNoExtn = fileInputNoExtn.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string fileBackup = fileInputNoExtn + "_" + timestamp + ".bkp"; File.Copy(fileInput, fileBackup, true); _logger.Info("Creating map backup: " + fileBackup); } catch (Exception) { _logger.Error("Unable to generate a map file backup!"); } } } if (_settings.FixupTiles) { map.FixupTileLayer(); } map.Draw(); if (_settings.MarkIceGrowth) { map.MarkIceGrowth(); } if (_settings.TunnelPaths) { map.PlotTunnels(_settings.TunnelPosition); } if (_settings.MarkStartPos && (_settings.StartPositionMarking == StartPositionMarking.Squared || _settings.StartPositionMarking == StartPositionMarking.Circled || _settings.StartPositionMarking == StartPositionMarking.Diamond || _settings.StartPositionMarking == StartPositionMarking.Ellipsed || _settings.StartPositionMarking == StartPositionMarking.Starred)) { map.DrawStartPositions(); } if (_settings.OutputFile == "") { _settings.OutputFile = DetermineMapName(mapFile, _settings.Engine, vfs); } if (_settings.OutputDir == "") { _settings.OutputDir = Path.GetDirectoryName(_settings.InputFile); } if (_settings.DiagnosticWindow) { using (var form = new DebugDrawingSurfaceWindow(map.GetDrawingSurface(), map.GetTiles(), map.GetTheater(), map)) { form.RequestTileEvaluate += map.DebugDrawTile; form.ShowDialog(); } } } // VFS resources can now be released // free up as much memory as possible before saving the large images Rectangle saveRect = map.GetSizePixels(_settings.SizeMode); DrawingSurface ds = map.GetDrawingSurface(); saveRect.Intersect(new Rectangle(0, 0, ds.Width, ds.Height)); // if we don't need this data anymore, we can try to save some memory if (!_settings.GeneratePreviewPack) { ds.FreeNonBitmap(); map.FreeUseless(); GC.Collect(); } if (_settings.SaveJPEG) { ds.SaveJPEG(Path.Combine(_settings.OutputDir, _settings.OutputFile + ".jpg"), _settings.JPEGCompression, saveRect); } if (_settings.SavePNG) { ds.SavePNG(Path.Combine(_settings.OutputDir, _settings.OutputFile + ".png"), _settings.PNGQuality, saveRect); } Regex reThumb = new Regex(@"(\+|)?\((\d+),(\d+)\)"); var match = reThumb.Match(_settings.ThumbnailConfig); if (match.Success) { Size dimensions = new Size( int.Parse(match.Groups[2].Captures[0].Value), int.Parse(match.Groups[3].Captures[0].Value)); var cutRect = map.GetSizePixels(_settings.SizeMode); if (match.Groups[1].Captures[0].Value == "+") { // + means maintain aspect ratio if (dimensions.Width > 0 && dimensions.Height > 0) { float scaleHeight = (float)dimensions.Height / (float)cutRect.Height; float scaleWidth = (float)dimensions.Width / (float)cutRect.Width; float scale = Math.Min(scaleHeight, scaleWidth); dimensions.Width = Math.Max((int)(cutRect.Width * scale), 1); dimensions.Height = Math.Max((int)(cutRect.Height * scale), 1); } else { double aspectRatio = cutRect.Width / (double)cutRect.Height; if (dimensions.Width / (double)dimensions.Height > aspectRatio) { dimensions.Height = (int)(dimensions.Width / aspectRatio); } else { dimensions.Width = (int)(dimensions.Height * aspectRatio); } } } _logger.Info("Saving thumbnail with dimensions {0}x{1}", dimensions.Width, dimensions.Height); if (!_settings.SavePNGThumbnails) { ds.SaveThumb(dimensions, cutRect, Path.Combine(_settings.OutputDir, "thumb_" + _settings.OutputFile + ".jpg")); } else { ds.SaveThumb(dimensions, cutRect, Path.Combine(_settings.OutputDir, "thumb_" + _settings.OutputFile + ".png"), true); } } if (_settings.GeneratePreviewPack || _settings.FixupTiles || _settings.FixOverlays || _settings.CompressTiles) { if (mapFile.BaseStream is MixFile) { _logger.Error( "Cannot fix tile layer or inject thumbnail into an archive (.mmx/.yro/.mix)!"); } else { if (_settings.GeneratePreviewPack) { map.GeneratePreviewPack(_settings.PreviewMarkers, _settings.SizeMode, mapFile, _settings.FixPreviewDimensions); } if (_settings.FixOverlays) { map.FixupOverlays(); // fixing is done earlier, it now creates overlay and its data pack } // Keep this last in tiles manipulation if (_settings.CompressTiles) { map.CompressIsoMapPack5(); } _logger.Info("Saving map to " + _settings.InputFile); mapFile.Save(_settings.InputFile); } } } catch (Exception exc) { _logger.Error(string.Format("An unknown fatal exception occurred: {0}", exc), exc); #if DEBUG throw; #endif return(EngineResult.Exception); } return(EngineResult.RenderedOk); }