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;
}
}
}