Загрузка данных


using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using UnityEngine;
using UnityEngine.SceneManagement;

[BepInPlugin("my.cheatmenu", "CheatMenu", "1.3")]
public class Plugin : BasePlugin
{
    public static ManualLogSource L;
    public override void Load()
    {
        L = Log;
        ClassInjector.RegisterTypeInIl2Cpp<MenuBehaviour>();
        AddComponent<MenuBehaviour>();
        Log.LogInfo("CheatMenu loaded, press Insert");
    }
}

public class MenuBehaviour : MonoBehaviour
{
    public MenuBehaviour(IntPtr ptr) : base(ptr) { }

    bool open, fly, god, stamina;
    float flySpeed = 15f;
    Transform player;
    CharacterController cc;
    Rigidbody rb;
    string filter = "";
    int scenePage = 0;
    float nextApply;

    // режим шахеда
    bool drone, exploding, soundsLoaded;
    float droneSpeed = 30f, crashSpeed = 20f, spinSpeed = 360f;
    float droneStartTime, respawnAt, explodeTime = -100f;
    Vector3 startPos;
    GameObject flash;
    AudioClip boomClip, buzzClip, musicClip;
    AudioSource buzzSrc, musicSrc;
    string lastHit = "-";
    string soundStatus = "звуки не загружены";
    string statsStatus = "";

    // поиск полей здоровья/стамины
    static Assembly gameAsm;
    static readonly Dictionary<string, Type> typeCache = new Dictionary<string, Type>();
    static readonly string[] HealthKeys = { "health", "hp" };
    static readonly string[] StaminaKeys = { "stamina", "energy" };
    static readonly string[] Bad = { "regen", "rate", "damage", "cost", "drain", "speed", "time", "delay", "max", "min", "percent", "ratio" };

    // ---------- звук ----------

    void EnsureSounds()
    {
        if (soundsLoaded) return;
        soundsLoaded = true;
        boomClip = LoadWav("boom.wav");
        buzzClip = LoadWav("buzz.wav");
        musicClip = LoadWav("music.wav");
        if (boomClip == null) { boomClip = MakeBoom(); Plugin.L.LogInfo("Using generated boom sound"); }
        soundStatus = "boom:" + (boomClip != null ? "ok" : "нет")
                    + " buzz:" + (buzzClip != null ? "ok" : "нет")
                    + " music:" + (musicClip != null ? "ok" : "нет");
    }

    AudioClip MakeClip(string name, float[] d, int ch, int rate)
    {
        var clip = AudioClip.Create(name, d.Length / ch, ch, rate, false);
        clip.SetData((Il2CppStructArray<float>)d, 0);
        return clip;
    }

    AudioClip MakeBoom()
    {
        try
        {
            int rate = 22050, n = rate * 2;
            var d = new float[n];
            var r = new System.Random(1);
            for (int i = 0; i < n; i++)
            {
                float t = (float)i / rate;
                d[i] = ((float)r.NextDouble() * 2f - 1f) * Mathf.Exp(-t * 3f) * 0.9f;
            }
            return MakeClip("boom_gen", d, 1, rate);
        }
        catch (Exception e) { Plugin.L.LogInfo("MakeBoom error: " + e.Message); return null; }
    }

    AudioClip LoadWav(string name)
    {
        try
        {
            using (var s = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
            {
                if (s == null) { Plugin.L.LogInfo("No embedded sound: " + name); return null; }
                var ms = new MemoryStream();
                s.CopyTo(ms);
                byte[] b = ms.ToArray();
                int pos = 12, ch = 1, rate = 44100, bits = 16, fmt = 1, dataPos = -1, dataLen = 0;
                while (pos + 8 <= b.Length)
                {
                    string id = Encoding.ASCII.GetString(b, pos, 4);
                    int size = BitConverter.ToInt32(b, pos + 4);
                    if (id == "fmt ")
                    {
                        fmt = BitConverter.ToInt16(b, pos + 8);
                        ch = BitConverter.ToInt16(b, pos + 10);
                        rate = BitConverter.ToInt32(b, pos + 12);
                        bits = BitConverter.ToInt16(b, pos + 22);
                    }
                    else if (id == "data") { dataPos = pos + 8; dataLen = Math.Min(size, b.Length - dataPos); break; }
                    pos += 8 + size + (size % 2);
                }
                if (dataPos < 0) { Plugin.L.LogInfo(name + ": no data chunk"); return null; }
                if (fmt != 1 && fmt != 65534) { Plugin.L.LogInfo(name + ": unsupported format " + fmt + " (need PCM)"); return null; }
                if (bits != 16) { Plugin.L.LogInfo(name + ": need 16-bit, got " + bits); return null; }
                int samples = dataLen / 2;
                var f = new float[samples];
                for (int i = 0; i < samples; i++) f[i] = BitConverter.ToInt16(b, dataPos + i * 2) / 32768f;
                var clip = MakeClip(name, f, ch, rate);
                Plugin.L.LogInfo("Loaded sound: " + name + " (" + ch + "ch, " + rate + "Hz, " + samples + " samples)");
                return clip;
            }
        }
        catch (Exception e) { Plugin.L.LogInfo("LoadWav error (" + name + "): " + e.Message); return null; }
    }

    AudioSource MakeLoopSource(string name, float volume)
    {
        var g = new GameObject(name);
        UnityEngine.Object.DontDestroyOnLoad(g);
        var src = g.AddComponent<AudioSource>();
        src.loop = true;
        src.spatialBlend = 0f;
        src.volume = volume;
        return src;
    }

    void StopDroneSounds()
    {
        if (buzzSrc != null) buzzSrc.Stop();
        if (musicSrc != null) musicSrc.Stop();
    }

    void PlayMusic()
    {
        EnsureSounds();
        if (buzzClip != null)
        {
            if (buzzSrc == null) buzzSrc = MakeLoopSource("DroneBuzz", 1f);
            buzzSrc.clip = buzzClip;
            buzzSrc.Play();
        }
        if (musicClip != null)
        {
            if (musicSrc == null) musicSrc = MakeLoopSource("DroneMusic", 0.8f);
            musicSrc.clip = musicClip;
            musicSrc.Play();
        }
    }

    // ---------- основной цикл ----------

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Insert)) open = !open;
        if (Input.GetKeyDown(KeyCode.F6)) SetFly(!fly);
        if (Input.GetKeyDown(KeyCode.F8)) SetDrone(!drone);

        if (open) { Cursor.visible = true; Cursor.lockState = CursorLockMode.None; }

        // обычный полёт
        if (fly && !drone && player != null && Camera.main != null && !open)
        {
            var cam = Camera.main.transform;
            Vector3 dir = Vector3.zero;
            if (Input.GetKey(KeyCode.W)) dir += cam.forward;
            if (Input.GetKey(KeyCode.S)) dir -= cam.forward;
            if (Input.GetKey(KeyCode.D)) dir += cam.right;
            if (Input.GetKey(KeyCode.A)) dir -= cam.right;
            if (Input.GetKey(KeyCode.Space)) dir += Vector3.up;
            if (Input.GetKey(KeyCode.LeftControl)) dir -= Vector3.up;
            float sp = Input.GetKey(KeyCode.LeftShift) ? flySpeed * 3f : flySpeed;
            player.position += dir.normalized * sp * Time.deltaTime;
        }

        // режим шахеда
        if (drone && !exploding && player != null && Camera.main != null && !open)
        {
            var cam = Camera.main.transform;
            Vector3 dir = cam.forward;
            if (Input.GetKey(KeyCode.W)) droneSpeed += 25f * Time.deltaTime;
            if (Input.GetKey(KeyCode.S)) droneSpeed -= 25f * Time.deltaTime;
            droneSpeed = Mathf.Clamp(droneSpeed, 5f, 80f);

            float dist = droneSpeed * Time.deltaTime + 0.6f;
            bool hit = false;
            var hits = Physics.SphereCastAll(player.position, 0.5f, dir, dist);
            foreach (var h in hits)
            {
                if (h.collider == null || h.collider.isTrigger) continue;
                if (h.transform == player || h.transform.IsChildOf(player)) continue;
                hit = true;
                lastHit = h.collider.name;
                break;
            }

            if (hit && Time.time - droneStartTime > 1.5f)
            {
                if (droneSpeed >= crashSpeed) Explode(player.position);
            }
            else
            {
                player.position += dir * droneSpeed * Time.deltaTime;
            }
            player.Rotate(dir, spinSpeed * Time.deltaTime, Space.World);
        }

        // сфера взрыва
        if (flash != null)
        {
            float k = (Time.time - explodeTime) / 0.6f;
            if (k >= 1f) { UnityEngine.Object.Destroy(flash); flash = null; }
            else flash.transform.localScale = Vector3.one * (2f + k * 14f);
        }

        // возврат на старт
        if (exploding && Time.time >= respawnAt)
        {
            if (player != null) player.position = startPos;
            exploding = false;
            SetFly(false);
        }

        if ((god || stamina) && Time.time >= nextApply)
        {
            nextApply = Time.time + 0.25f;
            ApplyStats(0);
        }
    }

    void SetFly(bool value)
    {
        if (value == fly) return;
        fly = value;
        if (fly)
        {
            cc = FindObjectOfType<CharacterController>();
            if (cc != null) { player = cc.transform; cc.enabled = false; }
            else
            {
                var go = GameObject.FindWithTag("Player");
                rb = go != null ? go.GetComponent<Rigidbody>() : null;
                if (rb != null) { player = rb.transform; rb.isKinematic = true; }
            }
            if (player == null) { fly = false; Plugin.L.LogInfo("Player not found (no CharacterController / Player-tag Rigidbody)"); }
        }
        else
        {
            drone = false;
            StopDroneSounds();
            if (cc != null) cc.enabled = true;
            if (rb != null) rb.isKinematic = false;
        }
    }

    void SetDrone(bool value)
    {
        if (value)
        {
            SetFly(true);
            if (!fly || player == null) return;
            startPos = player.position;
            droneStartTime = Time.time;
            exploding = false;
            drone = true;
            PlayMusic();
        }
        else
        {
            drone = false;
            StopDroneSounds();
        }
    }

    void Explode(Vector3 pos)
    {
        EnsureSounds();
        exploding = true;
        respawnAt = Time.time + 1.5f;
        explodeTime = Time.time;
        StopDroneSounds();
        Plugin.L.LogInfo("EXPLODE at " + pos + ", hit: " + lastHit);

        if (boomClip != null) AudioSource.PlayClipAtPoint(boomClip, pos, 1f);

        try
        {
            flash = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            flash.transform.position = pos;
            flash.transform.localScale = Vector3.one * 2f;
            var col = flash.GetComponent<Collider>();
            if (col != null) UnityEngine.Object.Destroy(col);
            var rend = flash.GetComponent<Renderer>();
            if (rend != null) rend.material.color = new Color(1f, 0.55f, 0.1f);
        }
        catch { }

        var near = Physics.OverlapSphere(pos, 10f);
        foreach (var c in near)
        {
            if (c == null) continue;
            var body = c.attachedRigidbody;
            if (body != null && body != rb) body.AddExplosionForce(800f, pos, 10f);
        }
    }

    // ---------- здоровье и стамина ----------

    Type ResolveType(MonoBehaviour mb)
    {
        string n = null;
        try { n = mb.GetIl2CppType().FullName; } catch { }
        if (n == null) return null;
        Type t;
        if (typeCache.TryGetValue(n, out t)) return t;
        if (gameAsm == null)
            foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
                if (a.GetName().Name == "Assembly-CSharp") { gameAsm = a; break; }
        t = gameAsm != null ? gameAsm.GetType(n) : null;
        typeCache[n] = t;
        return t;
    }

    // mode 0 = применить, mode 1 = записать найденное в лог
    void ApplyStats(int mode)
    {
        int found = 0;
        var all = UnityEngine.Object.FindObjectsOfType<MonoBehaviour>();
        foreach (var mb in all)
        {
            if (mb == null) continue;
            try
            {
                var t = ResolveType(mb);
                if (t == null) continue;
                object obj = Activator.CreateInstance(t, mb.Pointer);
                var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                foreach (var p in props)
                {
                    if (!p.CanRead || !p.CanWrite) continue;
                    if (p.PropertyType != typeof(float) && p.PropertyType != typeof(int)) continue;
                    string n = p.Name.ToLower();
                    bool isBad = false;
                    foreach (var b in Bad) if (n.Contains(b)) { isBad = true; break; }
                    if (isBad) continue;

                    bool isH = false, isS = false;
                    foreach (var k in HealthKeys) if (n.Contains(k)) isH = true;
                    foreach (var k in StaminaKeys) if (n.Contains(k)) isS = true;
                    if (!isH && !isS) continue;

                    if (mode == 1)
                    {
                        Plugin.L.LogInfo("FOUND: " + t.FullName + "." + p.Name + " = " + p.GetValue(obj));
                        found++;
                        continue;
                    }
                    if (!(isH && god) && !(isS && stamina)) continue;

                    double target = 9999;
                    foreach (var q in props)
                    {
                        string qn = q.Name.ToLower();
                        bool maxMatch = qn.Contains("max") && (isH ? (qn.Contains("health") || qn.Contains("hp")) : (qn.Contains("stamina") || qn.Contains("energy")));
                        if (!maxMatch || !q.CanRead) continue;
                        object v = q.GetValue(obj);
                        if (v is float f) target = f; else if (v is int i) target = i;
                        break;
                    }
                    if (p.PropertyType == typeof(float)) p.SetValue(obj, (float)target);
                    else p.SetValue(obj, (int)target);
                    found++;
                }
            }
            catch { }
        }
        statsStatus = "полей найдено: " + found;
        if (mode == 1) Plugin.L.LogInfo("Scan done, fields: " + found);
    }

    void DumpClasses()
    {
        var all = UnityEngine.Object.FindObjectsOfType<MonoBehaviour>();
        var seen = new HashSet<string>();
        foreach (var mb in all)
        {
            if (mb == null) continue;
            try
            {
                string n = mb.GetIl2CppType().FullName;
                if (seen.Add(n) && ResolveType(mb) != null) Plugin.L.LogInfo("CLASS: " + n);
            }
            catch { }
        }
    }

    // ---------- спавн ----------

    void SpawnByName(string name)
    {
        var all = Resources.FindObjectsOfTypeAll<GameObject>();
        foreach (var go in all)
        {
            if (go == null || go.name != name) continue;
            Vector3 pos = Vector3.zero;
            if (Camera.main != null) pos = Camera.main.transform.position + Camera.main.transform.forward * 3f;
            var copy = UnityEngine.Object.Instantiate(go, pos, Quaternion.identity);
            copy.SetActive(true);
            return;
        }
    }

    // ---------- интерфейс ----------

    void OnGUI()
    {
        // вспышка взрыва
        float fk = (Time.time - explodeTime) / 0.8f;
        if (fk >= 0f && fk < 1f)
        {
            GUI.color = new Color(1f, 0.6f, 0.1f, 1f - fk);
            GUI.DrawTexture(new Rect(0, 0, Screen.width, Screen.height), Texture2D.whiteTexture);
            GUI.color = Color.white;
        }

        // строка состояния
        if (open || fly || drone || god || stamina)
        {
            string st = "Полёт:" + (fly ? "да" : "нет") + "  Игрок:" + (player != null ? "найден" : "НЕТ")
                      + "  Шахед:" + (drone ? ("да, v=" + (int)droneSpeed) : "нет") + "\n"
                      + "Удар:" + lastHit + "  " + soundStatus + "\n" + statsStatus;
            GUI.Label(new Rect(Screen.width - 520, 10, 510, 70), st);
        }

        if (!open) return;
        float x = 20, y = 20, w = 320, h = 26;
        GUI.Box(new Rect(10, 10, w + 20, 860), "Cheat Menu (Insert)");
        y += 20;

        bool nf = GUI.Toggle(new Rect(x, y, w, h), fly, " Полёт / ноклип (F6)"); y += h;
        if (nf != fly) SetFly(nf);
        GUI.Label(new Rect(x, y, w, 20), "Скорость полёта: " + (int)flySpeed); y += 20;
        flySpeed = GUI.HorizontalSlider(new Rect(x, y, w, 20), flySpeed, 3f, 100f); y += 26;

        bool nd = GUI.Toggle(new Rect(x, y, w, h), drone, " Режим шахеда (F8)"); y += h;
        if (nd != drone) SetDrone(nd);
        GUI.Label(new Rect(x, y, w, 20), "Скорость дрона: " + (int)droneSpeed + " (W/S)"); y += 20;
        droneSpeed = GUI.HorizontalSlider(new Rect(x, y, w, 20), droneSpeed, 5f, 80f); y += 22;
        GUI.Label(new Rect(x, y, w, 20), "Порог взрыва: " + (int)crashSpeed); y += 20;
        crashSpeed = GUI.HorizontalSlider(new Rect(x, y, w, 20), crashSpeed, 5f, 80f); y += 22;
        GUI.Label(new Rect(x, y, w, 20), "Вращение: " + (int)spinSpeed + " град/с"); y += 20;
        spinSpeed = GUI.HorizontalSlider(new Rect(x, y, w, 20), spinSpeed, 0f, 1080f); y += 26;

        if (GUI.Button(new Rect(x, y, 150, 24), "Тест взрыва"))
        {
            Vector3 p = Camera.main != null ? Camera.main.transform.position + Camera.main.transform.forward * 5f : Vector3.zero;
            Explode(p);
            exploding = false;
        }
        if (GUI.Button(new Rect(x + 170, y, 150, 24), "Тест музыки")) PlayMusic();
        y += 28;
        if (GUI.Button(new Rect(x, y, w, 24), "Стоп звук")) StopDroneSounds();
        y += 32;

        god = GUI.Toggle(new Rect(x, y, w, h), god, " Бессмертие"); y += h;
        stamina = GUI.Toggle(new Rect(x, y, w, h), stamina, " Бесконечная стамина"); y += h;
        if (GUI.Button(new Rect(x, y, w, 24), "Найти поля health/stamina (в лог)")) ApplyStats(1);
        y += 28;
        if (GUI.Button(new Rect(x, y, w, 24), "Список классов игры (в лог)")) DumpClasses();
        y += 36;

        GUI.Label(new Rect(x, y, w, h), "Сцены (индекс в билде):"); y += 22;
        int count = SceneManager.sceneCountInBuildSettings;
        for (int i = scenePage * 4; i < Math.Min(count, scenePage * 4 + 4); i++)
        {
            string path = SceneUtility.GetScenePathByBuildIndex(i);
            string nm = Path.GetFileNameWithoutExtension(path);
            if (GUI.Button(new Rect(x, y, w, 24), i + ": " + nm)) SceneManager.LoadScene(i);
            y += 26;
        }
        if (GUI.Button(new Rect(x, y, 150, 24), "< Назад")) scenePage = Math.Max(0, scenePage - 1);
        if (GUI.Button(new Rect(x + 170, y, 150, 24), "Вперёд >")) { if ((scenePage + 1) * 4 < count) scenePage++; }
        y += 34;

        GUI.Label(new Rect(x, y, w, h), "Спавн: введите часть имени"); y += 22;
        filter = GUI.TextField(new Rect(x, y, w, 24), filter); y += 28;
        if (filter.Length >= 2)
        {
            int shown = 0;
            var all = Resources.FindObjectsOfTypeAll<GameObject>();
            foreach (var go in all)
            {
                if (go == null || shown >= 5) break;
                if (go.name.ToLower().Contains(filter.ToLower()))
                {
                    if (GUI.Button(new Rect(x, y, w, 24), go.name)) SpawnByName(go.name);
                    y += 26; shown++;
                }
            }
        }
    }
}