using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using UnityEngine; namespace MSCO { /// /// MSCO statt "My Summer Car": Fenstertitel und -symbol, und im Hauptmenü das große Spiel-Logo durch das MSCO-Logo /// (branding/menu-logo.png, in die DLL eingebettet) ersetzen. /// public static class MenuBranding { const string WindowTitle = "MSCO - My Summer Car Online"; const string MenuLogoResource = "MSCO.menu-logo.png"; // ---------------------------------------------------------------- Fenster delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam); [DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc callback, IntPtr lParam); [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr hwnd, out uint processId); [DllImport("user32.dll", CharSet = CharSet.Unicode)] static extern int GetClassNameW(IntPtr hwnd, StringBuilder name, int max); [DllImport("user32.dll", CharSet = CharSet.Unicode)] static extern bool SetWindowTextW(IntPtr hwnd, string text); [DllImport("user32.dll")] static extern IntPtr SendMessageW(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] static extern IntPtr CreateIconFromResourceEx(byte[] data, uint size, bool icon, uint version, int cx, int cy, uint flags); static readonly EnumWindowsProc enumProc = OnWindow; // Delegate festhalten (sonst räumt der GC ihn weg) static uint processId; static IntPtr bigIcon, smallIcon; /// Titel und Symbol des Spielfensters auf MSCO setzen (beim Start und nach jedem Szenenwechsel). public static void ApplyWindow() { try { processId = (uint)Process.GetCurrentProcess().Id; if (bigIcon == IntPtr.Zero && Branding.LogoPng.Length > 0) { // PNG-Daten direkt als Symbol (geht seit Windows Vista) bigIcon = CreateIconFromResourceEx(Branding.LogoPng, (uint)Branding.LogoPng.Length, true, 0x00030000, 0, 0, 0); smallIcon = CreateIconFromResourceEx(Branding.LogoPng, (uint)Branding.LogoPng.Length, true, 0x00030000, 16, 16, 0); } EnumWindows(enumProc, IntPtr.Zero); } catch (Exception e) { Bootstrap.Log("Window title/icon: " + e.Message); } } static bool OnWindow(IntPtr hwnd, IntPtr lParam) { uint pid; GetWindowThreadProcessId(hwnd, out pid); if (pid != processId) return true; var name = new StringBuilder(64); GetClassNameW(hwnd, name, name.Capacity); if (name.ToString() != "UnityWndClass") return true; SetWindowTextW(hwnd, WindowTitle); const uint WM_SETICON = 0x0080; if (bigIcon != IntPtr.Zero) SendMessageW(hwnd, WM_SETICON, (IntPtr)1, bigIcon); if (smallIcon != IntPtr.Zero) SendMessageW(hwnd, WM_SETICON, IntPtr.Zero, smallIcon); return true; } // ---------------------------------------------------------------- Logo im Hauptmenü static Texture2D source; static readonly Dictionary fitted = new Dictionary(); static bool dumped; static Texture2D Source { get { if (source != null) return source; using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream(MenuLogoResource)) { if (s == null) return null; var data = new byte[s.Length]; int read = 0; while (read < data.Length) read += s.Read(data, read, data.Length - read); source = new Texture2D(2, 2, TextureFormat.ARGB32, false); source.LoadImage(data); source.hideFlags = HideFlags.HideAndDontSave; } return source; } } /// Ist das eine Textur des Spiel-Logos? (Name enthält "logo", aber nicht vom Auto/Laden ...) static bool IsGameLogo(Texture t) { if (t == null || t == source) return false; string n = t.name.ToLowerInvariant(); return n.Contains("logo") && !n.Contains("msco") && !n.Contains("satsuma") && !n.Contains("teimo") && !n.Contains("amistech"); } /// Im Hauptmenü das Spiel-Logo ersetzen. Gibt die Anzahl ersetzter Texturen zurück. public static int ReplaceMenuLogo() { if (Source == null) return 0; int replaced = 0; var seen = new List(); // Auch ausgeblendete Objekte (das Menü wechselt zwischen mehreren Logo-Objekten) foreach (Renderer r in Resources.FindObjectsOfTypeAll()) { if (r == null || r.gameObject.hideFlags != HideFlags.None) continue; Material[] materials = r.sharedMaterials; for (int i = 0; i < materials.Length; i++) { Material m = materials[i]; if (m == null || m.mainTexture == null) continue; if (!dumped && seen.Count < 200) seen.Add(Path(r.transform) + " : " + m.mainTexture.name); if (!IsGameLogo(m.mainTexture)) continue; Texture original = m.mainTexture; m.mainTexture = Fit(original.width, original.height); Bootstrap.Log("Menu logo replaced: " + Path(r.transform) + " (" + original.name + ", " + original.width + "x" + original.height + ")"); replaced++; } } foreach (SpriteRenderer sr in UnityEngine.Object.FindObjectsOfType()) { if (sr == null || sr.sprite == null || !IsGameLogo(sr.sprite.texture)) continue; Texture2D tex = Fit(sr.sprite.texture.width, sr.sprite.texture.height); sr.sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), sr.sprite.pixelsPerUnit); Bootstrap.Log("Menu logo (sprite) replaced: " + Path(sr.transform)); replaced++; } foreach (GUITexture g in UnityEngine.Object.FindObjectsOfType()) { if (g == null || !IsGameLogo(g.texture)) continue; g.texture = Fit(g.texture.width, g.texture.height); Bootstrap.Log("Menu logo (GUITexture) replaced: " + Path(g.transform)); replaced++; } if (!dumped && replaced == 0) { // Zur Diagnose: welche Texturen gibt es im Menü? (nur einmal) Bootstrap.Log("Menu logo not found. Textures in the menu:\n " + string.Join("\n ", seen.ToArray())); } dumped = true; return replaced; } /// MSCO-Logo mittig in eine Textur der Größe des Originals (gleiches Seitenverhältnis wie dessen Fläche). static Texture2D Fit(int width, int height) { width = Mathf.Clamp(width, 64, 2048); height = Mathf.Clamp(height, 64, 2048); string key = width + "x" + height; Texture2D tex; if (fitted.TryGetValue(key, out tex) && tex != null) return tex; tex = new Texture2D(width, height, TextureFormat.ARGB32, true); tex.hideFlags = HideFlags.HideAndDontSave; tex.wrapMode = TextureWrapMode.Clamp; float scale = Mathf.Min((float)width / Source.width, (float)height / Source.height); float w = Source.width * scale, h = Source.height * scale; float x0 = (width - w) / 2f, y0 = (height - h) / 2f; var pixels = new Color[width * height]; for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) { float u = (x - x0) / w, v = (y - y0) / h; pixels[y * width + x] = u < 0f || u > 1f || v < 0f || v > 1f ? new Color(0, 0, 0, 0) : Source.GetPixelBilinear(u, v); } tex.SetPixels(pixels); tex.Apply(true); fitted[key] = tex; return tex; } static string Path(Transform t) { string path = t.name; for (Transform p = t.parent; p != null; p = p.parent) path = p.name + "/" + path; return path; } } }