public new MTexture GetAtlasSubtexturesAt(string key, int index) { if (orderedTexturesCache.TryGetValue(key, out List <MTexture> list)) { if (index < 0 || index >= list.Count) { Logger.Log(LogLevel.Warn, "Atlas", $"Requested atlas subtexture that falls out of range: {key} {index}"); return(GetFallback()); } return(list[index]); } MTexture result = GetAtlasSubtextureFromAtlasAt(key, index); if (result == null) { MTexture fallback = GetFallback(); if (fallback != null) { // SpriteData and other places use GetAtlasSubtextureAt to check if textures exist. // Logging this verbosely when no fallback exists doesn't make sense in those cases. Logger.Log(LogLevel.Warn, "Atlas", $"Requested atlas subtexture that does not exist: {key} {index}"); return(fallback); } } return(result); }
public new List <MTexture> GetAtlasSubtextures(string key) { List <MTexture> result = orig_GetAtlasSubtextures(key); if (result == null || result.Count == 0) { Logger.Log(LogLevel.Warn, "Atlas.GetAtlasSubtextures", $"Requested atlas subtextures but none were found: {key}"); } return(result); }
// log missing texture when getting one by ID (for example, tilesets) public new MTexture this[string id] { [MonoModReplace] get { if (!textures.ContainsKey(id)) { Logger.Log(LogLevel.Warn, "Atlas", $"Requested texture that does not exist: {id}"); } return(textures[id]); } // we don't want to modify the setter, but want it to exist in the patch class so that we can call it from within our patches. [MonoModIgnore] set { } }
// log missing texture when getting one by ID (for example, tilesets) public new MTexture this[string id] { [MonoModReplace] get { if (!textures.TryGetValue(id, out MTexture result)) { Logger.Log(LogLevel.Warn, "Atlas", $"Requested texture that does not exist: {id}"); return(GetFallback()); } return(result); } // we don't want to modify the setter, but want it to exist in the patch class so that we can call it from within our patches. [MonoModIgnore] set { } }
public new List <MTexture> GetAtlasSubtextures(string key) { PushFallback(null); List <MTexture> result = orig_GetAtlasSubtextures(key); PopFallback(); if (result == null || result.Count == 0) { Logger.Log(LogLevel.Warn, "Atlas", $"Requested atlas subtextures but none were found: {key}"); MTexture fallback = GetFallback(); if (fallback != null) { return new List <MTexture>() { fallback } } ; } return(result); }
/// <summary> /// Feed the given ModAsset into the atlas. /// </summary> public static void Ingest(this Atlas self, ModAsset asset) { // Crawl through all child assets. if (asset.Type == typeof(AssetTypeDirectory)) { foreach (ModAsset child in asset.Children) { self.Ingest(child); } return; } // Forcibly add the mod content to the atlas. if (asset.Type == typeof(Texture2D)) { Logger.Log(LogLevel.Verbose, "Atlas.Ingest", $"{self.GetDataPath()} + {asset.PathVirtual}"); string parentPath = self.GetDataPath(); if (parentPath.StartsWith(Everest.Content.PathContentOrig)) { parentPath = parentPath.Substring(Everest.Content.PathContentOrig.Length + 1); } parentPath = parentPath.Replace('\\', '/'); bool lq = false; string path = asset.PathVirtual; if (path.StartsWith(parentPath + "LQ/")) { lq = true; path = path.Substring(parentPath.Length + 3); } else if (path.StartsWith(parentPath + "/")) { path = path.Substring(parentPath.Length + 1); } else { return; } VirtualTexture vtex = VirtualContentExt.CreateTexture(asset); MTexture mtex; MTextureMeta meta = asset.GetMeta <MTextureMeta>(); if (meta != null) { if (meta.Width == 0) { meta.Width = vtex.Width; } if (meta.Height == 0) { meta.Height = vtex.Height; } } Dictionary <string, MTexture> textures = self.GetTextures(); MTexture existing; if (textures.TryGetValue(path, out existing)) { if (lq && !CoreModule.Settings.LQAtlas) { return; } if (existing.Texture.GetMetadata() == asset) { return; // We're the currently active overlay. } if (meta != null) { // Apply width and height from existing meta. existing.AddOverride(vtex, new Vector2(meta.X, meta.Y), meta.Width, meta.Height); } else { // Keep width and height from existing instance. existing.AddOverride(vtex, existing.DrawOffset, existing.Width, existing.Height); } mtex = existing; } else { if (meta != null) { // Apply width and height from existing meta. mtex = new MTexture(vtex, new Vector2(meta.X, meta.Y), meta.Width, meta.Height); } else { // Apply width and height from replacement texture. mtex = new MTexture(vtex); } mtex.SetAtlasPath(path); } VTextureToMTextureMap[vtex.Name] = mtex; self[path] = mtex; if (!self.Sources.Contains(vtex)) { self.Sources.Add(vtex); } return; } }
public void Ingest(ModAsset asset) { if (asset == null) { return; } // Crawl through all child assets. if (asset.Type == typeof(AssetTypeDirectory)) { lock (asset.Children) { foreach (ModAsset child in asset.Children) { Ingest(child); } } return; } // Forcibly add the mod content to the atlas. if (asset.Type != typeof(Texture2D)) { return; } string path = asset.PathVirtual; if (!path.StartsWith(RelativeDataPath)) { return; } path = path.Substring(RelativeDataPath.Length); if (textures.TryGetValue(path, out MTexture mtex)) { Logger.Log(LogLevel.Verbose, "Atlas.Ingest", $"{Path.GetFileName(DataPath)} + ({asset.Source?.Name ?? "???"}) {path}"); mtex.SetOverride(asset); this[path] = mtex; return; } VirtualTexture vtex; try { vtex = VirtualContentExt.CreateTexture(asset); } catch { // The game is going to crash from this. Log the offending texture to make debugging easier. Logger.Log(LogLevel.Verbose, "Atlas.Ingest", $"Error while loading texture {path} ({asset.Source?.Name ?? "???"}) into atlas {Path.GetFileName(DataPath)}"); throw; } MTextureMeta meta = asset.GetMeta <MTextureMeta>(); if (meta != null) { // Apply width and height from meta. if (meta.Width == 0) { meta.Width = vtex.Width; } if (meta.Height == 0) { meta.Height = vtex.Height; } mtex = new MTexture(vtex, new Vector2(meta.X, meta.Y), meta.Width, meta.Height); } else { // Apply width and height from replacement texture. mtex = new MTexture(vtex); } mtex.AtlasPath = path; mtex.SetAtlas(this); mtex.SetOverride(asset); this[path] = mtex; Sources.Add(vtex); }
public static void Ingest(this Atlas self, AssetMetadata asset) { Logger.Log("Atlas.Ingest", $"{self.GetDataPath()} + {asset.PathRelative}"); // Crawl through all child assets. if (asset.AssetType == typeof(AssetTypeDirectory)) { foreach (AssetMetadata child in asset.Children) { self.Ingest(child); } return; } // Forcibly add the mod content to the atlas. if (asset.AssetType == typeof(Texture2D)) { string parentPath = self.GetDataPath(); if (parentPath.StartsWith(Everest.Content.PathContentOrig)) { parentPath = parentPath.Substring(Everest.Content.PathContentOrig.Length + 1); } parentPath = parentPath.Replace('\\', '/'); string path = asset.PathRelative; if (!path.StartsWith(parentPath)) { return; } path = path.Substring(parentPath.Length + 1); VirtualTexture replacementV = VirtualContentExt.CreateTexture(asset); MTexture replacement; MTextureMeta meta = asset.GetMeta <MTextureMeta>(); Dictionary <string, MTexture> textures = self.GetTextures(); MTexture existing; if (textures.TryGetValue(path, out existing)) { // We're the currently active overlay. if (existing.Texture.GetMetadata() == asset) { return; } if (meta != null) { // Apply width and height from existing meta. existing.AddOverlay(replacementV, new Vector2(meta.X, meta.Y), meta.Width, meta.Height); } else { // Keep width and height from existing instance. existing.AddOverlay(replacementV, existing.DrawOffset, existing.Width, existing.Height); } replacement = existing; } else { if (meta != null) { // Apply width and height from existing meta. replacement = new MTexture(replacementV, new Vector2(meta.X, meta.Y), meta.Width, meta.Height); } else { // Apply width and height from replacement texture. replacement = new MTexture(replacementV); } // TODO: What's with the AtlasPath? Seems to stem from an atlas metadata property... } self[path] = replacement; return; } }
/// <summary> /// Feed the given ModAsset into the atlas. /// </summary> public static void Ingest(this Atlas atlas, ModAsset asset) { if (asset == null) { return; } // Crawl through all child assets. if (asset.Type == typeof(AssetTypeDirectory)) { lock (asset.Children) { foreach (ModAsset child in asset.Children) { atlas.Ingest(child); } } return; } // Forcibly add the mod content to the atlas. if (asset.Type != typeof(Texture2D)) { return; } string parentPath = atlas.GetDataPath(); if (parentPath.StartsWith(Everest.Content.PathContentOrig)) { parentPath = parentPath.Substring(Everest.Content.PathContentOrig.Length + 1); } parentPath = parentPath.Replace('\\', '/'); string path = asset.PathVirtual; if (!path.StartsWith(parentPath + "/")) { return; } path = path.Substring(parentPath.Length + 1); Logger.Log(LogLevel.Verbose, "Atlas.Ingest", $"{Path.GetFileName(atlas.GetDataPath())} + ({asset.Source?.Name ?? "???"}) {path}"); MTexture mtex; Dictionary <string, MTexture> textures = atlas.GetTextures(); if (textures.TryGetValue(path, out mtex)) { mtex.SetOverride(asset); } else { VirtualTexture vtex = VirtualContentExt.CreateTexture(asset); MTextureMeta meta = asset.GetMeta <MTextureMeta>(); if (meta != null) { // Apply width and height from meta. if (meta.Width == 0) { meta.Width = vtex.Width; } if (meta.Height == 0) { meta.Height = vtex.Height; } mtex = new MTexture(vtex, new Vector2(meta.X, meta.Y), meta.Width, meta.Height); } else { // Apply width and height from replacement texture. mtex = new MTexture(vtex); } mtex.AtlasPath = path; mtex.SetAtlas(atlas); mtex.SetOverride(asset); } atlas.ResetCaches(); atlas[path] = mtex; }
private Source _RequestStart() { Entries = null; ErrorDialog = null; string data; try { using (WebClient wc = new WebClient()) data = wc.DownloadString(Index); } catch (Exception e) { ErrorDialog = "updater_versions_err_download"; Logger.Log(LogLevel.Warn, "updater", "Failed requesting index: " + e.ToString()); return(this); } List <Entry> entries = new List <Entry>(); if (ParseData != null) { try { entries.AddRange(ParseData(data)); } catch (Exception e) { ErrorDialog = "updater_versions_err_format"; Logger.Log(LogLevel.Warn, "updater", "Failed parsing index: " + e.ToString()); return(this); } } else { string[] lines = data.Split('\n'); for (int i = 0; i < lines.Length; i++) { string line = lines[i].Trim('\r', '\n').Trim(); if (line.Length == 0 || line.StartsWith("#")) { continue; } try { Entry entry = ParseLine(line); if (entry != null) { entries.Add(entry); } } catch (Exception e) { ErrorDialog = "updater_versions_err_format"; Logger.Log(LogLevel.Warn, "updater", "Failed parsing index: " + e.ToString()); return(this); } } } // Highly convoluted scientific method to determine the entry order: // - Order by first occurence of branch // - Order by version inside branch Dictionary <string, int> branchFirsts = new Dictionary <string, int>(); // Force stable, then beta, then dev branches to appear first. branchFirsts["stable"] = int.MaxValue; branchFirsts["beta"] = int.MaxValue - 3; branchFirsts["dev"] = int.MaxValue - 4; // Make sure that the branch we're on appears between stable and beta. // This ensures that people don't miss out on important stability updates, // but don't get dragged onto another branch by accident. foreach (Entry entry in entries) { if (entry.Build == Build) { CoreModule.Settings.CurrentBranch = entry.Branch; break; } } if (CoreModule.Settings.CurrentBranch != null) { branchFirsts[CoreModule.Settings.CurrentBranch] = int.MaxValue - 2; } for (int i = 0; i < entries.Count; i++) { Entry entry = entries[i]; if (!branchFirsts.ContainsKey(entry.Branch)) { branchFirsts[entry.Branch] = i; } } entries.Sort((a, b) => { if (a.Branch != b.Branch) { return(-(branchFirsts[a.Branch].CompareTo(branchFirsts[b.Branch]))); } return(-a.Build.CompareTo(b.Build)); }); Entries = new ReadOnlyCollection <Entry>(entries); return(this); }
/// <summary> /// Load a mod .dll given its metadata at runtime. Doesn't load the mod content. /// </summary> /// <param name="meta">Metadata of the mod to load.</param> public static void LoadMod(EverestModuleMetadata meta) { if (Flags.IsDisabled || !Flags.SupportRuntimeMods) { Logger.Log(LogLevel.Warn, "loader", "Loader disabled!"); return; } if (meta == null) { return; } // Add an AssemblyResolve handler for all bundled libraries. AppDomain.CurrentDomain.AssemblyResolve += GenerateModAssemblyResolver(meta); ApplyRelinkerHackfixes(meta); // Load the actual assembly. Assembly asm = null; if (!string.IsNullOrEmpty(meta.PathArchive)) { bool returnEarly = false; using (ZipFile zip = new ZipFile(meta.PathArchive)) { foreach (ZipEntry entry in zip.Entries) { string entryName = entry.FileName.Replace('\\', '/'); if (entryName == meta.DLL) { using (MemoryStream stream = entry.ExtractStream()) { if (meta.Prelinked) { asm = Assembly.Load(stream.GetBuffer()); } else { asm = Relinker.GetRelinkedAssembly(meta, stream); } } } if (entryName == "main.lua") { new LuaModule(meta).Register(); returnEarly = true; } } } if (returnEarly) { return; } } else { if (!string.IsNullOrEmpty(meta.DLL) && File.Exists(meta.DLL)) { if (meta.Prelinked) { asm = Assembly.LoadFrom(meta.DLL); } else { using (FileStream stream = File.OpenRead(meta.DLL)) asm = Relinker.GetRelinkedAssembly(meta, stream); } } if (File.Exists(Path.Combine(meta.PathDirectory, "main.lua"))) { new LuaModule(meta).Register(); return; } } ApplyModHackfixes(meta, asm); if (asm == null) { // Register a null module for content mods. new NullModule(meta).Register(); return; } LoadModAssembly(meta, asm); }
/// <summary> /// Load a mod from a directory at runtime. /// </summary> /// <param name="dir">The path to the mod directory.</param> public static void LoadDir(string dir) { if (Flags.IsDisabled || !Flags.SupportRuntimeMods) { Logger.Log(LogLevel.Warn, "loader", "Loader disabled!"); return; } if (!Directory.Exists(dir)) // Relative path? { dir = Path.Combine(PathMods, dir); } if (!Directory.Exists(dir)) // It just doesn't exist. { return; } Logger.Log(LogLevel.Verbose, "loader", $"Loading mod directory: {dir}"); EverestModuleMetadata meta = null; EverestModuleMetadata[] multimetas = null; string metaPath = Path.Combine(dir, "metadata.yaml"); if (File.Exists(metaPath)) { using (StreamReader reader = new StreamReader(metaPath)) { try { meta = YamlHelper.Deserializer.Deserialize <EverestModuleMetadata>(reader); meta.PathDirectory = dir; meta.PostParse(); } catch (Exception e) { Logger.Log(LogLevel.Warn, "loader", $"Failed parsing metadata.yaml in {dir}: {e}"); } } } metaPath = Path.Combine(dir, "multimetadata.yaml"); if (!File.Exists(metaPath)) { metaPath = Path.Combine(dir, "everest.yaml"); } if (!File.Exists(metaPath)) { metaPath = Path.Combine(dir, "everest.yml"); } if (File.Exists(metaPath)) { using (StreamReader reader = new StreamReader(metaPath)) { try { if (!reader.EndOfStream) { multimetas = YamlHelper.Deserializer.Deserialize <EverestModuleMetadata[]>(reader); foreach (EverestModuleMetadata multimeta in multimetas) { multimeta.PathDirectory = dir; multimeta.PostParse(); } } } catch (Exception e) { Logger.Log(LogLevel.Warn, "loader", $"Failed parsing everest.yaml in {dir}: {e}"); } } } FileSystemModContent contentMeta = new FileSystemModContent(dir); EverestModuleMetadata contentMetaParent = null; Action contentCrawl = () => { if (contentMeta == null) { return; } if (contentMetaParent != null) { contentMeta.Mod = contentMetaParent; contentMeta.Name = contentMetaParent.Name; } Content.Crawl(contentMeta); contentMeta = null; }; if (multimetas != null) { foreach (EverestModuleMetadata multimeta in multimetas) { if (contentMetaParent == null) { contentMetaParent = multimeta; } LoadModDelayed(multimeta, contentCrawl); } } else { if (meta == null) { meta = new EverestModuleMetadata() { Name = "_dir_" + Path.GetFileName(dir), VersionString = "0.0.0-dummy", PathDirectory = dir }; meta.PostParse(); } contentMetaParent = meta; LoadModDelayed(meta, contentCrawl); } }
/// <summary> /// Load a mod from a .zip archive at runtime. /// </summary> /// <param name="archive">The path to the mod .zip archive.</param> public static void LoadZip(string archive) { if (Flags.IsDisabled || !Flags.SupportRuntimeMods) { Logger.Log(LogLevel.Warn, "loader", "Loader disabled!"); return; } if (!File.Exists(archive)) // Relative path? Let's just make it absolute. { archive = Path.Combine(PathMods, archive); } if (!File.Exists(archive)) // It just doesn't exist. { return; } Logger.Log(LogLevel.Verbose, "loader", $"Loading mod .zip: {archive}"); EverestModuleMetadata meta = null; EverestModuleMetadata[] multimetas = null; // In case the icon appears before the metadata in the .zip, store it temporarily, set it later. Texture2D icon = null; using (ZipFile zip = new ZipFile(archive)) { foreach (ZipEntry entry in zip.Entries) { if (entry.FileName == "metadata.yaml") { using (MemoryStream stream = entry.ExtractStream()) using (StreamReader reader = new StreamReader(stream)) { try { meta = YamlHelper.Deserializer.Deserialize <EverestModuleMetadata>(reader); meta.PathArchive = archive; meta.PostParse(); } catch (Exception e) { Logger.Log(LogLevel.Warn, "loader", $"Failed parsing metadata.yaml in {archive}: {e}"); } } continue; } if (entry.FileName == "multimetadata.yaml" || entry.FileName == "everest.yaml" || entry.FileName == "everest.yml") { using (MemoryStream stream = entry.ExtractStream()) using (StreamReader reader = new StreamReader(stream)) { try { if (!reader.EndOfStream) { multimetas = YamlHelper.Deserializer.Deserialize <EverestModuleMetadata[]>(reader); foreach (EverestModuleMetadata multimeta in multimetas) { multimeta.PathArchive = archive; multimeta.PostParse(); } } } catch (Exception e) { Logger.Log(LogLevel.Warn, "loader", $"Failed parsing multimetadata.yaml in {archive}: {e}"); } } continue; } if (entry.FileName == "icon.png") { using (Stream stream = entry.ExtractStream()) icon = Texture2D.FromStream(Celeste.Instance.GraphicsDevice, stream); continue; } } } if (meta != null) { if (icon != null) { meta.Icon = icon; } } ZipModContent contentMeta = new ZipModContent(archive); EverestModuleMetadata contentMetaParent = null; Action contentCrawl = () => { if (contentMeta == null) { return; } if (contentMetaParent != null) { contentMeta.Mod = contentMetaParent; contentMeta.Name = contentMetaParent.Name; } Content.Crawl(contentMeta); contentMeta = null; }; if (multimetas != null) { foreach (EverestModuleMetadata multimeta in multimetas) { if (contentMetaParent == null) { contentMetaParent = multimeta; } LoadModDelayed(multimeta, contentCrawl); } } else { if (meta == null) { meta = new EverestModuleMetadata() { Name = "_zip_" + Path.GetFileNameWithoutExtension(archive), VersionString = "0.0.0-dummy", PathArchive = archive }; meta.PostParse(); } contentMetaParent = meta; LoadModDelayed(meta, contentCrawl); } }