using System; using System.Buffers.Binary; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text.Json; using System.Text.RegularExpressions; using BoneLib; using BoneLib.BoneMenu; using BonelabRP.Telephone; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSLZ.Bonelab; using Il2CppSLZ.Marrow; using Il2CppSLZ.Marrow.Data; using Il2CppSLZ.Marrow.Interaction; using Il2CppSLZ.Marrow.Warehouse; using Il2CppTMPro; using LabFusion.Audio; using LabFusion.Entities; using LabFusion.Network; using LabFusion.Network.Serialization; using LabFusion.Player; using LabFusion.RPC; using LabFusion.SDK.Metadata; using LabFusion.SDK.Modules; using LabFusion.Scene; using LabFusion.Senders; using LabFusion.UI; using LabFusion.Utilities; using LabFusion.Voice; using LabFusion.Voice.Unity; using LabRP.Economy; using MelonLoader; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(PhoneMod), "BonelabRP Telephone", "0.8.9", "Romu", null)] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("BonelabRP.Telephone")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BonelabRP.Telephone")] [assembly: AssemblyTitle("BonelabRP.Telephone")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BonelabRP.Telephone { public static class PhonePin { public static bool Valid(string pin) { return pin != null && pin.Length == 4 && pin.All((char c) => c >= '0' && c <= '9'); } public static string Salt() { return Convert.ToBase64String(RandomNumberGenerator.GetBytes(16)); } public static string Hash(string pin, string salt) { if (!Valid(pin)) { throw new InvalidOperationException("Le code doit contenir quatre chiffres."); } using Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(pin, Convert.FromBase64String(salt), 100000, HashAlgorithmName.SHA256); return Convert.ToBase64String(rfc2898DeriveBytes.GetBytes(32)); } public static bool Verify(string pin, string salt, string hash) { if (!Valid(pin) || salt == "" || hash == "") { return false; } try { return CryptographicOperations.FixedTimeEquals(Convert.FromBase64String(Hash(pin, salt)), Convert.FromBase64String(hash)); } catch (FormatException) { return false; } } } public sealed class PhoneDeviceInfo { public ulong Owner { get; set; } public ushort Device { get; set; } public bool PinSet { get; set; } public bool Parked { get; set; } public long Placement { get; set; } public float X { get; set; } public float Y { get; set; } public float Z { get; set; } public float Yaw { get; set; } } public sealed class PhoneAccessView { public int Protocol { get; set; } = 16; public string Session { get; set; } = ""; public ulong Player { get; set; } public ulong Owner { get; set; } public ushort Device { get; set; } public bool BankPinSet { get; set; } public bool BankUnlocked { get; set; } public long Revision { get; set; } public int Epoch { get; set; } public bool PinSet { get; set; } public string Wallpaper { get; set; } = ""; public string Token { get; set; } = ""; public string Notice { get; set; } = ""; public PhoneView Profile { get; set; } public WindyView Windy { get; set; } public List Deleted { get; set; } = new List(); public List Gallery { get; set; } = new List(); } public sealed class PhoneAccess { public sealed class Lease { public ulong Owner; public ushort Device; public string Token; public double Until; public bool BankAuthorized; } private readonly Dictionary leases = new Dictionary(); private readonly Dictionary<(ulong, ulong), (int Count, double Until)> failures = new Dictionary<(ulong, ulong), (int, double)>(); public Lease Get(ulong actor) { Lease value; return leases.TryGetValue(actor, out value) ? value : null; } public void Clear() { leases.Clear(); failures.Clear(); } public void Lock(ulong actor) { leases.Remove(actor); } public string Unlock(ulong actor, ulong owner, ushort device, string pin, bool held, Func verify, double now) { if (!held) { throw new InvalidOperationException("Tenir ce téléphone pour le déverrouiller."); } (ulong, ulong) key = (actor, owner); failures.TryGetValue(key, out (int, double) value); if (now < value.Item2) { throw new InvalidOperationException("Trop de tentatives. Attendre trente secondes."); } if (!verify(pin)) { value.Item1++; if (value.Item1 >= 5) { value.Item1 = 0; value.Item2 = now + 30.0; } failures[key] = value; throw new InvalidOperationException("Code incorrect."); } failures.Remove(key); ulong[] array = (from p in leases where p.Value.Device == device select p.Key).ToArray(); foreach (ulong key2 in array) { leases.Remove(key2); } string text = Guid.NewGuid().ToString("N"); leases[actor] = new Lease { Owner = owner, Device = device, Token = text, Until = now + 8.0 }; return text; } public bool Valid(ulong actor, ulong owner, ushort device, string token, bool held, double now) { Lease value; return held && leases.TryGetValue(actor, out value) && value.Owner == owner && value.Device == device && value.Token == token && now < value.Until; } public bool Renew(ulong actor, ulong owner, ushort device, string token, bool held, double now) { if (!Valid(actor, owner, device, token, held, now)) { Lock(actor); return false; } leases[actor].Until = now + 8.0; return true; } public ulong[] Expire(double now, Func held) { ulong[] array = (from p in leases where now >= p.Value.Until || !held(p.Key, p.Value) select p.Key).ToArray(); ulong[] array2 = array; foreach (ulong actor in array2) { Lock(actor); } return array; } public ulong[] Readers(ulong owner) { return (from p in leases where p.Value.Owner == owner select p.Key).ToArray(); } } public sealed class PhoneAmbientBuffer { private sealed class Track { public readonly Queue Samples = new Queue(); public double Last; public int Rate; public float Gain; } private readonly Dictionary tracks = new Dictionary(); public void Clear() { tracks.Clear(); } public void Add(ulong actor, short[] pcm, int rate, float gain, double now) { if (pcm == null || rate < 8000 || rate > 192000 || !float.IsFinite(gain) || gain <= 0f) { return; } if (!tracks.TryGetValue(actor, out var value)) { if (tracks.Count >= 8) { return; } value = (tracks[actor] = new Track()); } if (value.Rate != rate) { value.Samples.Clear(); } value.Rate = rate; value.Gain = Math.Clamp(gain, 0f, 0.7f); value.Last = now; int num = Math.Min(pcm.Length, rate / 4); while (value.Samples.Count + num > rate / 4) { value.Samples.Dequeue(); } for (int i = pcm.Length - num; i < pcm.Length; i++) { value.Samples.Enqueue(pcm[i]); } } public short[] Mix(short[] own, int rate, double now, Func allowed) { ulong[] array = tracks.Keys.ToArray(); foreach (ulong num in array) { if (now - tracks[num].Last > 0.3 || !allowed(num) || tracks[num].Rate != rate) { tracks.Remove(num); } } if (tracks.Count == 0) { return own; } short[] array2 = new short[own.Length]; for (int j = 0; j < own.Length; j++) { double num2 = (double)own[j] / 32768.0; foreach (Track value in tracks.Values) { if (value.Samples.Count > 0) { num2 += (double)value.Samples.Dequeue() / 32768.0 * (double)value.Gain; } } double num3 = Math.Abs(num2); if (num3 > 0.85) { num2 = (double)Math.Sign(num2) * (0.85 + 0.15 * Math.Tanh((num3 - 0.85) / 0.15)); } array2[j] = (short)Math.Clamp(num2 * 32767.0, -32767.0, 32767.0); } return array2; } } public sealed class PhoneAmbientCapture { private readonly PhoneAmbientBuffer buffer = new PhoneAmbientBuffer(); private string scope = ""; private string Scope(PhoneMod mod) { PhoneVoiceNotes notes = mod.Notes; return (notes != null && notes.Recording) ? ("note/" + mod.ScreenOwner + "/" + mod.Access.Token) : (mod.IsCasting ? ("live/" + mod.Live.Active.Id) : ((mod.CallActive && mod.View.Call.Speaker) ? ("call/" + mod.View.Call.Id) : "")); } private bool Nearby(PhoneMod mod, ulong actor) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) PlayerID playerID = PlayerIDManager.GetPlayerID(actor); if (playerID == null || !playerID.IsValid || playerID.IsMe || (mod.CallActive && mod.View.Call.Peer == actor) || !PhoneMod.VoiceHead(playerID, out var head)) { return false; } Vector3 position; if (mod.ScreenDevice.HasValue) { GameObject val = PhoneDevices.Resolve(mod.ScreenDevice.Value); if ((Object)(object)val == (Object)null) { return false; } position = val.transform.position; } else { if ((Object)(object)Player.Head == (Object)null) { return false; } position = Player.Head.position; } return PhoneMod.Within(head, position, 2f); } private bool Ready(PhoneMod mod) { string text = Scope(mod); if (text != scope) { scope = text; buffer.Clear(); } return text != "" && !VoiceInfo.IsDeafened; } public void Receive(PhoneMod mod, UnityVoiceSpeaker speaker, byte[] data) { if (Ready(mod) && ((VoiceSpeaker)speaker).ID != null && Nearby(mod, ((VoiceSpeaker)speaker).ID.PlatformID) && PhoneVoiceGuard.Valid(data)) { buffer.Add(((VoiceSpeaker)speaker).ID.PlatformID, VoiceConverter.Decode(data), AudioInfo.OutputSampleRate, ((VoiceSpeaker)speaker).Volume * 0.5f, Time.realtimeSinceStartup); } } public short[] Mix(PhoneMod mod, short[] own, int rate) { return Ready(mod) ? buffer.Mix(own, rate, Time.realtimeSinceStartup, (ulong id) => Nearby(mod, id)) : own; } } [HarmonyPatch(typeof(UnityVoiceSpeaker), "OnVoiceDataReceived")] public static class PhoneAmbientVoicePatch { [HarmonyPrefix] public static void Prefix(UnityVoiceSpeaker __instance, byte[] __0) { try { PhoneMod instance = PhoneMod.Instance; instance?.Ambient.Receive(instance, __instance, __0); } catch (Exception) { } } } public sealed class PhoneApp { private sealed class Toast { public ulong Owner; public string Title; public string Text; public Action Click; public float Until; } private readonly PhoneMod mod; private PhoneSurface surface; private string page = "home"; private string number = ""; private string name = ""; private string body = ""; private string editField = ""; private string returnPage = ""; private string attached = ""; private string viewing = ""; private string viewerBack = "gallery"; private string attachDestination = "compose"; private string windyName = ""; private string windyBody = ""; private string windyPhoto = ""; private WindyPostView windySelected; private int offset; private string amount = ""; private string clock = ""; private bool callTone; private string lastCall = ""; private string timedCall = ""; private float callBegan; private int callSecond = -1; private bool dirty = true; private bool capitals; private bool bankClosing; private static readonly string[] FrenchDays = new string[7] { "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi" }; private static readonly string[] FrenchMonths = new string[12] { "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre" }; private string contactSearch = ""; private static readonly Color ContactGreen = new Color(0.06f, 0.6f, 0.37f, 1f); private static readonly Color ContactRed = new Color(0.85f, 0.16f, 0.23f, 1f); private static readonly Color Blue = new Color(0.06f, 0.44f, 0.9f, 1f); private static readonly Color Navy = new Color(0.06f, 0.16f, 0.35f, 1f); private string pin = ""; private string pinFirst = ""; private string pinError = ""; private string liveText = ""; private bool pinConfirm; private bool wasUnlocked; private string liveUiId = ""; private readonly List notifications = new List(); private Toast banner; private float nextMessagesUi; private PhoneSms selectedMessage; private string Value => (editField == "dialNumber") ? number : ((editField == "contactSearch") ? contactSearch : ((editField == "number") ? number : ((editField == "liveText") ? liveText : ((editField == "windyName") ? windyName : ((editField == "windyBody") ? windyBody : ((editField == "name") ? name : ((editField == "amount") ? amount : body))))))); private PhoneContact SelectedContact => mod.ScreenProfile.Contacts.FirstOrDefault((PhoneContact c) => c.Number == number); private bool Dark => mod.Sounds.Preferences.Theme == "dark"; private Color Ink => Dark ? new Color(0.93f, 0.95f, 0.98f, 1f) : new Color(0.06f, 0.1f, 0.17f, 1f); private Color Muted => Dark ? new Color(0.65f, 0.7f, 0.78f, 1f) : new Color(0.4f, 0.46f, 0.54f, 1f); private Color Card => (Color)(Dark ? new Color(0.1f, 0.14f, 0.2f, 1f) : Color.white); private Color Background => Dark ? new Color(0.025f, 0.04f, 0.065f, 1f) : new Color(0.95f, 0.97f, 0.99f, 1f); public void WindyPosted() { windyBody = (windyPhoto = ""); if (page == "windyCompose") { Go("windy"); } Refresh(); } public void OpenShared(PhoneNotice n) { number = n.Number; name = n.Name; Go("contact"); } private string S(string text) { return PhoneLanguage.Status(mod.Sounds.Preferences.Language, text); } private string T(string text) { return PhoneLanguage.Text(mod.Sounds.Preferences.Language, text); } public PhoneApp(PhoneMod mod) { this.mod = mod; } public void Bind(PhoneSurface screen) { surface = screen; dirty = true; } public void Refresh() { dirty = true; } public void VoiceSent() { Go("thread"); } private void Go(string destination) { if (BankPage(page) && !BankPage(destination) && mod.Access.BankUnlocked) { bankClosing = true; mod.Request("bankLock", "", "", "", "", enabled: false, 0L); } if (BankPage(destination) && destination != "keyboard" && (!mod.Access.BankUnlocked || bankClosing)) { destination = "bankGate"; } if (destination == "bankGate" && page != "bankGate") { pin = (pinFirst = (pinError = "")); pinConfirm = false; } if (page == "voiceRecord" && destination != "voiceRecord") { mod.Notes.Clear(); } page = destination; offset = 0; dirty = true; } private void Edit(string target, string back) { editField = target; returnPage = back; Go("keyboard"); } private void ValueSet(string value) { if (editField == "dialNumber") { number = value; } else if (editField == "contactSearch") { contactSearch = value; } else if (editField == "number") { number = value; } else if (editField == "name") { name = value; } else if (editField == "liveText") { liveText = value; } else if (editField == "windyName") { windyName = value; } else if (editField == "windyBody") { windyBody = value; } else if (editField == "amount") { amount = value; } else { body = value; } dirty = true; } private void Add(string value) { int num = ((editField == "dialNumber") ? 11 : ((editField == "contactSearch") ? 32 : ((editField == "liveText") ? 140 : ((editField == "windyName") ? 20 : ((editField == "windyBody") ? 280 : ((editField == "amount") ? 12 : ((editField == "number") ? 10 : ((editField == "name") ? 32 : 320)))))))); if (Value.Length + value.Length <= num) { ValueSet(Value + value); } } private void Row(string text, float y, Action action) { surface.Button(text, 0f, y, 540f, 75f, action); } public void Tick() { //IL_039a: Unknown result type (might be due to invalid IL or missing references) //IL_03b1: Unknown result type (might be due to invalid IL or missing references) //IL_065a: Unknown result type (might be due to invalid IL or missing references) //IL_063f: Unknown result type (might be due to invalid IL or missing references) //IL_068a: Unknown result type (might be due to invalid IL or missing references) //IL_0683: Unknown result type (might be due to invalid IL or missing references) //IL_06f5: Unknown result type (might be due to invalid IL or missing references) //IL_0800: Unknown result type (might be due to invalid IL or missing references) //IL_0f82: Unknown result type (might be due to invalid IL or missing references) //IL_0fd1: Unknown result type (might be due to invalid IL or missing references) //IL_1026: Unknown result type (might be due to invalid IL or missing references) //IL_107b: Unknown result type (might be due to invalid IL or missing references) //IL_10d0: Unknown result type (might be due to invalid IL or missing references) //IL_1125: Unknown result type (might be due to invalid IL or missing references) //IL_1174: Unknown result type (might be due to invalid IL or missing references) //IL_11c3: Unknown result type (might be due to invalid IL or missing references) //IL_1218: Unknown result type (might be due to invalid IL or missing references) string text = DateTime.Now.ToString("HH:mm"); if (clock != text) { clock = text; dirty = true; } string text2 = mod.View.Call.Id + "/" + mod.View.Call.State; if (text2 != lastCall) { lastCall = text2; if (mod.View.Call.State != "Idle") { Go("call"); } else if (page == "call") { Go("recentCalls"); } } if (mod.View.Call.State == "Active") { if (timedCall != mod.View.Call.Id) { timedCall = mod.View.Call.Id; callBegan = Time.realtimeSinceStartup; callSecond = -1; } int num = (int)(Time.realtimeSinceStartup - callBegan); if (page == "call" && num != callSecond) { callSecond = num; dirty = true; } } else { timedCall = ""; callSecond = -1; } mod.Media.PhotoPage = mod.ScreenUnlocked && page == "photo"; mod.Media.PreviewPage = mod.ScreenUnlocked && page == "liveSetup"; if ((page == "nearby" || page == "voiceRecord") && Time.realtimeSinceStartup >= nextMessagesUi) { nextMessagesUi = Time.realtimeSinceStartup + 0.5f; dirty = true; } TickExperienceUi(); if (bankClosing && !mod.Access.BankUnlocked) { bankClosing = false; } if (page == "bankGate" && mod.Access.BankUnlocked && !bankClosing) { pin = (pinFirst = (pinError = "")); pinConfirm = false; Go("bank"); } if (BankPage(page) && page != "bankGate" && !mod.Access.BankUnlocked) { Go("bankGate"); } if (surface == null || !dirty) { return; } dirty = false; if (!mod.ScreenUnlocked) { surface.Clear(Background); surface.TextColor = Ink; DrawWallpaper(); DrawLock(); surface.Finish(); return; } bool flag = page == "messageDetail" || page == "inbox" || page == "thread" || page == "voiceRecord" || page == "nearby" || page == "appearance" || page == "wallpaperPicker" || page == "dial" || page == "recentCalls" || page == "settings" || page == "language" || page == "contacts" || page == "contact" || page == "contactEdit" || page == "contactPhoto" || page == "compose" || page == "deleteContact" || page == "bank" || page == "bankTransfer" || page == "windy" || page == "windyCompose" || page == "windyRead" || page == "confirmBank" || page == "gallery" || page == "attach" || page == "liveList" || page == "liveSetup" || page == "live" || page == "transactions"; surface.Clear((Color)((page == "call") ? new Color(0.025f, 0.04f, 0.065f, 1f) : Background)); surface.TextColor = ((page == "call") ? Color.white : Ink); if (page == "home") { DrawWallpaper(); } PhoneView screenProfile = mod.ScreenProfile; if (flag) { surface.Label(clock + " WindyPhone • 4G", 0f, 455f, 530f, 40f, 21f, Ink); } if (!flag) { surface.Text((page == "home") ? ("WindyPhone • " + PhoneService.Display(screenProfile.Number)) : ("WindyPhone • " + T("Téléphone")), 455f, 45f, 23f); } if (page != "home" && page != "call" && !flag) { surface.Text(S(mod.Status), 370f, 75f, 22f); } if (flag) { surface.Label(Short(S(mod.Status), 80), 0f, -412f, 530f, 38f, 18f, Muted); } if (page != "keyboard") { surface.Button(T("Accueil"), 0f, -455f, 180f, 45f, delegate { Go("home"); }); } switch (page) { case "home": surface.Text(clock, 330f, 85f, 70f); surface.Text(PhoneLanguage.Date(mod.Sounds.Preferences.Language, DateTime.Now), 265f, 40f, 22f); surface.Tile("contacts", "Contacts", -180f, 155f, new Color(0.13f, 0.44f, 0.88f, 1f), delegate { Go("contacts"); }); surface.Tile("messages", "Messages", 0f, 155f, new Color(0.15f, 0.65f, 0.36f, 1f), delegate { Go("inbox"); }); surface.Tile("phone", T("Téléphone"), 180f, 155f, new Color(0.09f, 0.64f, 0.53f, 1f), delegate { if (mod.View.Call.State != "Idle") { Go("call"); } else { number = ""; Go("dial"); } }); surface.Tile("camera", T("Photo"), -180f, -55f, new Color(0.3f, 0.33f, 0.4f, 1f), delegate { Go("photo"); }); surface.Tile("gallery", T("Galerie"), 0f, -55f, new Color(0.9f, 0.57f, 0.12f, 1f), delegate { Go("gallery"); }); surface.Tile("bank", T("Banque"), 180f, -55f, new Color(0.44f, 0.29f, 0.82f, 1f), delegate { Go("bank"); }); surface.Tile("nearby", "AirDrop", -180f, -265f, new Color(0.12f, 0.56f, 0.89f, 1f), delegate { Go("nearby"); }); surface.Tile("windy", "Windy", 0f, -265f, new Color(0.1f, 0.58f, 0.83f, 1f), delegate { Go("windy"); mod.Request("windySync", "", "", "", "", enabled: false, 0L); }); surface.Tile("settings", T("Réglages"), 180f, -265f, new Color(0.35f, 0.4f, 0.49f, 1f), delegate { Go("settings"); }); surface.Text(S(mod.Status), -408f, 38f, 18f); break; case "photo": surface.Picture((Texture)(object)mod.Media.Camera.Preview, 0f, 80f, 520f, 390f); if ((Object)(object)mod.Media.Camera.Preview == (Object)null) { surface.Text(T("Préparation de la caméra…"), 80f, 100f, 26f); } Row(mod.Media.FrontCamera ? T("Caméra avant") : T("Caméra arrière"), -175f, delegate { mod.Media.FrontCamera = !mod.Media.FrontCamera; Refresh(); }); surface.Text((surface.HoldsLeft && !surface.HoldsRight) ? T("Appuyer sur Y de la main porteuse") : T("Appuyer sur B de la main porteuse"), -280f, 100f, 25f); break; case "gallery": case "attach": DrawGallery(); break; case "viewer": surface.Picture((Texture)(object)mod.Media.OpenPhoto, 0f, 90f, 520f, 390f); if ((Object)(object)mod.Media.OpenPhoto == (Object)null) { surface.Text(T("Chargement…"), 90f, 100f, 26f); } Row(T("Envoyer cette photo"), -165f, delegate { attached = viewing; number = (body = ""); Go("compose"); }); if (mod.Gallery.Contains(viewing)) { Row(T("Supprimer de Galerie"), -270f, delegate { Go("deletePhoto"); }); } surface.Button(T("Retour"), 0f, -370f, 250f, 50f, delegate { Go(viewerBack); }); break; case "deletePhoto": surface.Text(T("Supprimer cette photo de Galerie ?\nLes copies déjà envoyées restent chez leurs destinataires."), 130f, 230f, 28f); Row(T("Confirmer la suppression"), -75f, delegate { mod.Media.DeleteGalleryPhoto(viewing); Go("gallery"); }); Row(T("Annuler"), -185f, delegate { Go("viewer"); }); break; case "bankGate": DrawBankGate(); break; case "bank": DrawBank(); break; case "bankTransfer": DrawBankTransfer(); break; case "transactions": DrawTransactions(); break; case "confirmBank": DrawBankConfirm(); break; case "nearby": DrawAirDrop(); break; case "settings": DrawSettings(); break; case "appearance": DrawAppearance(); break; case "wallpaperPicker": DrawGallery(); break; case "language": DrawLanguage(); break; case "cameraSettings": { surface.Text(T("Visio : ") + mod.Sounds.Preferences.VideoFps + T(" images/s"), 265f, 60f, 27f); int[] array2 = new int[3] { 8, 15, 24 }; for (int num4 = 0; num4 < array2.Length; num4++) { int fps = array2[num4]; surface.Button(fps + T(" i/s"), (num4 - 1) * 180, 170f, 160f, 75f, delegate { Fps(video: true, fps); }); } surface.Text(T("Aperçu photo : ") + mod.Sounds.Preferences.PhotoFps + T(" images/s"), 35f, 70f, 27f); int[] array3 = new int[3] { 15, 30, 45 }; for (int num5 = 0; num5 < array3.Length; num5++) { int fps2 = array3[num5]; surface.Button(fps2 + T(" i/s"), (num5 - 1) * 180, -70f, 160f, 75f, delegate { Fps(video: false, fps2); }); } surface.Text(T("Cadence cible : le PC et le réseau déterminent la fluidité réelle."), -245f, 130f, 23f); break; } case "windy": DrawWindy(); break; case "liveList": DrawLiveList(); break; case "liveSetup": DrawLiveSetup(); break; case "live": DrawLive(); break; case "liveComment": DrawLiveComment(); break; case "notifications": DrawNotifications(); break; case "pinChange": DrawPinChange(); break; case "windyCompose": DrawWindyCompose(); break; case "windyRead": DrawWindyRead(); break; case "tones": { string[] array = (callTone ? PhoneSounds.CallNames : PhoneSounds.MessageNames); for (int num2 = 0; num2 < array.Length; num2++) { int choice = num2; int num3 = (callTone ? mod.Sounds.Preferences.CallTone : mod.Sounds.Preferences.MessageTone); Row(((num3 == num2) ? "✓ " : "") + T(array[num2]), 230 - num2 * 110, delegate { try { mod.Sounds.Select(callTone, choice); Refresh(); } catch (Exception ex) { mod.LocalStatus(ex.Message); } }); } break; } case "contacts": DrawContacts(); break; case "contact": DrawContact(); break; case "contactEdit": DrawContactEdit(); break; case "contactPhoto": DrawGallery(); break; case "deleteContact": DrawDeleteContact(); break; case "compose": DrawCompose(); break; case "inbox": DrawInbox(); break; case "thread": DrawThread(); break; case "messageDetail": DrawMessageDetail(); break; case "voiceRecord": DrawVoiceRecorder(); break; case "dial": DrawDialer(); break; case "recentCalls": DrawRecentCalls(); break; case "call": DrawCall(); break; case "keyboard": Keyboard(); break; } DrawBanner(); surface.Finish(); } private static string FrenchDate(DateTime date) { return FrenchDays[(int)date.DayOfWeek] + " " + date.Day.ToString(CultureInfo.InvariantCulture) + " " + FrenchMonths[date.Month - 1]; } private void Fps(bool video, int fps) { try { mod.Sounds.SetFps(video, fps); Refresh(); } catch (Exception ex) { mod.LocalStatus(ex.Message); } } private void Volume(float delta) { try { mod.Sounds.SetVolume(mod.Sounds.Preferences.RingVolume + delta); Refresh(); } catch (Exception ex) { mod.LocalStatus(ex.Message); } } private void Pages(int count, int amount) { surface.Button("◀", -180f, -355f, 120f, 55f, delegate { offset = Math.Max(0, offset - amount); dirty = true; }); surface.Button("▶", 180f, -355f, 120f, 55f, delegate { if (offset + amount < count) { offset += amount; } dirty = true; }); } private void Keyboard() { surface.Text(Value, 205f, 235f, 25f); string[] array = ((!(editField == "number") && !(editField == "amount") && !(editField == "dialNumber")) ? ((!(mod.Sounds.Preferences.Language == "en")) ? new string[4] { "1234567890", "azertyuiop", "qsdfghjkl", "wxcvbnm._" } : new string[4] { "1234567890", "qwertyuiop", "asdfghjkl", "zxcvbnm._" }) : ((!(editField == "dialNumber")) ? new string[4] { "123", "456", "789", "0" } : new string[4] { "123", "456", "789", "#0" })); for (int i = 0; i < array.Length; i++) { string text = array[i]; float num = Math.Min(165f, 540f / (float)text.Length); for (int j = 0; j < text.Length; j++) { string text2 = text[j].ToString(); if (capitals) { text2 = text2.ToUpperInvariant(); } string key = text2; surface.Button(key, ((float)j - (float)(text.Length - 1) * 0.5f) * num, 30 - i * 82, num - 6f, 70f, delegate { Add(key); }); } } surface.Button(T("Effacer"), -180f, -310f, 165f, 65f, delegate { if (Value.Length > 0) { ValueSet(Value.Substring(0, Value.Length - 1)); } }); surface.Button(T("Espace"), 0f, -310f, 165f, 65f, delegate { if (editField != "number" && editField != "amount") { Add(" "); } }); surface.Button(T("Maj"), 180f, -310f, 165f, 65f, delegate { capitals = !capitals; dirty = true; }); Row(T("Terminé"), -420f, delegate { Go(returnPage); }); } private void DrawAirDrop() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_02b7: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_03b4: Unknown result type (might be due to invalid IL or missing references) //IL_03ef: Unknown result type (might be due to invalid IL or missing references) //IL_0430: Unknown result type (might be due to invalid IL or missing references) //IL_037d: Unknown result type (might be due to invalid IL or missing references) Header("AirDrop", T("Partager ton numéro à proximité")); surface.Panel(0f, 260f, 540f, 70f, Navy); surface.Label(PhoneService.Display(mod.ScreenProfile.Number), 0f, 260f, 510f, 55f, 29f, Color.white); (ulong, string, float)[] array = mod.NearbyPeople().Take(3).ToArray(); for (int i = 0; i < array.Length; i++) { (ulong Id, string Name, float Distance) person = array[i]; float x = ((float)i - (float)(array.Length - 1) * 0.5f) * 180f; surface.Tile("contacts", Short(person.Name, 15), x, 125f, Blue, delegate { mod.Request("share", "", "", person.Id.ToString(CultureInfo.InvariantCulture), "", enabled: false, 0L); }, Ink); surface.Label(person.Distance.ToString("F1", CultureInfo.InvariantCulture) + " m", x, 0f, 170f, 30f, 17f, Muted); } if (array.Length == 0) { surface.Label(T("Aucun joueur à moins de 3 m"), 0f, 125f, 510f, 90f, 25f, Muted); } surface.Label(T("Numéros reçus"), 0f, -50f, 510f, 40f, 22f, Ink, left: true); PhoneNotice[] array2 = mod.SharedNumbers.Where((PhoneNotice n) => n.Owner == mod.ScreenOwner).Skip(offset).Take(2) .ToArray(); for (int num = 0; num < array2.Length; num++) { PhoneNotice offer = array2[num]; float num2 = -120 - num * 110; surface.Button("", 0f, num2, 540f, 95f, delegate { OpenShared(offer); }, Card); surface.Label(offer.Name, -55f, num2 + 18f, 390f, 38f, 25f, Ink, left: true); surface.Label(PhoneService.Display(offer.Number), -55f, num2 - 22f, 390f, 32f, 21f, Muted, left: true); surface.Label("+", 230f, num2, 45f, 60f, 40f, Blue); } if (array2.Length == 0) { surface.Label(T("Aucun numéro reçu"), 0f, -130f, 510f, 75f, 24f, Muted); } surface.Button("◀", -230f, -335f, 80f, 65f, delegate { offset = Math.Max(0, offset - 2); Refresh(); }, Navy); surface.Button("▶", 230f, -335f, 80f, 65f, delegate { if (offset + 2 < mod.SharedNumbers.Count((PhoneNotice n) => n.Owner == mod.ScreenOwner)) { offset += 2; } Refresh(); }, Navy); surface.Button(T("Partager à tous • 3 m"), 0f, -335f, 340f, 65f, delegate { mod.Request("share", "", "", "", "", enabled: false, 0L); }, Blue); } private void DrawWallpaper() { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) string visibleWallpaper = mod.VisibleWallpaper; if (!(visibleWallpaper == "")) { Texture2D val = mod.Media.PictureFor(visibleWallpaper); if (!((Object)(object)val == (Object)null)) { surface.Picture((Texture)(object)val, 0f, 0f, 600f, 1000f, crop: true); surface.Panel(0f, 0f, 600f, 1000f, new Color(0f, 0f, 0f, Dark ? 0.5f : 0.38f)); surface.TextColor = Color.white; } } } private void DrawAppearance() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) Header(T("Apparence"), "WindyPhone"); surface.Label(T("Thème"), 0f, 270f, 510f, 50f, 28f, Ink, left: true); surface.Button((Dark ? "" : "✓ ") + T("Clair"), -140f, 185f, 250f, 80f, delegate { Theme("light"); }, Dark ? Navy : Blue); surface.Button((Dark ? "✓ " : "") + T("Sombre"), 140f, 185f, 250f, 80f, delegate { Theme("dark"); }, Dark ? Blue : Navy); surface.Label(T("Fond d’écran"), 0f, 75f, 510f, 50f, 28f, Ink, left: true); surface.Button(T("Choisir dans la Galerie"), 0f, -5f, 530f, 75f, delegate { Go("wallpaperPicker"); }, Blue); surface.Label(T("Même image sur le verrouillage et l’accueil."), 0f, -95f, 510f, 75f, 23f, Muted); if (mod.VisibleWallpaper != "") { surface.Button(T("Retirer le fond d’écran"), 0f, -205f, 530f, 65f, delegate { mod.Request("wallpaper", "", "", "", "", enabled: false, 0L); }, Navy); } surface.Button(T("Retour"), 0f, -330f, 380f, 60f, delegate { Go("settings"); }, Navy); } private void Theme(string theme) { try { mod.Sounds.SetTheme(theme); Refresh(); } catch (Exception ex) { mod.LocalStatus(S(ex.Message)); } } private bool BankPage(string value) { int result; switch (value) { case "keyboard": result = ((returnPage == "bankTransfer") ? 1 : 0); break; default: result = 0; break; case "bank": case "bankTransfer": case "confirmBank": case "transactions": case "bankGate": result = 1; break; } return (byte)result != 0; } private string BankText(string fr, string en) { return (mod.Sounds.Preferences.Language == "en") ? en : fr; } private void DrawBankGate() { bool setup = !mod.Access.BankPinSet; surface.Text("LabBank", 370f, 65f, 38f); surface.Text(setup ? BankText(pinConfirm ? "Confirme ton code bancaire" : "Crée ton code bancaire à 4 chiffres", pinConfirm ? "Confirm your bank PIN" : "Create your 4-digit bank PIN") : BankText("Saisis ton code bancaire", "Enter your bank PIN"), 290f, 85f, 27f); if (setup && mod.ScreenOwner != PlayerIDManager.LocalPlatformID) { surface.Text(BankText("Le propriétaire doit créer son code bancaire.", "The owner must set up their bank PIN."), 80f, 150f, 27f); return; } PinKeys(delegate { if (!PhonePin.Valid(pin)) { pinError = BankText("Saisis les quatre chiffres du code.", "Enter the four digits."); Refresh(); } else if (setup && !pinConfirm) { pinFirst = pin; pin = ""; pinConfirm = true; Refresh(); } else if (setup && pin != pinFirst) { pinError = BankText("Les codes ne correspondent pas.", "PINs do not match."); pin = (pinFirst = ""); pinConfirm = false; Refresh(); } else { mod.Request(setup ? "bankPin" : "bankUnlock", "", "", pin, "", enabled: false, 0L); pin = (pinFirst = ""); pinConfirm = false; Refresh(); } }); } private void ContactAvatar(PhoneContact contact, string title, float x, float y, float size, Color text) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) Texture2D val = ((contact != null && contact.Photo != "") ? mod.Media.PictureFor(contact.Photo) : null); surface.Panel(x, y, size, size, new Color(0.18f, 0.39f, 0.64f, 1f)); if ((Object)(object)val != (Object)null) { surface.Picture((Texture)(object)val, x, y, size - 4f, size - 4f, crop: true); } else { surface.Label(string.IsNullOrWhiteSpace(title) ? "?" : title.Trim().Substring(0, 1).ToUpperInvariant(), x, y, size, size, size * 0.48f, text); } } private void DrawContacts() { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_031a: Unknown result type (might be due to invalid IL or missing references) Header("Contacts", mod.ScreenProfile.Contacts.Count + T(" contacts • Carnet personnel")); surface.Button((contactSearch == "") ? T("Rechercher") : (T("Recherche : ") + Short(contactSearch, 20)), -90f, 270f, 350f, 55f, delegate { Edit("contactSearch", "contacts"); }, Navy); surface.Button(T("+ Ajouter"), 190f, 270f, 165f, 55f, delegate { number = (name = ""); Go("contactEdit"); }, Blue); PhoneContact[] array = mod.ScreenProfile.Contacts.Where((PhoneContact phoneContact) => contactSearch == "" || phoneContact.Name.Contains(contactSearch, StringComparison.OrdinalIgnoreCase) || phoneContact.Number.Contains(contactSearch.Replace(" ", ""))).OrderBy((PhoneContact phoneContact) => phoneContact.Name, StringComparer.OrdinalIgnoreCase).ToArray(); offset = Math.Min(offset, (array.Length != 0) ? ((array.Length - 1) / 5 * 5) : 0); PhoneContact[] array2 = array.Skip(offset).Take(5).ToArray(); for (int num = 0; num < array2.Length; num++) { PhoneContact c = array2[num]; float num2 = 185 - num * 102; surface.Button("", 0f, num2, 540f, 92f, delegate { number = c.Number; name = c.Name; Go("contact"); }, Card); ContactAvatar(c, c.Name, -215f, num2, 68f, Color.white); surface.Label(c.Name, 30f, num2 + 18f, 360f, 38f, 27f, Ink, left: true); surface.Label(PhoneService.Display(c.Number), 30f, num2 - 20f, 360f, 32f, 21f, Muted, left: true); surface.Label("›", 240f, num2, 35f, 50f, 35f, Blue); } if (array2.Length == 0) { surface.Label((contactSearch == "") ? T("Ton carnet est vide.\nAjoute ton premier contact.") : T("Aucun contact trouvé"), 0f, 30f, 520f, 140f, 27f, Muted); } Pages(array.Length, 5); surface.Label((array.Length == 0) ? T("0 contact") : (offset / 5 + 1 + " / " + (array.Length + 4) / 5), 0f, -355f, 150f, 45f, 21f, Muted); } private void DrawContact() { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) PhoneContact c = SelectedContact; if (c == null) { DrawContactEdit(); return; } Header(c.Name, PhoneService.Display(c.Number)); ContactAvatar(c, c.Name, 0f, 200f, 145f, Color.white); surface.Tile("phone", T("Appeler"), -180f, -5f, ContactGreen, delegate { mod.Request("call", number, "", "", "", enabled: false, 0L); }, Ink); surface.Tile("messages", "SMS", 0f, -5f, Blue, delegate { body = (attached = ""); Go("compose"); }, Ink); surface.Tile("camera", T("Visio"), 180f, -5f, Navy, delegate { mod.Request("call", number, "", "", "", enabled: true, 0L); }, Ink); surface.Button((c.Photo == "") ? T("Ajouter une photo") : T("Changer la photo"), -65f, -170f, 395f, 60f, delegate { Go("contactPhoto"); }, Navy); if (c.Photo != "") { surface.Button(T("Retirer"), 220f, -170f, 110f, 60f, delegate { mod.Request("contactPhoto", number, "", "", "", enabled: false, 0L); }, ContactRed); } surface.Button(T("Modifier le contact"), 0f, -250f, 530f, 65f, delegate { name = c.Name; Go("contactEdit"); }, Blue); surface.Button(T("Supprimer le contact"), 0f, -335f, 530f, 60f, delegate { Go("deleteContact"); }, ContactRed); surface.Button(T("Retour aux contacts"), 0f, -410f, 380f, 45f, delegate { Go("contacts"); }, Navy); } private void DrawContactEdit() { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) PhoneContact c = SelectedContact; Header((c == null) ? T("Nouveau contact") : T("Modifier le contact"), T("Nom et numéro de téléphone")); surface.Label(T("NOM"), -15f, 250f, 500f, 40f, 20f, Muted, left: true); surface.Button((name == "") ? T("Saisir le nom") : name, 0f, 190f, 530f, 80f, delegate { Edit("name", "contactEdit"); }, Navy); surface.Label(T("NUMÉRO"), -15f, 110f, 500f, 40f, 20f, Muted, left: true); if (c == null) { surface.Button((number == "") ? T("Saisir le numéro") : PhoneService.Display(number), 0f, 50f, 530f, 80f, delegate { Edit("number", "contactEdit"); }, Navy); } else { surface.Label(PhoneService.Display(number), 0f, 50f, 530f, 80f, 32f, Ink); } surface.Button(T("Enregistrer"), 0f, -90f, 530f, 75f, delegate { mod.Request("contact", number, name, "", "", enabled: false, 0L); Go("contact"); }, ContactGreen); surface.Label(T("Tu pourras choisir une photo après\nl’enregistrement du contact."), 0f, -200f, 510f, 95f, 23f, Muted); surface.Button(T("Annuler"), 0f, -335f, 400f, 60f, delegate { Go((c == null) ? "contacts" : "contact"); }, Navy); } private void DrawDeleteContact() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) Header(T("Supprimer le contact"), SelectedContact?.Name ?? PhoneService.Display(number)); surface.Label(T("Retirer ce contact de ton carnet ?\nLes conversations resteront disponibles."), 0f, 135f, 510f, 170f, 28f, Ink); surface.Button(T("Supprimer"), 0f, -80f, 530f, 75f, delegate { mod.Request("deleteContact", number, "", "", "", enabled: false, 0L); Go("contacts"); }, ContactRed); surface.Button(T("Annuler"), 0f, -195f, 530f, 75f, delegate { Go("contact"); }, Navy); } private void DrawCompose() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_0331: Unknown result type (might be due to invalid IL or missing references) //IL_0392: Unknown result type (might be due to invalid IL or missing references) //IL_03d3: Unknown result type (might be due to invalid IL or missing references) PhoneContact c = SelectedContact; Header(T("Nouveau message"), c?.Name ?? "SMS / photo"); ContactAvatar(c, c?.Name ?? number, -220f, 245f, 68f, Color.white); surface.Button((PhoneService.Display(number) == "") ? T("Choisir le destinataire") : PhoneService.Display(number), 60f, 245f, 400f, 65f, delegate { Edit("number", "compose"); }, Navy); surface.Panel(0f, 45f, 540f, 270f, Card); surface.Label((body == "") ? T("Écris ton message…") : Short(body, 320), 0f, 45f, 500f, 235f, 25f, (body == "") ? Muted : Ink, left: true); surface.Button(T("Écrire / modifier"), 0f, -135f, 530f, 60f, delegate { Edit("body", "compose"); }, Navy); surface.Button((attached == "") ? T("Joindre une photo") : T("Changer la photo jointe"), (!(attached == "")) ? (-55) : 0, -220f, (attached == "") ? 530 : 420, 60f, delegate { attachDestination = "compose"; Go("attach"); }, Navy); surface.MarkIcon("clip", (attached == "") ? (-225) : (-225), -220f, 32f); if (attached != "") { surface.Button(T("Retirer"), 225f, -220f, 100f, 60f, delegate { attached = ""; Refresh(); }, ContactRed); } surface.Button(mod.Media.UploadBusy ? T("Envoi de la photo…") : T("Envoyer le message"), 0f, -325f, 530f, 80f, delegate { try { if (body.Trim() == "" && attached == "") { throw new InvalidOperationException(T("Écrire un message ou joindre une photo.")); } if (attached != "") { mod.Media.SendPhoto(attached, number, body); } else { mod.Request("sms", number, "", body, "", enabled: false, 0L); } } catch (Exception ex) { mod.LocalStatus(ex.Message); } }, Blue); surface.MarkIcon("send", -225f, -325f, 36f); surface.Button(T("Retour"), -140f, -410f, 250f, 45f, delegate { Go((c != null) ? "contact" : "inbox"); }, Navy); surface.Button(T("Vocal"), 140f, -410f, 250f, 45f, delegate { Go("voiceRecord"); }, Blue); } private void PhoneTabs(string selected) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) surface.Button(T("Clavier"), -180f, -370f, 170f, 55f, delegate { Go("dial"); }, (selected == "dial") ? Blue : Navy); surface.Button(T("Récents"), 0f, -370f, 170f, 55f, delegate { Go("recentCalls"); }, (selected == "recentCalls") ? Blue : Navy); surface.Button("Contacts", 180f, -370f, 170f, 55f, delegate { Go("contacts"); }, Navy); } private void DrawDialer() { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0374: Unknown result type (might be due to invalid IL or missing references) //IL_03d5: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_0323: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) Header(T("Téléphone"), "WindyPhone • " + PhoneService.Display(mod.ScreenProfile.Number)); surface.Label((number == "") ? T("Saisir le numéro") : PhoneDial.Display(number), -25f, 240f, 455f, 70f, 34f, Ink); surface.Button("⌫", 240f, 240f, 65f, 65f, delegate { if (number.Length > 0) { number = number.Substring(0, number.Length - 1); } Refresh(); }, Navy); bool flag = number.StartsWith("#"); surface.Label(flag ? T("Appel masqué") : T("Ajoute # avant le numéro pour appeler en masqué"), 0f, 190f, 530f, 38f, 19f, flag ? Blue : Muted); string[] array = new string[12] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "✎", "0", "#" }; string[] array2 = new string[12] { "", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ", T("Modifier"), "", T("Masqué") }; for (int num = 0; num < array.Length; num++) { string key = array[num]; float x = (num % 3 - 1) * 175; float num2 = 115 - num / 3 * 95; surface.Button("", x, num2, 145f, 85f, delegate { DialKey(key); }, (key == "#" && flag) ? Blue : Card); surface.Label(key, x, num2 + 9f, 135f, 47f, 35f, (key == "#" && flag) ? Color.white : Ink); surface.Label(array2[num], x, num2 - 27f, 135f, 25f, 15f, (key == "#" && flag) ? Color.white : Muted); } surface.Button(T("Appeler"), -85f, -280f, 350f, 70f, delegate { DialCall(video: false); }, ContactGreen); surface.MarkIcon("phone", -225f, -280f, 32f); surface.Button(T("Visio"), 195f, -280f, 145f, 70f, delegate { DialCall(video: true); }, Blue); PhoneTabs("dial"); } private void DialKey(string key) { if (key == "#") { number = (number.StartsWith("#") ? number.Substring(1) : ("#" + number)); Refresh(); return; } if (key == "✎") { Edit("dialNumber", "dial"); return; } if (number.TrimStart('#').Length < 10) { number += key; } Refresh(); } private void DialCall(bool video) { try { PhoneDial.Parse(number, out var _); mod.Request("call", number, "", "", "", video, 0L); } catch (Exception ex) { mod.LocalStatus(S(ex.Message)); } } private void DrawRecentCalls() { //IL_041b: Unknown result type (might be due to invalid IL or missing references) //IL_0456: Unknown result type (might be due to invalid IL or missing references) //IL_03e4: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_0325: Unknown result type (might be due to invalid IL or missing references) //IL_038e: Unknown result type (might be due to invalid IL or missing references) List all = mod.ScreenProfile.Calls; Header(T("Récents"), T("Historique des appels")); PhoneCallEntry[] array = all.Skip(offset).Take(4).ToArray(); for (int i = 0; i < array.Length; i++) { PhoneCallEntry entry = array[i]; float num = 210 - i * 120; bool privateIncoming = entry.Incoming && entry.Masked; PhoneContact phoneContact = (privateIncoming ? null : mod.ScreenProfile.Contacts.FirstOrDefault((PhoneContact c) => c.Number == entry.Number)); string text = (privateIncoming ? T("Numéro masqué") : (phoneContact?.Name ?? PhoneService.Display(entry.Number))); bool flag = entry.Incoming && (entry.Outcome == "missed" || entry.Outcome == "declined" || entry.Outcome == "cancelled"); surface.Button("", 0f, num, 540f, 110f, delegate { if (privateIncoming) { mod.LocalStatus(T("Impossible de rappeler un numéro masqué.")); } else { number = ((entry.Masked && !entry.Incoming) ? ("#" + entry.Number) : entry.Number); Go("dial"); } }, Card); ContactAvatar(phoneContact, text, -215f, num, 68f, Color.white); surface.Label(text, 30f, num + 24f, 365f, 38f, 26f, flag ? ContactRed : Ink, left: true); string text2 = (flag ? T("Manqué") : (entry.Incoming ? T("Entrant") : T("Sortant"))); if (entry.Outcome == "declined") { text2 = T("Refusé"); } else if (entry.Outcome == "cancelled" && !entry.Incoming) { text2 = T("Annulé"); } if (entry.Masked && !entry.Incoming) { text2 = text2 + " • " + T("Masqué"); } surface.Label(text2 + " • " + entry.Utc.ToLocalTime().ToString("dd/MM HH:mm"), 30f, num - 14f, 365f, 30f, 19f, Muted, left: true); if (entry.Outcome == "completed") { surface.Label(TimeSpan.FromSeconds(entry.Seconds).ToString("mm\\:ss"), 30f, num - 40f, 365f, 25f, 18f, Muted, left: true); } } if (array.Length == 0) { surface.Label(T("Aucun appel récent"), 0f, 80f, 510f, 100f, 28f, Muted); } surface.Button("◀", -220f, -285f, 80f, 45f, delegate { offset = Math.Max(0, offset - 4); Refresh(); }, Navy); surface.Button("▶", 220f, -285f, 80f, 45f, delegate { if (offset + 4 < all.Count) { offset += 4; } Refresh(); }, Navy); PhoneTabs("recentCalls"); } public void PushNotification(string title, string text, Action click = null, ulong owner = 0uL) { Toast item = new Toast { Owner = owner, Title = title, Text = text, Click = (click ?? ((Action)delegate { Go((title == "Messages") ? "inbox" : ((title == "Banque") ? "bank" : "nearby")); })), Until = Time.realtimeSinceStartup + 6f }; notifications.Insert(0, item); while (notifications.Count > 20) { notifications.RemoveAt(20); } banner = item; dirty = true; } public void OpenWindyNotification(bool live) { Go(live ? "liveList" : "windy"); mod.Request(live ? "sync" : "windySync", "", "", "", "", enabled: false, 0L); } public void LockStatus(string value) { pinError = value; dirty = true; } public void ForgetPrivate() { selectedMessage = null; mod.Notes?.Clear(); contactSearch = (pin = (pinFirst = (pinError = (number = (name = (body = (attached = (viewing = (windyName = (windyBody = (windyPhoto = (liveText = "")))))))))))); windySelected = null; amount = (editField = (returnPage = "")); pinConfirm = false; wasUnlocked = false; bankClosing = false; Go("home"); } private void TickExperienceUi() { bool screenUnlocked = mod.ScreenUnlocked; if (screenUnlocked && !wasUnlocked) { pin = (pinFirst = (pinError = "")); pinConfirm = false; Go((mod.View.Call.State != "Idle") ? "call" : "home"); } if (!screenUnlocked && wasUnlocked) { ForgetPrivate(); } wasUnlocked = screenUnlocked; string text = mod.Live.Active?.Id ?? ""; if (screenUnlocked && text != "" && (text != liveUiId || (mod.IsCasting && surface != null && !surface.IsHeld && page != "live"))) { Go("live"); } if (text == "" && liveUiId != "" && page == "live") { Go("liveList"); } liveUiId = text; if (banner != null && Time.realtimeSinceStartup >= banner.Until) { banner = null; dirty = true; } } private static string Short(string value, int max) { return (value.Length > max) ? (value.Substring(0, max - 1) + "…") : value; } private void Header(string title, string subtitle) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) surface.Label(title, 0f, 380f, 530f, 65f, 40f, Ink, left: true); surface.Label(subtitle, 0f, 325f, 530f, 45f, 21f, Muted, left: true); } private void DrawBank() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) PhoneView screenProfile = mod.ScreenProfile; Header("LabBank", T("Mes comptes / Banque personnelle")); surface.Panel(0f, 180f, 540f, 225f, Navy); surface.Label(T("COMPTE COURANT"), 0f, 250f, 490f, 38f, 21f, new Color(0.63f, 0.75f, 0.94f, 1f), left: true); surface.Label(screenProfile.BankReady ? (screenProfile.Balance.ToString("N0", CultureInfo.InvariantCulture) + " $") : T("Indisponible"), 0f, 175f, 490f, 80f, 50f, Color.white, left: true); surface.Label("LABRP •••• " + screenProfile.Number.Substring(Math.Max(0, screenProfile.Number.Length - 4)), 0f, 100f, 490f, 40f, 23f, Color.white, left: true); surface.Button(T("Faire un virement"), 0f, 5f, 540f, 75f, delegate { Go("bankTransfer"); }, Blue); surface.Label(T("DERNIÈRES OPÉRATIONS"), 0f, -75f, 530f, 40f, 22f, Muted, left: true); PhoneBankEntry[] array = screenProfile.Transactions.Take(2).ToArray(); if (array.Length == 0) { surface.Label(T("Aucun virement enregistré"), 0f, -165f, 510f, 65f, 25f, Muted); } for (int num = 0; num < array.Length; num++) { BankEntry(array[num], -150 - num * 90); } surface.Button(T("Tout l’historique"), -135f, -345f, 255f, 60f, delegate { Go("transactions"); }, Navy); surface.Button(T("Actualiser"), 145f, -345f, 245f, 60f, delegate { mod.Request("sync", "", "", "", "", enabled: false, 0L); }, Blue); } private void DrawBankTransfer() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) Header(T("Virement"), T("Un transfert entre comptes LabRP")); surface.Panel(0f, 155f, 540f, 240f, Card); surface.Label(T("BÉNÉFICIAIRE"), 0f, 245f, 490f, 35f, 20f, Muted, left: true); surface.Button((PhoneService.Display(number) == "") ? T("Choisir un numéro") : PhoneService.Display(number), 0f, 180f, 490f, 70f, delegate { Edit("number", "bankTransfer"); }, Navy); surface.Label(T("MONTANT"), 0f, 100f, 490f, 35f, 20f, Muted, left: true); surface.Button((amount == "") ? T("Saisir un montant") : (amount + " $"), 0f, 30f, 490f, 70f, delegate { Edit("amount", "bankTransfer"); }, Navy); surface.Button(T("Continuer"), 0f, -115f, 540f, 75f, delegate { if (PhoneBankRules.Amount(amount, out var _)) { Go("confirmBank"); } else { mod.LocalStatus(T("Saisir un montant entier positif.")); } }, Blue); surface.Label(T("Solde disponible : ") + mod.ScreenProfile.Balance.ToString("N0", CultureInfo.InvariantCulture) + " $", 0f, -230f, 520f, 65f, 25f, Muted); surface.Button(T("Retour aux comptes"), 0f, -345f, 400f, 60f, delegate { Go("bank"); }, Navy); } private void BankEntry(PhoneBankEntry t, float y) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) surface.Panel(0f, y, 540f, 80f, Card); surface.Label(t.Label + "\n" + t.Utc.ToLocalTime().ToString("dd/MM HH:mm"), -70f, y, 355f, 70f, 22f, Ink, left: true); surface.Label(((t.Amount > 0) ? "+" : "") + t.Amount + " $", 195f, y, 135f, 65f, 24f, (Color)((t.Amount > 0) ? new Color(0.04f, 0.5f, 0.32f, 1f) : Ink)); } private void DrawBankConfirm() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) Header(T("Confirmer le virement"), T("Vérifie le bénéficiaire et le montant")); surface.Panel(0f, 125f, 540f, 240f, Card); surface.Label(amount + " $", 0f, 175f, 500f, 80f, 48f, Ink); surface.Label(T("Vers ") + PhoneService.Display(number), 0f, 65f, 500f, 70f, 28f, Muted); surface.Button(T("Confirmer"), 0f, -100f, 530f, 75f, delegate { if (PhoneBankRules.Amount(amount, out var value)) { mod.Request("transfer", number, "", "", "", enabled: false, value); } Go("bank"); }, Blue); surface.Button(T("Modifier"), 0f, -235f, 420f, 65f, delegate { Go("bankTransfer"); }, Navy); } private void DrawTransactions() { Header(T("Opérations"), T("Historique des virements")); PhoneBankEntry[] array = mod.ScreenProfile.Transactions.Skip(offset).Take(5).ToArray(); for (int i = 0; i < array.Length; i++) { BankEntry(array[i], 235 - i * 105); } Pages(mod.ScreenProfile.Transactions.Count, 5); } private void DrawGallery() { //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) bool wallpaperPicker = page == "wallpaperPicker"; bool contactPicker = page == "contactPhoto"; bool attach = page == "attach"; List gallery = mod.Gallery; Header(wallpaperPicker ? T("Fond d’écran") : (contactPicker ? T("Photo du contact") : (attach ? T("Joindre une photo") : T("Galerie"))), gallery.Count + T(" photos • Photos de ce téléphone")); string[] array = gallery.Skip(offset).Take(9).ToArray(); for (int i = 0; i < array.Length; i++) { string id = array[i]; float x = (i % 3 - 1) * 180; float y = 225 - i / 3 * 170; surface.PhotoTile((Texture)(object)mod.Media.PictureFor(id), x, y, 170f, 128f, delegate { if (wallpaperPicker) { if (!mod.Access.Gallery.Contains(id)) { mod.LocalStatus(T("Photo en cours de synchronisation. Réessayer dans quelques secondes.")); } else { mod.Request("wallpaper", "", "", "", "", enabled: false, 0L, id); Go("appearance"); } } else if (contactPicker) { if (!mod.Access.Gallery.Contains(id)) { mod.LocalStatus(T("Photo en cours de synchronisation. Réessayer dans quelques secondes.")); } else { mod.Request("contactPhoto", number, "", "", "", enabled: false, 0L, id); Go("contact"); } } else if (attach) { if ((Object)(object)mod.Media.PictureFor(id) == (Object)null) { mod.LocalStatus(T("Attendre le chargement de la photo.")); } else { if (attachDestination == "windyCompose") { windyPhoto = id; } else { attached = id; } Go(attachDestination); } } else { viewing = id; viewerBack = "gallery"; mod.Media.ViewPhoto(id); Go("viewer"); } }); } if (gallery.Count == 0) { surface.Label(T("Ta Galerie est vide.\nPrends ta première photo dans Photo."), 0f, 40f, 510f, 140f, 27f, Muted); } Pages(gallery.Count, 9); if (wallpaperPicker) { surface.Button(T("Retour"), 0f, -410f, 360f, 45f, delegate { Go("appearance"); }, Navy); } if (contactPicker) { surface.Button(T("Retour au contact"), 0f, -410f, 360f, 45f, delegate { Go("contact"); }, Navy); } } private void DrawWindy() { //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_04a0: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02e4: Unknown result type (might be due to invalid IL or missing references) //IL_0325: Unknown result type (might be due to invalid IL or missing references) //IL_04dd: Unknown result type (might be due to invalid IL or missing references) //IL_051e: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_03b6: Unknown result type (might be due to invalid IL or missing references) //IL_0466: Unknown result type (might be due to invalid IL or missing references) Header("Windy", T("Pour vous / @") + mod.Windy.Handle); if (mod.Windy.Handle == "") { surface.Label(T("Rejoins la conversation"), 0f, 195f, 500f, 100f, 30f, Ink); surface.Button((windyName == "") ? T("Choisir un pseudo") : ("@" + windyName), 0f, 60f, 520f, 75f, delegate { Edit("windyName", "windy"); }, Navy); surface.Button(T("Créer mon compte"), 0f, -65f, 520f, 75f, delegate { mod.Request("windyJoin", "", windyName, "", "", enabled: false, 0L); }, Blue); return; } surface.Button(T("Publier"), -180f, 272f, 165f, 50f, delegate { Go("windyCompose"); }, Blue); surface.Button(T("Lives • ") + mod.Live.Available.Count, 0f, 272f, 165f, 50f, (Action)delegate { Go("liveList"); }, (Color?)new Color(0.84f, 0.12f, 0.31f, 1f)); surface.Button(T("Actualiser"), 180f, 272f, 165f, 50f, delegate { mod.Request("windySync", "", "", "", "", enabled: false, 0L, "", mod.Windy.Offset); }, Navy); WindyPostView post = mod.Windy.Posts.FirstOrDefault(); if (post != null) { surface.Panel(0f, -35f, 550f, 545f, Card); surface.Panel(-230f, 198f, 48f, 48f, Blue); surface.Label(post.Handle.Substring(0, 1).ToUpperInvariant(), -230f, 198f, 45f, 45f, 28f, Color.white); surface.Label("@" + post.Handle, -5f, 198f, 395f, 50f, 26f, Ink, left: true); surface.Label(Short(post.Text, (post.Photo != "") ? 110 : 280), 0f, (post.Photo != "") ? 120 : 40, 510f, (post.Photo != "") ? 100 : 280, 25f, Ink, left: true); if (post.Photo != "") { surface.PhotoTile((Texture)(object)mod.Media.PictureFor(post.Photo), 0f, -90f, 420f, 315f, delegate { windySelected = post; mod.Media.ViewPhoto(post.Photo); Go("windyRead"); }); } surface.Label(post.Utc.ToLocalTime().ToString("dd/MM HH:mm"), 0f, -280f, 510f, 35f, 20f, Muted, left: true); } else { surface.Label(T("Le fil est calme.\nPublie le premier message !"), 0f, 40f, 510f, 160f, 28f, Muted); } surface.Button(T("Plus récent"), -140f, -365f, 250f, 55f, delegate { mod.Request("windySync", "", "", "", "", enabled: false, 0L, "", Math.Max(0, mod.Windy.Offset - 1)); }, Navy); surface.Button(T("Plus ancien"), 140f, -365f, 250f, 55f, delegate { if (mod.Windy.Offset + 1 < mod.Windy.Total) { mod.Request("windySync", "", "", "", "", enabled: false, 0L, "", mod.Windy.Offset + 1); } }, Navy); } private void DrawWindyCompose() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) Header(T("Nouvelle publication"), "@" + mod.Windy.Handle); surface.Panel(0f, 150f, 540f, 240f, Card); surface.Label((windyBody == "") ? T("Quoi de neuf ?") : Short(windyBody, 200), 0f, 150f, 500f, 200f, 26f, Ink, left: true); surface.Button(T("Écrire le texte"), 0f, -30f, 530f, 65f, delegate { Edit("windyBody", "windyCompose"); }, Navy); surface.Button((windyPhoto == "") ? T("Joindre une photo") : T("Changer la photo"), 0f, -125f, 530f, 65f, delegate { attachDestination = "windyCompose"; Go("attach"); }, Navy); surface.Button(mod.Media.UploadBusy ? T("Envoi…") : T("Publier"), 0f, -235f, 530f, 75f, delegate { try { if (windyBody.Trim() == "" && windyPhoto == "") { throw new InvalidOperationException(T("Écrire un message ou joindre une photo.")); } if (windyPhoto != "") { mod.Media.SendWindyPhoto(windyPhoto, windyBody); } else { mod.Request("windyPost", "", "", windyBody, "", enabled: false, 0L); } } catch (Exception ex) { mod.LocalStatus(ex.Message); } }, Blue); if (windyPhoto != "") { surface.Button(T("Retirer la photo"), 0f, -345f, 350f, 50f, delegate { windyPhoto = ""; Refresh(); }, Navy); } } private void DrawWindyRead() { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) if (windySelected == null) { Go("windy"); return; } Header("@" + windySelected.Handle, windySelected.Utc.ToLocalTime().ToString("dd/MM HH:mm")); surface.Label(windySelected.Text, 0f, (windySelected.Photo != "") ? 195 : 95, 520f, (windySelected.Photo != "") ? 180 : 370, 24f, Ink, left: true); if (windySelected.Photo != "") { surface.PhotoTile((Texture)(object)mod.Media.PictureFor(windySelected.Photo), 0f, -115f, 400f, 300f, delegate { }); } surface.Button(T("Retour au fil"), 0f, -345f, 400f, 60f, delegate { Go("windy"); }, Navy); } private void DrawLiveList() { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) Header("Windy Live", T("Regarde, commente et partage le moment")); surface.Button(mod.IsCasting ? T("Ton live") : T("Passer en live"), 0f, 255f, 530f, 65f, (Action)delegate { Go(mod.IsCasting ? "live" : "liveSetup"); }, (Color?)new Color(0.85f, 0.11f, 0.3f, 1f)); for (int num = 0; num < mod.Live.Available.Count; num++) { WindyLiveSummary live = mod.Live.Available[num]; surface.Button("LIVE @" + live.Handle + "\n" + live.Viewers + T(" spectateurs • ") + live.Likes + " likes", 0f, 140 - num * 125, 530f, 105f, delegate { if (mod.Live.Casting && mod.Live.Active?.Id == live.Id) { Go("live"); } else { mod.Request("liveJoin", "", "", "", live.Id, enabled: false, 0L); } }, Navy); } if (mod.Live.Available.Count == 0) { surface.Label(T("Aucun live en cours"), 0f, 65f, 500f, 100f, 30f, Muted); } } private void DrawLiveSetup() { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) if (mod.IsCasting) { DrawLive(); return; } Header(T("Ton live"), T("La caméra et le micro sont diffusés sur Windy")); surface.Picture((Texture)(object)mod.Media.Camera.Preview, 0f, 95f, 440f, 330f); surface.Button(mod.Media.FrontCamera ? T("Caméra avant") : T("Caméra arrière"), 0f, -140f, 520f, 65f, delegate { mod.Media.FrontCamera = !mod.Media.FrontCamera; Refresh(); }, Navy); surface.Button(T("Commencer le live"), 0f, -245f, 520f, 75f, (Action)delegate { mod.Request("liveStart", "", "", "", "", mod.Media.FrontCamera, 0L); }, (Color?)new Color(0.85f, 0.11f, 0.3f, 1f)); surface.Label(T("Tu peux poser le téléphone pendant le live."), 0f, -350f, 510f, 70f, 22f, Muted); } private void DrawLive() { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0388: Unknown result type (might be due to invalid IL or missing references) //IL_03c9: Unknown result type (might be due to invalid IL or missing references) //IL_040a: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Unknown result type (might be due to invalid IL or missing references) WindyLiveSummary live = mod.Live.Active; if (live == null) { Go("liveList"); return; } surface.Label("LIVE @" + live.Handle, 0f, 385f, 520f, 60f, 32f, new Color(0.85f, 0.11f, 0.3f, 1f), left: true); surface.Label(live.Viewers + T(" spectateurs • ") + live.Likes + " likes", 0f, 330f, 520f, 40f, 23f, Muted, left: true); Texture val = (Texture)(mod.Live.Casting ? ((object)mod.Media.Camera.Live) : ((object)mod.Media.RemoteLive)); surface.Panel(0f, 105f, 530f, 400f, Navy); surface.Picture(val, 0f, 105f, 520f, 390f); if ((Object)(object)val == (Object)null) { surface.Label(T("Connexion au direct…"), 0f, 105f, 490f, 90f, 28f, Color.white); } WindyLiveComment[] array = mod.Live.Comments.TakeLast(3).ToArray(); for (int i = 0; i < array.Length; i++) { surface.Label("@" + array[i].Handle + " " + Short(array[i].Text, 75), 0f, -140 - i * 63, 525f, 58f, 21f, Ink, left: true); } if (mod.Live.Casting) { surface.Button(live.Front ? T("Caméra arrière") : T("Caméra avant"), -140f, -355f, 260f, 65f, delegate { mod.Request("liveCamera", "", "", "", live.Id, !live.Front, 0L); }, Blue); surface.Button(T("Terminer"), 140f, -355f, 250f, 65f, (Action)delegate { mod.Request("liveStop", "", "", "", live.Id, enabled: false, 0L); }, (Color?)new Color(0.85f, 0.11f, 0.3f, 1f)); return; } surface.Button(mod.Live.Liked ? T("Aimé") : T("J’aime"), -185f, -355f, 160f, 65f, (Action)delegate { mod.Request("liveLike", "", "", "", live.Id, enabled: false, 0L); }, (Color?)new Color(0.85f, 0.11f, 0.3f, 1f)); surface.Button(T("Commenter"), 0f, -355f, 190f, 65f, delegate { liveText = ""; Edit("liveText", "liveComment"); }, Blue); surface.Button(T("Quitter"), 190f, -355f, 150f, 65f, delegate { mod.Request("liveLeave", "", "", "", live.Id, enabled: false, 0L); }, Navy); } private void DrawLiveComment() { surface.Text(liveText, 145f, 240f, 28f); Row(T("Envoyer le commentaire"), -60f, delegate { if (mod.Live.Active != null) { mod.Request("liveComment", "", "", call: mod.Live.Active.Id, text: liveText, enabled: false, amount: 0L); } liveText = ""; Go("live"); }); Row(T("Modifier"), -175f, delegate { Edit("liveText", "liveComment"); }); } private void DrawNotifications() { surface.Text("Notifications", 300f, 70f, 35f); Toast[] array = notifications.Where((Toast toast) => toast.Owner == mod.ScreenOwner).Skip(offset).Take(4) .ToArray(); for (int num = 0; num < array.Length; num++) { Toast n = array[num]; surface.Button(T(n.Title) + "\n" + Short((n.Title == "Banque") ? S(n.Text) : n.Text, 70), 0f, 180 - num * 130, 540f, 115f, delegate { n.Click?.Invoke(); }); } Pages(notifications.Count((Toast toast) => toast.Owner == mod.ScreenOwner), 4); } private void DrawBanner() { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) if (page == "keyboard" || page == "pinChange") { return; } if (banner != null && banner.Owner == mod.ScreenOwner) { surface.Button(T(banner.Title) + " • " + Short((banner.Title == "Banque") ? S(banner.Text) : banner.Text, 60), 0f, 410f, 555f, 95f, (Action)delegate { Toast toast = banner; banner = null; toast?.Click?.Invoke(); Refresh(); }, (Color?)new Color(0.12f, 0.24f, 0.4f, 1f)); } if (page == "home") { surface.Button("Notifications • " + notifications.Count((Toast n) => n.Owner == mod.ScreenOwner), 0f, -455f, 300f, 45f, delegate { Go("notifications"); }); } } private void DrawCall() { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0342: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_03f5: Unknown result type (might be due to invalid IL or missing references) //IL_0539: Unknown result type (might be due to invalid IL or missing references) //IL_0451: Unknown result type (might be due to invalid IL or missing references) //IL_04a9: Unknown result type (might be due to invalid IL or missing references) //IL_05c7: Unknown result type (might be due to invalid IL or missing references) //IL_05c3: Unknown result type (might be due to invalid IL or missing references) //IL_0509: Unknown result type (might be due to invalid IL or missing references) //IL_061b: Unknown result type (might be due to invalid IL or missing references) //IL_0617: Unknown result type (might be due to invalid IL or missing references) //IL_066a: Unknown result type (might be due to invalid IL or missing references) //IL_0666: Unknown result type (might be due to invalid IL or missing references) //IL_06b9: Unknown result type (might be due to invalid IL or missing references) //IL_0715: Unknown result type (might be due to invalid IL or missing references) PhoneCallView call = mod.View.Call; PhoneContact phoneContact = ((call.Masked && call.Incoming) ? null : mod.ScreenProfile.Contacts.FirstOrDefault((PhoneContact c) => c.Number == call.Number)); string text = ((call.Masked && call.Incoming) ? T("Numéro masqué") : (phoneContact?.Name ?? PhoneService.Display(call.Number))); Color white = Color.white; Color val = default(Color); ((Color)(ref val))..ctor(0.08f, 0.69f, 0.38f, 1f); Color val2 = default(Color); ((Color)(ref val2))..ctor(0.89f, 0.16f, 0.23f, 1f); Color val3 = default(Color); ((Color)(ref val3))..ctor(0.16f, 0.21f, 0.29f, 1f); surface.Label(call.VideoRequested ? T("APPEL VIDÉO") : T("APPEL TÉLÉPHONIQUE"), 0f, 365f, 520f, 45f, 20f, new Color(0.64f, 0.73f, 0.85f, 1f)); surface.Label(text, 0f, 300f, 520f, 70f, 38f, white); if (call.Masked && !call.Incoming) { surface.Label(T("Appel masqué"), 0f, 240f, 520f, 40f, 23f, Blue); } else if (phoneContact != null) { surface.Label(PhoneService.Display(call.Number), 0f, 240f, 520f, 40f, 23f, new Color(0.64f, 0.73f, 0.85f, 1f)); } bool flag = call.State == "Active" && (call.Video || call.PeerVideo || call.VideoRequested); if (flag) { surface.Picture((Texture)(object)mod.Media.RemoteVideo, 0f, 75f, 470f, 280f); if ((Object)(object)mod.Media.RemoteVideo == (Object)null) { surface.Label(call.PeerVideo ? T("Connexion vidéo…") : T("Caméra du correspondant coupée"), 0f, 75f, 440f, 100f, 24f, white); } } else { ContactAvatar(phoneContact, text, 0f, 95f, 155f, white); } string text2 = ((call.State == "Active") ? TimeSpan.FromSeconds(Math.Max(0, callSecond)).ToString("mm\\:ss") : ((!(call.State == "Ringing")) ? T("Appel terminé") : (call.Incoming ? T("Appel entrant") : T("Appel en cours…")))); surface.Label(text2, 0f, flag ? (-100) : (-40), 520f, 45f, 25f, white); if (call.State == "Ringing") { if (call.Incoming) { surface.Tile("phone", T("Refuser"), -150f, -270f, val2, delegate { mod.Request("reject", "", "", "", call.Id, enabled: false, 0L); }); surface.Tile("phone", call.VideoRequested ? T("Avec caméra") : T("Répondre"), 150f, -270f, val, delegate { mod.Request("answer", "", "", "", call.Id, call.VideoRequested, 0L); }); if (call.VideoRequested) { surface.Button(T("Répondre en audio"), 0f, -135f, 430f, 60f, delegate { mod.Request("answer", "", "", "", call.Id, enabled: false, 0L); }, val3); } } else { surface.Tile("phone", T("Annuler"), 0f, -270f, val2, delegate { mod.Request("hangup", "", "", "", call.Id, enabled: false, 0L); }); } } else if (call.State == "Active") { surface.Tile("microphone", mod.CallMicMuted ? T("Micro coupé") : T("Micro"), -180f, -195f, mod.CallMicMuted ? val : val3, mod.ToggleCallMicrophone); surface.Tile("speaker", T("Haut-parleur"), 0f, -195f, call.Speaker ? val : val3, delegate { mod.Request("speaker", "", "", "", call.Id, !call.Speaker, 0L); }); surface.Tile("camera", T("Caméra"), 180f, -195f, call.Video ? val : val3, delegate { mod.Request("video", "", "", "", call.Id, !call.Video, 0L); }); surface.Button(T("Raccrocher"), 0f, -380f, 290f, 80f, delegate { mod.Request("hangup", "", "", "", call.Id, enabled: false, 0L); }, val2); if (call.PeerSpeaker) { surface.Label(T("Correspondant sur haut-parleur"), 0f, -108f, 530f, 35f, 19f, new Color(0.64f, 0.73f, 0.85f, 1f)); } } } private void PinKeys(Action complete) { //IL_017d: Unknown result type (might be due to invalid IL or missing references) surface.Text(string.Join(" ", from i in Enumerable.Range(0, 4) select (i < pin.Length) ? "●" : "○"), 205f, 65f, 34f); string[] array = new string[4] { "123", "456", "789", "0" }; for (int num = 0; num < array.Length; num++) { for (int num2 = 0; num2 < array[num].Length; num2++) { string key = array[num][num2].ToString(); surface.Button(key, ((float)num2 - (float)(array[num].Length - 1) * 0.5f) * 155f, 100 - num * 100, 120f, 85f, delegate { if (pin.Length < 4) { pin += key; pinError = ""; Refresh(); } }); } } surface.Button(T("Effacer"), -145f, -310f, 250f, 70f, delegate { if (pin.Length > 0) { pin = pin.Substring(0, pin.Length - 1); } Refresh(); }); surface.Button(T("Valider"), 145f, -310f, 250f, 70f, complete, Blue); if (pinError != "") { surface.Text(Short(pinError, 90), -405f, 95f, 22f); } } private void SubmitPin(bool setup) { if (!PhonePin.Valid(pin)) { pinError = T("Saisis les quatre chiffres du code."); Refresh(); } else if (setup && !pinConfirm) { pinFirst = pin; pin = ""; pinConfirm = true; Refresh(); } else if (setup && pin != pinFirst) { pinError = T("Les codes ne correspondent pas."); pin = (pinFirst = ""); pinConfirm = false; Refresh(); } else { mod.Request(setup ? "pin" : "unlock", "", "", pin, "", enabled: false, 0L); pin = (pinFirst = ""); pinConfirm = false; Refresh(); } } private void DrawLock() { surface.Text(clock, 385f, 110f, 75f); surface.Text(PhoneLanguage.Date(mod.Sounds.Preferences.Language, DateTime.Now), 310f, 50f, 24f); bool flag = mod.ScreenOwner == PlayerIDManager.LocalPlatformID; bool setup = !mod.ScreenPinSet; if (setup && !flag) { surface.Text(T("Téléphone verrouillé\nLe propriétaire doit créer son code."), 70f, 200f); return; } surface.Text((!setup) ? T("Saisis le code du téléphone") : (pinConfirm ? T("Confirme ton code") : T("Crée ton code à quatre chiffres")), 265f, 65f, 27f); PinKeys(delegate { SubmitPin(setup); }); int num = notifications.Count((Toast n) => n.Owner == mod.ScreenOwner); if (num > 0) { surface.Text(num + T(" notification(s)"), -475f, 35f, 19f); } } private void DrawPinChange() { if (mod.ScreenOwner != PlayerIDManager.LocalPlatformID) { surface.Text(T("Le code est réservé au propriétaire."), 100f, 150f); return; } surface.Text(pinConfirm ? T("Confirme le nouveau code") : T("Nouveau code à quatre chiffres"), 300f, 75f, 29f); PinKeys(delegate { SubmitPin(setup: true); }); } private string MessagePreview(PhoneSms message) { return (message.Voice != "") ? (T("Message vocal") + " • " + message.VoiceSeconds + " s") : ((message.Photo != "") ? (T("Photo") + ((message.Text != "") ? (" • " + message.Text) : "")) : message.Text); } private void DrawInbox() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_034f: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02f9: Unknown result type (might be due to invalid IL or missing references) Header("Messages", T("Tes conversations")); surface.Button(T("Nouveau message"), 0f, 275f, 530f, 55f, delegate { number = (body = (attached = "")); Go("compose"); }, Blue); PhoneView view = mod.ScreenProfile; string[] array = (from m in view.Messages orderby m.Utc descending select (m.From == view.Number) ? m.To : m.From).Distinct().ToArray(); string[] array2 = array.Skip(offset).Take(5).ToArray(); for (int num = 0; num < array2.Length; num++) { string n = array2[num]; PhoneContact phoneContact = view.Contacts.FirstOrDefault((PhoneContact c) => c.Number == n); PhoneSms phoneSms = (from m in view.Messages where m.From == n || m.To == n orderby m.Utc descending select m).First(); bool flag = view.Messages.Any((PhoneSms m) => m.From == n && !m.Read); float num2 = 185 - num * 98; surface.Button("", 0f, num2, 540f, 90f, delegate { number = n; Go("thread"); mod.Request("read", n, "", "", "", enabled: false, 0L); }, Card); ContactAvatar(phoneContact, phoneContact?.Name ?? n, -218f, num2, 62f, Color.white); surface.Label((flag ? "● " : "") + (phoneContact?.Name ?? PhoneService.Display(n)), 10f, num2 + 22f, 350f, 34f, 24f, flag ? Blue : Ink, left: true); surface.Label(Short(MessagePreview(phoneSms), 42), 10f, num2 - 16f, 350f, 37f, 19f, Muted, left: true); surface.Label(phoneSms.Utc.ToLocalTime().ToString("HH:mm"), 225f, num2 + 24f, 80f, 30f, 16f, Muted); } if (array2.Length == 0) { surface.Label(T("Aucun message"), 0f, 70f, 510f, 100f, 28f, Muted); } Pages(array.Length, 5); } private void DrawThread() { //IL_04d1: Unknown result type (might be due to invalid IL or missing references) //IL_0512: Unknown result type (might be due to invalid IL or missing references) //IL_055e: Unknown result type (might be due to invalid IL or missing references) //IL_0494: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Unknown result type (might be due to invalid IL or missing references) //IL_03bb: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0441: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_0341: Unknown result type (might be due to invalid IL or missing references) Header(SelectedContact?.Name ?? PhoneService.Display(number), T("Conversation")); PhoneSms[] array = (from m in mod.ScreenProfile.Messages where m.From == number || m.To == number orderby m.Utc descending select m).ToArray(); PhoneSms[] array2 = array.Skip(offset).Take(2).Reverse() .ToArray(); for (int num = 0; num < array2.Length; num++) { PhoneSms message = array2[num]; bool flag = message.From == mod.ScreenProfile.Number; float x = (flag ? 35 : (-35)); float num2 = 160 - num * 230; Color val = (flag ? Blue : Card); Color color = (flag ? Color.white : Ink); surface.Panel(x, num2, 475f, 210f, val); if (message.Photo != "") { surface.PhotoTile((Texture)(object)mod.Media.PictureFor(message.Photo), x, num2 + 22f, 220f, 125f, delegate { viewing = message.Photo; viewerBack = "thread"; mod.Media.ViewPhoto(message.Photo); Go("viewer"); }); if (message.Text != "") { surface.Label(Short(message.Text, 100), x, num2 - 62f, 435f, 52f, 19f, color, left: true); } } else if (message.Voice != "") { string id = message.Voice; surface.Button(T((mod.Notes.DownloadingId == id) ? "Chargement…" : ((mod.Notes.PlayingId == id) ? "Arrêter" : "Écouter")) + " • " + T("Vocal") + " • " + message.VoiceSeconds + " s", x, num2 + 5f, 410f, 70f, delegate { try { if (mod.Notes.PlayingId == id || mod.Notes.DownloadingId == id) { mod.Notes.StopPlayback(); } else { mod.Notes.Listen(id); } Refresh(); } catch (Exception ex) { mod.LocalStatus(S(ex.Message)); } }, flag ? Navy : Blue); DrawVoiceBars(x, num2 - 58f, color); } else { surface.Button("", x, num2 + 12f, 435f, 155f, delegate { selectedMessage = message; Go("messageDetail"); }, val); surface.Label(Short(message.Text, 150), x, num2 + 12f, 425f, 145f, 23f, color, left: true); } surface.Label(message.Utc.ToLocalTime().ToString("HH:mm") + (flag ? (" • " + T(message.Read ? "Lu" : "Envoyé")) : ""), x, num2 - 87f, 425f, 28f, 15f, color, left: true); } if (array2.Length == 0) { surface.Label(T("Commence la conversation"), 0f, 100f, 510f, 100f, 27f, Muted); } surface.Button(T("Écrire"), -140f, -270f, 250f, 65f, delegate { attached = (body = ""); Go("compose"); }, Blue); surface.Button(T("Vocal"), 140f, -270f, 250f, 65f, delegate { Go("voiceRecord"); }, Navy); Pages(array.Length, 2); surface.Button(T("Messages"), 0f, -355f, 180f, 55f, delegate { Go("inbox"); }, Navy); } private void DrawMessageDetail() { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) if (selectedMessage == null) { Go("thread"); return; } Header(T("Message"), SelectedContact?.Name ?? PhoneService.Display(number)); surface.Panel(0f, 55f, 540f, 460f, Card); surface.Label(selectedMessage.Text, 0f, 55f, 500f, 425f, 23f, Ink, left: true); surface.Button(T("Retour"), 0f, -300f, 380f, 60f, delegate { Go("thread"); }, Navy); } private void DrawVoiceBars(float x, float y, Color color) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) int[] array = new int[14] { 8, 17, 26, 13, 31, 22, 9, 27, 18, 12, 22, 30, 13, 8 }; for (int i = 0; i < array.Length; i++) { surface.Panel(x + ((float)i - (float)(array.Length - 1) * 0.5f) * 19f, y, 7f, array[i], color); } } private void DrawVoiceRecorder() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_039f: Unknown result type (might be due to invalid IL or missing references) //IL_03dc: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_0363: Unknown result type (might be due to invalid IL or missing references) Header(T("Message vocal"), SelectedContact?.Name ?? PhoneService.Display(number)); surface.Panel(0f, 135f, 530f, 200f, Card); surface.Label(mod.Notes.Recording ? T("Enregistrement…") : (mod.Notes.Draft ? T("Vocal prêt") : T("Enregistre ta voix")), 0f, 195f, 490f, 65f, 30f, Ink); surface.Label(TimeSpan.FromSeconds(mod.Notes.Elapsed).ToString("mm\\:ss") + " / 00:15", 0f, 100f, 490f, 65f, 40f, mod.Notes.Recording ? ContactRed : Ink); if (mod.Notes.Recording) { surface.Label(T("Niveau micro") + " : " + (int)(mod.Notes.InputLevel * 100f) + " %", 0f, 25f, 490f, 32f, 20f, Muted); surface.Label(T(mod.Notes.MicrophoneStatus), 0f, -100f, 510f, 30f, 18f, Muted); } if (mod.Notes.Recording) { surface.Button(T("Arrêter"), 0f, -40f, 530f, 75f, delegate { NoteAction(delegate { mod.Notes.Stop(); }); }, ContactRed); } else { surface.Button(mod.Notes.Draft ? T("Réenregistrer") : T("Enregistrer"), 0f, -40f, 530f, 75f, delegate { NoteAction(delegate { mod.Notes.Start(); }); }, ContactRed); } if (mod.Notes.Draft) { surface.Button(T("Réécouter"), -140f, -155f, 250f, 70f, delegate { NoteAction(delegate { mod.Notes.Preview(); }); }, Navy); surface.Button(mod.Notes.Sending ? T("Envoi…") : T("Envoyer"), 140f, -155f, 250f, 70f, delegate { NoteAction(delegate { mod.Notes.Send(number); }); }, Blue); } surface.Label(T("Micro Fusion • 15 secondes maximum"), 0f, -265f, 510f, 50f, 20f, Muted); surface.Button(T("Annuler"), 0f, -350f, 380f, 60f, delegate { mod.Notes.Clear(); Go("thread"); }, Navy); } private void NoteAction(Action action) { try { action(); Refresh(); } catch (Exception ex) { mod.LocalStatus(S(ex.Message)); } } private void DrawSettings() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_0332: Unknown result type (might be due to invalid IL or missing references) Header(T("Réglages"), "WindyPhone"); surface.Button(T("Appels : ") + T(PhoneSounds.CallNames[mod.Sounds.Preferences.CallTone]), 0f, 235f, 530f, 65f, delegate { callTone = true; Go("tones"); }, Navy); surface.Button(T("Messages : ") + T(PhoneSounds.MessageNames[mod.Sounds.Preferences.MessageTone]), 0f, 150f, 530f, 65f, delegate { callTone = false; Go("tones"); }, Navy); surface.Label(T("Volume des sonneries : ") + Math.Round(mod.Sounds.Preferences.RingVolume * 100f) + " %", 0f, 75f, 530f, 45f, 24f, Ink); surface.Button("− 10 %", -140f, 10f, 250f, 55f, delegate { Volume(-0.1f); }, Blue); surface.Button("+ 10 %", 140f, 10f, 250f, 55f, delegate { Volume(0.1f); }, Blue); surface.Button(T("Fluidité caméra / visio"), 0f, -75f, 530f, 65f, delegate { Go("cameraSettings"); }, Navy); surface.Button(T("Langue") + " : " + ((mod.Sounds.Preferences.Language == "en") ? "English" : "Français"), 0f, -165f, 530f, 65f, delegate { Go("language"); }, Blue); surface.Button(T("Changer le code"), -140f, -275f, 250f, 70f, delegate { pin = (pinFirst = (pinError = "")); pinConfirm = false; Go("pinChange"); }, Navy); surface.Button(T("Verrouiller"), 140f, -275f, 250f, 70f, delegate { mod.LockPhone(); }, Navy); surface.Button(T("Apparence"), 0f, -355f, 530f, 55f, delegate { Go("appearance"); }, Blue); } private void DrawLanguage() { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) Header(T("Langue"), "WindyPhone • Français / English"); surface.Button(((mod.Sounds.Preferences.Language == "fr") ? "✓ " : "") + "Français", 0f, 190f, 530f, 90f, delegate { Language("fr"); }, Blue); surface.Button(((mod.Sounds.Preferences.Language == "en") ? "✓ " : "") + "English", 0f, 60f, 530f, 90f, delegate { Language("en"); }, Navy); surface.Button(T("Retour"), 0f, -200f, 400f, 65f, delegate { Go("settings"); }, Navy); } private void Language(string language) { try { mod.Sounds.SetLanguage(language); Refresh(); } catch (Exception ex) { mod.LocalStatus(ex.Message); } } } public sealed class PhoneAudioBuffer { private readonly SortedDictionary pending = new SortedDictionary(); private uint played; public int Count => pending.Count; public bool Push(uint sequence, byte[] bytes, double now) { if (sequence == 0 || sequence <= played || pending.ContainsKey(sequence) || bytes == null || bytes.Length == 0 || pending.Count >= 32) { return false; } pending.Add(sequence, (bytes, now)); return true; } public void Drain(double now, Action play, Action missing = null) { while (pending.Count > 0) { KeyValuePair keyValuePair = pending.First(); if (now - keyValuePair.Value.Item2 < 0.08) { break; } pending.Remove(keyValuePair.Key); if (played != 0 && keyValuePair.Key > played + 1) { missing?.Invoke((int)Math.Min(20u, keyValuePair.Key - played - 1)); } played = keyValuePair.Key; play(keyValuePair.Value.Item1); } } public void Clear() { pending.Clear(); played = 0u; } public static float Limit(float sample) { return (float.IsNaN(sample) || float.IsInfinity(sample)) ? 0f : Math.Max(-0.96f, Math.Min(0.96f, sample)); } } public sealed class PhoneAudioWindow { private uint highest; private ulong seen; public bool Accept(uint seq) { if (seq == 0) { return false; } if (seq > highest) { uint num = seq - highest; seen = ((num >= 64) ? 1 : ((seen << (int)num) | 1)); highest = seq; return true; } uint num2 = highest - seq; if (num2 >= 64) { return false; } ulong num3 = (ulong)(1L << (int)num2); if ((seen & num3) != 0) { return false; } seen |= num3; return true; } } public sealed class PhoneAudioPlayer : IDisposable { private readonly PhonePcmBuffer buffer; private readonly PhoneVoiceFilter filter; private readonly AudioClip clip; private readonly PhoneVoiceContinuity continuity = new PhoneVoiceContinuity(); private float lastVolume; private readonly Action> reader; private readonly PCMReaderCallback callback; private float[] block = Array.Empty(); private bool disposed; public GameObject gameObject { get; } public Transform transform => gameObject.transform; public AudioSource AudioSource { get; } public PhoneAudioPlayer() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown int outputSampleRate = AudioInfo.OutputSampleRate; filter = new PhoneVoiceFilter(outputSampleRate, 18000.0); buffer = new PhonePcmBuffer(outputSampleRate); gameObject = new GameObject("RP_BufferedPhoneAudio"); AudioSource = gameObject.AddComponent(); AudioSource.playOnAwake = false; AudioSource.loop = true; AudioSource.dopplerLevel = 0f; AudioSource.bypassReverbZones = true; AudioSource.reverbZoneMix = 0f; reader = Fill; callback = PCMReaderCallback.op_Implicit(reader); clip = AudioClip.Create("RP_PhoneStreamingVoice", outputSampleRate, 1, outputSampleRate, true, callback); AudioSource.clip = clip; AudioSource.Play(); } private void Fill(Il2CppStructArray data) { if (block.Length != ((Il2CppArrayBase)(object)data).Length) { block = new float[((Il2CppArrayBase)(object)data).Length]; } buffer.Read(block); IntPtr pointer = ((Il2CppObjectBase)data).Pointer; for (int i = 0; i < block.Length; i++) { InteropUtilities.FloatArrayFastWrite(pointer, IntPtr.Size, i, block[i]); } } public void Push(short[] pcm, float volume) { if (pcm != null) { lastVolume = volume; pcm = continuity.Prepare(pcm); short[] array = new short[pcm.Length]; for (int i = 0; i < pcm.Length; i++) { array[i] = (short)Math.Clamp(PhoneVoiceContinuity.Limit(filter.Process((float)pcm[i] / 32768f)) * 32767f, -32767f, 32767f); } buffer.Push(array, volume); } } public void Conceal(int frames) { short[] array = continuity.Fill(Math.Clamp(frames, 0, 20) * AudioInfo.OutputSampleRate / 100); short[] array2 = new short[array.Length]; for (int i = 0; i < array.Length; i++) { array2[i] = (short)(PhoneVoiceContinuity.Limit(filter.Process((float)array[i] / 32768f)) * 32767f); } buffer.Push(array2, lastVolume); } public void Dispose() { if (!disposed) { disposed = true; buffer.Dispose(); AudioSource.Stop(); Object.Destroy((Object)(object)gameObject); Object.Destroy((Object)(object)clip); } } } public static class PhoneBankRules { public static bool Amount(string text, out long value) { value = 0L; return text != null && text.Length > 0 && text.Length <= 12 && text.All((char c) => c >= '0' && c <= '9') && long.TryParse(text, out value) && value > 0 && value <= 999999999999L; } } public sealed class PhoneCallView { public string Id { get; set; } = ""; public string State { get; set; } = "Idle"; public ulong Peer { get; set; } public string Number { get; set; } = ""; public bool VideoRequested { get; set; } public bool Video { get; set; } public bool PeerVideo { get; set; } public bool Speaker { get; set; } public bool PeerSpeaker { get; set; } public bool Masked { get; set; } public bool Incoming { get; set; } } public sealed class PhoneCallUpdate { public int Protocol { get; set; } = 16; public string Session { get; set; } = ""; public ulong Player { get; set; } public long Revision { get; set; } public PhoneCallView Call { get; set; } = new PhoneCallView(); public string Notice { get; set; } = ""; } public sealed class PhoneCallResult { public string Id; public string Outcome; public ulong Caller; public ulong Callee; public bool Masked; public bool Answered; public int Seconds; public DateTime Utc; } public static class PhoneDial { public static string Parse(string input, out bool masked) { if (input == null || input.Length > 41) { throw new InvalidOperationException("Numéro invalide."); } input = input.Trim(); masked = input.StartsWith("#", StringComparison.Ordinal); return PhoneService.Normalize(masked ? input.Substring(1) : input); } public static string Display(string input) { if (string.IsNullOrEmpty(input)) { return ""; } bool flag = input.StartsWith("#"); return (flag ? "# " : "") + PhoneService.Display(flag ? input.Substring(1) : input); } } public sealed class PhoneCalls { private sealed class Call { public string Id = Guid.NewGuid().ToString("N"); public string State = "Ringing"; public ulong Caller; public ulong Callee; public bool CallerSpeaker; public bool CalleeSpeaker; public bool VideoRequested; public bool CallerVideo; public bool CalleeVideo; public bool Masked; public bool Answered; public double AnsweredAt; public DateTime Utc = DateTime.UtcNow; public string CallerToken = ""; public string CalleeToken = ""; public double Created; public double CallerSeen; public double CalleeSeen; } private readonly Dictionary byPlayer = new Dictionary(); public Func Clock { get; set; } = () => (double)Environment.TickCount64 / 1000.0; public event Action Finished; public bool Busy(ulong player) { return byPlayer.ContainsKey(player); } public ulong[] Start(ulong caller, ulong callee, double now, bool video = false, bool masked = false) { if (caller == callee) { throw new InvalidOperationException("Impossible de s'appeler soi-même."); } if (Busy(caller)) { throw new InvalidOperationException("Tu es déjà en appel."); } if (Busy(callee)) { throw new InvalidOperationException("Ce correspondant est occupé."); } Call value = new Call { Caller = caller, Callee = callee, Created = now, CallerSeen = now, CalleeSeen = now, VideoRequested = video, CallerVideo = video, Masked = masked }; byPlayer.Add(caller, value); byPlayer.Add(callee, value); return new ulong[2] { caller, callee }; } private Call Get(ulong actor, string token) { if (!byPlayer.TryGetValue(actor, out var value) || string.IsNullOrEmpty(token) || value.Id != token) { throw new InvalidOperationException("Cet appel est terminé."); } return value; } public ulong[] Answer(ulong actor, string token, double now, bool video = false) { Call call = Get(actor, token); if (actor != call.Callee || call.State != "Ringing") { throw new InvalidOperationException("Aucun appel entrant à accepter."); } if (now - call.Created >= 30.0) { throw new InvalidOperationException("Appel expiré."); } call.Answered = true; call.AnsweredAt = now; call.CalleeVideo = video; call.State = "Active"; call.CallerSeen = (call.CalleeSeen = now); return new ulong[2] { call.Caller, call.Callee }; } public ulong[] Reject(ulong actor, string token) { Call call = Get(actor, token); if (actor != call.Callee || call.State != "Ringing") { throw new InvalidOperationException("Aucun appel entrant à refuser."); } return End(call, "declined"); } public ulong[] Hangup(ulong actor, string token) { Call call = Get(actor, token); return End(call, (call.State == "Active") ? "completed" : ((actor == call.Caller) ? "cancelled" : "declined")); } public void Heartbeat(ulong actor, string token, double now) { Call call = Get(actor, token); if (actor == call.Caller) { call.CallerSeen = now; } else { call.CalleeSeen = now; } } public bool TryRoute(ulong actor, string token, double now, out ulong recipient) { recipient = 0uL; if (!byPlayer.TryGetValue(actor, out var value) || value.Id != token || value.State != "Active" || now - value.CallerSeen >= 12.0 || now - value.CalleeSeen >= 12.0) { return false; } recipient = ((actor == value.Caller) ? value.Callee : value.Caller); return true; } public PhoneCallView View(ulong actor, Func number) { if (!byPlayer.TryGetValue(actor, out var value)) { return new PhoneCallView(); } ulong num = ((actor == value.Caller) ? value.Callee : value.Caller); return new PhoneCallView { VideoRequested = value.VideoRequested, Video = ((actor == value.Caller) ? value.CallerVideo : value.CalleeVideo), PeerVideo = ((actor == value.Caller) ? value.CalleeVideo : value.CallerVideo), Id = value.Id, State = value.State, Peer = num, Masked = value.Masked, Number = ((value.Masked && actor == value.Callee) ? "" : number(num)), Speaker = ((actor == value.Caller) ? value.CallerSpeaker : value.CalleeSpeaker), PeerSpeaker = ((actor == value.Caller) ? value.CalleeSpeaker : value.CallerSpeaker), Incoming = (actor == value.Callee) }; } public ulong[] SetVideo(ulong actor, string token, bool enabled) { Call call = Get(actor, token); if (call.State != "Active") { throw new InvalidOperationException("Accepter l'appel avant la caméra."); } if (actor == call.Caller) { call.CallerVideo = enabled; } else { call.CalleeVideo = enabled; } return new ulong[2] { call.Caller, call.Callee }; } public bool VideoEnabled(ulong actor, string token) { Call value; return byPlayer.TryGetValue(actor, out value) && value.Id == token && value.State == "Active" && ((actor == value.Caller) ? value.CallerVideo : value.CalleeVideo); } public ulong[] SetSpeaker(ulong actor, string token, bool enabled) { Call call = Get(actor, token); if (call.State != "Active") { throw new InvalidOperationException("Accepter l'appel avant d'activer le haut-parleur."); } if (actor == call.Caller && call.CallerSpeaker != enabled) { call.CallerSpeaker = enabled; call.CallerToken = Guid.NewGuid().ToString("N"); } if (actor == call.Callee && call.CalleeSpeaker != enabled) { call.CalleeSpeaker = enabled; call.CalleeToken = Guid.NewGuid().ToString("N"); } return new ulong[2] { call.Caller, call.Callee }; } public (ulong Owner, string Token)[] Speakers(ulong actor, string token) { if (!byPlayer.TryGetValue(actor, out var value) || value.Id != token || value.State != "Active") { return Array.Empty<(ulong, string)>(); } List<(ulong, string)> list = new List<(ulong, string)>(); if (value.CallerSpeaker) { list.Add((value.Caller, value.CallerToken)); } if (value.CalleeSpeaker) { list.Add((value.Callee, value.CalleeToken)); } return list.ToArray(); } private ulong[] End(Call call, string outcome = null) { byPlayer.Remove(call.Caller); byPlayer.Remove(call.Callee); this.Finished?.Invoke(new PhoneCallResult { Id = call.Id, Caller = call.Caller, Callee = call.Callee, Masked = call.Masked, Answered = call.Answered, Utc = call.Utc, Outcome = (outcome ?? (call.Answered ? "completed" : "missed")), Seconds = (call.Answered ? ((int)Math.Min(86400.0, Math.Max(0.0, Clock() - call.AnsweredAt))) : 0) }); return new ulong[2] { call.Caller, call.Callee }; } public ulong[] Disconnect(ulong actor) { Call value; return byPlayer.TryGetValue(actor, out value) ? End(value) : Array.Empty(); } public ulong[] Tick(double now) { Call[] source = (from c in byPlayer.Values.Distinct() where (c.State == "Ringing") ? (now - c.Created >= 30.0) : (now - c.CallerSeen >= 12.0 || now - c.CalleeSeen >= 12.0) select c).ToArray(); return source.SelectMany((Call c) => End(c)).Distinct().ToArray(); } public ulong[] StopAll() { Call[] source = byPlayer.Values.Distinct().ToArray(); return source.SelectMany((Call c) => End(c)).Distinct().ToArray(); } } public sealed class PhoneCamera : IDisposable { private GameObject go; private Camera camera; private RenderTexture preview; private RenderTexture live; private RenderTexture photo; private Texture2D smallPixels; private Texture2D largePixels; private bool disposed; private readonly Action warn; public bool Busy { get; private set; } public RenderTexture Preview => preview; public RenderTexture Live => live; public PhoneCamera(Action warn) { this.warn = warn; } public bool Capture(PhoneSurface screen, bool large, bool front, bool encode, Action completed, bool photoPreview = false, bool allowUnheld = false) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Expected O, but got Unknown //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Expected O, but got Unknown //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Expected O, but got Unknown //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) if (disposed || Busy || screen == null || (Object)(object)screen.Body == (Object)null || (!screen.IsHeld && !allowUnheld) || !screen.PlacementReady) { return false; } if ((Object)(object)camera == (Object)null) { go = new GameObject("RP_LocalPhoneCamera"); camera = go.AddComponent(); ((Behaviour)camera).enabled = false; camera.stereoTargetEye = (StereoTargetEyeMask)0; camera.fieldOfView = 70f; camera.nearClipPlane = 0.04f; camera.farClipPlane = 200f; camera.cullingMask = -33; camera.clearFlags = (CameraClearFlags)1; camera.allowHDR = false; camera.allowMSAA = false; preview = new RenderTexture(320, 240, 24, (RenderTextureFormat)0); preview.Create(); live = new RenderTexture(320, 240, 24, (RenderTextureFormat)0); live.Create(); smallPixels = new Texture2D(320, 240, (TextureFormat)3, false); } if (large && (Object)(object)photo == (Object)null) { photo = new RenderTexture(640, 480, 24, (RenderTextureFormat)0); photo.Create(); largePixels = new Texture2D(640, 480, (TextureFormat)3, false); } Transform cameraPose = screen.CameraPose; Vector3 val = (front ? (-cameraPose.forward) : cameraPose.forward); ((Component)camera).transform.position = cameraPose.position + val * 0.05f; ((Component)camera).transform.rotation = Quaternion.LookRotation(val, cameraPose.up); camera.targetTexture = (large ? photo : (photoPreview ? preview : live)); camera.aspect = 1.3333334f; Busy = true; ((Behaviour)camera).enabled = true; try { MelonCoroutines.Start(ReadFrame(large, encode, completed, camera.targetTexture)); } catch { ((Behaviour)camera).enabled = false; Busy = false; throw; } return true; } private IEnumerator ReadFrame(bool large, bool encode, Action completed, RenderTexture target) { yield return (object)new WaitForEndOfFrame(); if (disposed) { Busy = false; yield break; } try { ((Behaviour)camera).enabled = false; if (!encode) { completed?.Invoke(null); yield break; } Texture2D pixels = (large ? largePixels : smallPixels); RenderTexture previous = RenderTexture.active; try { RenderTexture.active = target; pixels.ReadPixels(new Rect(0f, 0f, (float)(large ? 640 : 320), (float)(large ? 480 : 240)), 0, 0, false); pixels.Apply(false, false); } finally { RenderTexture.active = previous; } byte[] bytes = null; int cap = (large ? 65536 : 16384); int[] array = ((!large) ? new int[3] { 30, 20, 12 } : new int[3] { 70, 45, 25 }); foreach (int quality in array) { bytes = ((IEnumerable)ImageConversion.EncodeToJPG(pixels, quality)).ToArray(); if (bytes.Length <= cap) { break; } } if (bytes.Length > cap || !PhoneMediaRules.Jpeg(bytes, large ? 640 : 320, large ? 480 : 240)) { throw new InvalidOperationException("Image trop volumineuse."); } completed?.Invoke(bytes); } catch (Exception ex) { Exception e = ex; warn("Caméra téléphone : " + e.Message); PhoneMod.Instance?.LocalStatus("Capture impossible : " + e.Message); } finally { if ((Object)(object)camera != (Object)null) { ((Behaviour)camera).enabled = false; } Busy = false; } } public void Dispose() { disposed = true; if ((Object)(object)camera != (Object)null) { ((Behaviour)camera).enabled = false; } if ((Object)(object)go != (Object)null) { Object.Destroy((Object)(object)go); } RenderTexture[] array = (RenderTexture[])(object)new RenderTexture[3] { preview, live, photo }; foreach (RenderTexture val in array) { if ((Object)(object)val != (Object)null) { val.Release(); Object.Destroy((Object)(object)val); } } if ((Object)(object)smallPixels != (Object)null) { Object.Destroy((Object)(object)smallPixels); } if ((Object)(object)largePixels != (Object)null) { Object.Destroy((Object)(object)largePixels); } camera = null; go = null; preview = (live = (photo = null)); smallPixels = (largePixels = null); } } public sealed class PhoneDevices { private sealed class Device { public NetworkEntity Entity; public GameObject Object; public bool Pending; public float Started; public int Token; public string Error = ""; public string Barcode = ""; public PhoneDeviceInfo Placement; public float Recalled; } private readonly Dictionary devices = new Dictionary(); public Action Parked; private int generation; private long placements; private string cachedBarcode = ""; private Spawnable cachedSpawnable; public ushort? Id(ulong player) { Device value; return (devices.TryGetValue(player, out value) && (Object)(object)value.Object != (Object)null && value.Object.activeInHierarchy && value.Entity != null && value.Entity.IsRegistered) ? new ushort?(value.Entity.ID) : ((ushort?)null); } public PhoneDeviceInfo[] Catalog(Func pin) { return (from p in devices.Keys where Id(p).HasValue select Info(p, Id(p).Value, pin(p))).ToArray(); } private PhoneDeviceInfo Info(ulong owner, ushort id, bool pin) { PhoneDeviceInfo placement = devices[owner].Placement; return new PhoneDeviceInfo { Owner = owner, Device = id, PinSet = pin, Parked = (placement?.Parked ?? false), Placement = (placement?.Placement ?? 0), X = (placement?.X ?? 0f), Y = (placement?.Y ?? 0f), Z = (placement?.Z ?? 0f), Yaw = (placement?.Yaw ?? 0f) }; } public void Tick(Func held) { foreach (KeyValuePair device in devices) { Device value = device.Value; ushort? num = Id(device.Key); if (num.HasValue) { bool flag = held(device.Key, num.Value); if ((value.Placement?.Parked ?? false) && flag) { value.Placement.Parked = false; } } } } private void Park(Device d, Vector3 position, float yaw, ulong owner, bool preserveRotation = false) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) d.Placement = new PhoneDeviceInfo { Owner = owner, Device = d.Entity.ID, Parked = true, Placement = ++placements, X = position.x, Y = position.y, Z = position.z, Yaw = yaw }; d.Object.transform.position = position; if (!preserveRotation) { d.Object.transform.rotation = Quaternion.Euler(0f, yaw, 0f); } foreach (Rigidbody componentsInChild in d.Object.GetComponentsInChildren()) { componentsInChild.velocity = Vector3.zero; componentsInChild.angularVelocity = Vector3.zero; } Parked?.Invoke(d.Placement, d.Object); } public string Status(ulong player) { if (!devices.TryGetValue(player, out var value)) { return "En attente du téléphone."; } if (value.Error != "") { return value.Error; } if (value.Pending) { return "Téléphone en préparation…"; } object result; if (!Id(player).HasValue) { result = "Recréer le téléphone depuis BoneMenu."; } else { PhoneDeviceInfo placement = value.Placement; result = ((placement != null && placement.Parked) ? "Téléphone en attente de prise en main." : "Téléphone disponible."); } return (string)result; } public void Remove(ulong player) { if (devices.TryGetValue(player, out var value)) { value.Token++; Despawn(value.Entity); devices.Remove(player); } } public void Reset() { generation++; cachedSpawnable = null; cachedBarcode = ""; foreach (Device value in devices.Values) { value.Token++; Despawn(value.Entity); } devices.Clear(); } public void Ensure(PlayerID player, PhoneConfiguration config, bool replace = false, Func held = null) { //IL_03e3: Unknown result type (might be due to invalid IL or missing references) //IL_03ea: Unknown result type (might be due to invalid IL or missing references) //IL_03f4: Unknown result type (might be due to invalid IL or missing references) //IL_03f9: Unknown result type (might be due to invalid IL or missing references) //IL_03fe: Unknown result type (might be due to invalid IL or missing references) //IL_0408: Unknown result type (might be due to invalid IL or missing references) //IL_040d: Unknown result type (might be due to invalid IL or missing references) //IL_0412: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_042b: Unknown result type (might be due to invalid IL or missing references) //IL_0441: Unknown result type (might be due to invalid IL or missing references) //IL_0446: Unknown result type (might be due to invalid IL or missing references) //IL_045d: Unknown result type (might be due to invalid IL or missing references) //IL_0462: Unknown result type (might be due to invalid IL or missing references) //IL_0472: Unknown result type (might be due to invalid IL or missing references) //IL_048a: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_0310: Expected O, but got Unknown //IL_035c: Unknown result type (might be due to invalid IL or missing references) //IL_0361: Unknown result type (might be due to invalid IL or missing references) //IL_036f: Expected O, but got Unknown //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) if (!NetworkInfo.IsHost || player == null || !player.IsValid || !NetworkSceneManager.InCurrentLevel(player)) { return; } if (replace && devices.TryGetValue(player.PlatformID, out var value)) { if (value.Pending) { throw new InvalidOperationException("Téléphone déjà en préparation."); } if (Id(player.PlatformID).HasValue && value.Barcode == config.Barcode) { if (held != null && held(player.PlatformID, Id(player.PlatformID).Value)) { throw new InvalidOperationException("Relâcher le téléphone avant de le rappeler."); } if (Time.realtimeSinceStartup - value.Recalled < 2f) { throw new InvalidOperationException("Attendre avant de rappeler le téléphone."); } NetworkPlayer val = default(NetworkPlayer); if (NetworkPlayerManager.TryGetPlayer(player.SmallID, ref val) && val.HasRig && !((Object)(object)val.RigRefs.Head == (Object)null)) { NetworkEntityManager.TakeOwnership(value.Entity); Transform head = val.RigRefs.Head; Park(value, head.position + head.forward * 0.45f + Vector3.down * 0.35f, head.eulerAngles.y, player.PlatformID); value.Recalled = Time.realtimeSinceStartup; value.Error = ""; } return; } Remove(player.PlatformID); } if (devices.TryGetValue(player.PlatformID, out var value2)) { if (value2.Pending && Time.realtimeSinceStartup - value2.Started > 30f) { value2.Token++; value2.Pending = false; value2.Error = "Téléphone indisponible : vérifier le pallet puis recréer."; } } else { NetworkPlayer val2 = default(NetworkPlayer); if (((MetadataVariableT)(object)player.Metadata.Loading).GetValue() || !NetworkPlayerManager.TryGetPlayer(player.SmallID, ref val2) || !val2.HasRig || (Object)(object)val2.RigRefs.Head == (Object)null) { return; } AssetWarehouse instance = AssetWarehouse.Instance; if (instance == null) { return; } if (cachedSpawnable == null || cachedBarcode != config.Barcode) { SpawnableCrateReference val3 = new SpawnableCrateReference(config.Barcode); SpawnableCrate val4 = default(SpawnableCrate); if (!((CrateReferenceT)(object)val3).TryGetCrate(ref val4)) { devices[player.PlatformID] = new Device { Error = "Pallet Phone absent : l'installer puis recréer le téléphone." }; return; } cachedBarcode = config.Barcode; cachedSpawnable = new Spawnable { crateRef = val3 }; } Device d = new Device { Pending = true, Started = Time.realtimeSinceStartup, Barcode = config.Barcode }; devices[player.PlatformID] = d; int epoch = generation; int token = d.Token; Transform head2 = val2.RigRefs.Head; Vector3 spawnPosition = head2.position + head2.forward * 0.45f + Vector3.down * 0.35f; float spawnYaw = head2.eulerAngles.y; NetworkAssetSpawner.Spawn(new SpawnRequestInfo { Spawnable = cachedSpawnable, Position = spawnPosition, Rotation = Quaternion.Euler(0f, spawnYaw, 0f), SpawnEffect = false, SpawnSource = (EntitySource)1, SpawnCallback = delegate(SpawnCallbackInfo info) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) if (generation != epoch || token != d.Token || !devices.TryGetValue(player.PlatformID, out var value3) || value3 != d) { Despawn(info.Entity); } else { d.Pending = false; d.Entity = info.Entity; d.Object = info.Spawned; if (info.Entity != null && (Object)(object)info.Spawned != (Object)null) { Park(d, spawnPosition, spawnYaw, player.PlatformID); } if (info.Entity == null || (Object)(object)info.Spawned == (Object)null) { d.Error = "Échec du spawn : recréer le téléphone."; } } } }); } } public static GameObject Resolve(ushort id) { NetworkEntity entity = NetworkEntityManager.IDManager.RegisteredEntities.GetEntity(id); object result; if (entity == null) { result = null; } else { IMarrowEntityExtender extender = entity.GetExtender(); if (extender == null) { result = null; } else { MarrowEntity marrowEntity = extender.MarrowEntity; result = ((marrowEntity != null) ? ((Component)marrowEntity).gameObject : null); } } return (GameObject)result; } private static void Despawn(NetworkEntity entity) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (entity != null && entity.IsRegistered && NetworkInfo.HasServer && NetworkInfo.IsHost) { NetworkAssetSpawner.Despawn(new DespawnRequestInfo { EntityID = entity.ID, DespawnEffect = false }); } } } public sealed class PhoneMod : MelonMod { private readonly PhoneAccess access = new PhoneAccess(); private readonly PhoneAccess bankAttempts = new PhoneAccess(); private readonly Dictionary<(ulong Actor, ulong Owner, ushort Device), bool> heldChecks = new Dictionary<(ulong, ulong, ushort), bool>(); private readonly PhoneLive lives = new PhoneLive(); private readonly ConcurrentQueue accessViews = new ConcurrentQueue(); private readonly ConcurrentQueue liveViews = new ConcurrentQueue(); private readonly ConcurrentQueue<(ulong Actor, string Id, uint Seq, byte[] Audio, bool Host)> liveAudio = new ConcurrentQueue<(ulong, string, uint, byte[], bool)>(); private readonly Dictionary inspected = new Dictionary(); private readonly Dictionary callIdentities = new Dictionary(); private readonly Dictionary inspectedEpoch = new Dictionary(); private int accessEpoch; private long lastAccessRevision; private readonly Dictionary liveSequences = new Dictionary(); private float nextLivePulse; private float accessAt; private float liveAt; private float nextAccessPulse; private float nextSelection; private long accessRevision; private long liveRevision; private string catalogKey = ""; private readonly ConcurrentQueue<(ulong Player, PhoneCommand Command)> commands = new ConcurrentQueue<(ulong, PhoneCommand)>(); private readonly ConcurrentQueue snapshots = new ConcurrentQueue(); private readonly PhoneDevices devices = new PhoneDevices(); private readonly Dictionary lastCommands = new Dictionary(); private readonly HashSet online = new HashSet(); private readonly Dictionary deviceStatuses = new Dictionary(); private readonly HashSet retiredSessions = new HashSet(); private readonly PhoneCalls calls = new PhoneCalls(); private readonly ConcurrentQueue callUpdates = new ConcurrentQueue(); private readonly ConcurrentQueue<(ulong Actor, string Call, uint Sequence, byte[] Audio, bool Host)> voiceFrames = new ConcurrentQueue<(ulong, string, uint, byte[], bool)>(); private readonly Dictionary voiceReady = new Dictionary(); private readonly Dictionary> callNonces = new Dictionary>(); private readonly Dictionary audioSequences = new Dictionary(); private readonly HashSet retiredCalls = new HashSet(); private PhoneVoice voice; private PhoneWindy windy; private readonly ConcurrentQueue windyViews = new ConcurrentQueue(); private readonly Dictionary windyOffsets = new Dictionary(); private readonly Dictionary lastWindyPosts = new Dictionary(); private readonly HashSet publicPhotos = new HashSet(); private long windyRevision; public readonly List SharedNumbers = new List(); private readonly ConcurrentQueue notices = new ConcurrentQueue(); private readonly ConcurrentQueue speakers = new ConcurrentQueue(); private readonly HashSet seenNotices = new HashSet(); private readonly Dictionary listenReady = new Dictionary(); private readonly Dictionary lastShares = new Dictionary(); private long callRevision; private float nextHeartbeat; private float lastCallSync; private PhoneService service; private PhoneConfiguration configuration = new PhoneConfiguration(); private PhoneApp app; private PhoneSurface surface; private ushort? bound; private string screenKey = ""; private string session = ""; private string scene = ""; private string directory; private bool connected; private bool wasHost; private bool loading; private float nextTick; private float nextSync; private float nextWarning; private long revision; private FunctionElement menuNumber; private FunctionElement menuStatus; private Page menuRoot; private Page menuAdmin; private bool adminVisible; private readonly PhoneParking parking = new PhoneParking(); public PhoneAccessView Access { get; private set; } = new PhoneAccessView(); public WindyLiveView Live { get; private set; } = new WindyLiveView(); public PhoneView ScreenProfile => ScreenUnlocked ? Access.Profile : null; public bool ScreenUnlocked => Access.Token != "" && Access.Profile != null && Access.Device == bound && surface != null && (surface.IsHeld || IsCasting) && Time.realtimeSinceStartup - accessAt < 8f; public ulong ScreenOwner { get; private set; } public string VisibleWallpaper => (Access.Owner == ScreenOwner && Access.Device == bound) ? (Access.Wallpaper ?? "") : ""; public ushort? ScreenDevice => bound; public bool ScreenPinSet { get { PhoneDeviceInfo? phoneDeviceInfo = View.Devices.FirstOrDefault((PhoneDeviceInfo d) => d.Device == bound); return (phoneDeviceInfo != null && phoneDeviceInfo.PinSet) || (Access.Device == bound && Access.PinSet); } } public bool IsCasting => !loading && View.Scene == scene && Live.Casting && Live.Active != null && Live.Active.Host == PlayerIDManager.LocalPlatformID && surface != null && (Object)(object)surface.Body != (Object)null && bound == Live.Active.Device && View.Devices.Any((PhoneDeviceInfo d) => d.Owner == Live.Active.Owner && d.Device == Live.Active.Device) && Time.realtimeSinceStartup - liveAt < 10f; public bool IsWatching => ScreenUnlocked && !Live.Casting && Live.Active != null && Time.realtimeSinceStartup - liveAt < 10f; public List Gallery => ScreenUnlocked ? Access.Gallery.Concat(from id in Media.Photos.Captured(ScreenOwner) where !Access.Deleted.Contains(id) select id).Distinct().ToList() : new List(); public static PhoneMod Instance { get; private set; } public PhoneView View { get; private set; } = new PhoneView(); public string Status { get; private set; } = "Rejoindre un lobby Fusion."; public WindyView Windy { get; private set; } = new WindyView(); public PhoneMedia Media { get; private set; } public string ServerSession => session; public PhoneSounds Sounds { get; private set; } public bool CallMicMuted { get; private set; } public bool CallActive => View.Call != null && View.Call.State == "Active" && Time.realtimeSinceStartup - lastCallSync < 12f; public PhoneVoiceNotes Notes { get; private set; } public PhoneAmbientCapture Ambient { get; } = new PhoneAmbientCapture(); private bool LiveSourceAvailable(ulong actor, ulong owner, ushort device) { return !loading && online.Contains(actor) && devices.Id(owner) == device; } private bool CastingValid(ulong actor, string id) { return lives.SourceValid(actor, id, LiveSourceAvailable); } public void EnqueueAccess(PhoneAccessView v) { if (accessViews.Count < 32) { accessViews.Enqueue(v); } } public void EnqueueLive(WindyLiveView v) { if (liveViews.Count < 32) { liveViews.Enqueue(v); } } public void EnqueueLiveVoice(ulong a, string id, uint seq, byte[] bytes, bool host) { if (liveAudio.Count < 64) { liveAudio.Enqueue((a, id, seq, bytes, host)); } } private static bool HandHolds(Hand hand, GameObject body) { if ((Object)(object)body == (Object)null) { return false; } GameObject val = ((hand != null) ? hand.m_CurrentAttachedGO : null); return (Object)(object)val != (Object)null && ((Object)(object)val == (Object)(object)body || val.transform.IsChildOf(body.transform) || body.transform.IsChildOf(val.transform)); } private bool ServerHolds(ulong actor, ulong owner, ushort device) { (ulong, ulong, ushort) key = (actor, owner, device); if (heldChecks.TryGetValue(key, out var value)) { return value; } value = CheckServerHands(actor, owner, device); heldChecks[key] = value; return value; } private bool CheckServerHands(ulong actor, ulong owner, ushort device) { if (loading || devices.Id(owner) != device) { return false; } PlayerID playerID = PlayerIDManager.GetPlayerID(actor); NetworkPlayer val = default(NetworkPlayer); if (playerID == null || !playerID.IsValid || !NetworkPlayerManager.TryGetPlayer(playerID.SmallID, ref val) || !val.HasRig) { return false; } GameObject body = PhoneDevices.Resolve(device); return HandHolds(val.RigRefs.LeftHand, body) || HandHolds(val.RigRefs.RightHand, body); } private bool LeaseHeld(ulong actor, ulong owner, ushort device) { return ServerHolds(actor, owner, device) || lives.SourceValid(actor, lives.Casting(actor), (ulong a, ulong o, ushort d) => o == owner && d == device && LiveSourceAvailable(a, o, d)); } private bool LeaseValid(ulong actor) { PhoneAccess.Lease lease = access.Get(actor); return lease != null && access.Valid(actor, lease.Owner, lease.Device, lease.Token, LeaseHeld(actor, lease.Owner, lease.Device), Time.realtimeSinceStartup); } public ulong UploadOwner(ulong actor, PhoneMediaChunk chunk) { if (!chunk.Device.HasValue || !access.Valid(actor, chunk.Owner, chunk.Device.Value, chunk.Access, ServerHolds(actor, chunk.Owner, chunk.Device.Value), Time.realtimeSinceStartup)) { throw new InvalidOperationException("Déverrouiller le téléphone avant d'envoyer une photo."); } return chunk.Owner; } private ulong AuthorizedOwner(ulong actor, PhoneCommand c) { if (!c.Device.HasValue || !access.Valid(actor, c.Owner, c.Device.Value, c.Access, LeaseHeld(actor, c.Owner, c.Device.Value), Time.realtimeSinceStartup)) { throw new InvalidOperationException("Téléphone verrouillé. Saisir son code."); } return c.Owner; } private bool HandleAccess(ulong actor, PhoneCommand c) { if (c.Operation != "phoneInspect" && c.Operation != "unlock" && c.Operation != "pin" && c.Operation != "lock" && c.Operation != "phonePulse") { return false; } if (!c.Device.HasValue || devices.Id(c.Owner) != c.Device) { throw new InvalidOperationException("Téléphone introuvable."); } if (c.Operation == "lock") { access.Lock(actor); lives.Disconnect(actor); CloseBorrowedCall(actor); PublishAccess(actor); PublishLives(); return true; } if (c.Operation == "phonePulse" && !ServerHolds(actor, c.Owner, c.Device.Value) && LeaseHeld(actor, c.Owner, c.Device.Value)) { if (!access.Renew(actor, c.Owner, c.Device.Value, c.Access, held: true, Time.realtimeSinceStartup)) { throw new InvalidOperationException("Téléphone verrouillé."); } lives.Pulse(actor, Time.realtimeSinceStartup); PublishAccess(actor, "", light: true); PublishLive(actor); return true; } if (!ServerHolds(actor, c.Owner, c.Device.Value)) { throw new InvalidOperationException("Tenir le téléphone, puis réessayer."); } inspected[actor] = new PhoneDeviceInfo { Owner = c.Owner, Device = c.Device.Value }; inspectedEpoch[actor] = c.Epoch; if (c.Operation == "pin") { if (actor != c.Owner) { throw new InvalidOperationException("Seul le propriétaire peut définir ou modifier le code."); } if (service.PinSet(c.Owner)) { AuthorizedOwner(actor, c); } service.SetPin(c.Owner, c.Text); access.Lock(actor); access.Unlock(actor, c.Owner, c.Device.Value, c.Text, held: true, (string p) => service.VerifyPin(c.Owner, p), Time.realtimeSinceStartup); foreach (ulong item in online) { Publish(item); } } else if (c.Operation == "unlock") { access.Unlock(actor, c.Owner, c.Device.Value, c.Text, held: true, (string p) => service.VerifyPin(c.Owner, p), Time.realtimeSinceStartup); } else if (c.Operation == "phonePulse") { if (access.Renew(actor, c.Owner, c.Device.Value, c.Access, held: true, Time.realtimeSinceStartup)) { lives.Pulse(actor, Time.realtimeSinceStartup); } else { lives.Disconnect(actor); } PublishAccess(actor, "", light: true); PublishLive(actor); return true; } PublishAccess(actor); return true; } private bool HandleBankAccess(ulong actor, ulong owner, PhoneCommand c) { if (c.Operation != "bankUnlock" && c.Operation != "bankPin" && c.Operation != "bankLock") { return false; } PhoneAccess.Lease lease = access.Get(actor); if (c.Operation == "bankLock") { lease.BankAuthorized = false; PublishAccess(actor); return true; } if (c.Operation == "bankPin") { if (actor != owner) { throw new InvalidOperationException("Seul le propriétaire peut créer le code bancaire."); } if (service.BankPinSet(owner)) { throw new InvalidOperationException("Un code bancaire existe déjà."); } service.SetBankPin(owner, c.Text); } bankAttempts.Unlock(actor, owner, c.Device.Value, c.Text, held: true, (string p) => service.VerifyBankPin(owner, p), Time.realtimeSinceStartup); bankAttempts.Lock(actor); lease.BankAuthorized = true; PublishAccess(actor, "Application Banque déverrouillée."); return true; } private void PublishAccess(ulong actor, string notice = "", bool light = false) { PlayerID playerID = PlayerIDManager.GetPlayerID(actor); if (playerID == null || !playerID.IsValid || service == null) { return; } PhoneAccess.Lease lease = (LeaseValid(actor) ? access.Get(actor) : null); inspected.TryGetValue(actor, out var value); int value2; PhoneAccessView phoneAccessView = new PhoneAccessView { Epoch = (inspectedEpoch.TryGetValue(actor, out value2) ? value2 : 0), Player = actor, Session = session, Revision = ++accessRevision, Owner = (lease?.Owner ?? value?.Owner ?? actor), Device = (lease?.Device ?? value?.Device ?? 0), Notice = notice }; phoneAccessView.BankPinSet = service.BankPinSet(phoneAccessView.Owner); phoneAccessView.BankUnlocked = lease?.BankAuthorized ?? false; phoneAccessView.PinSet = service.PinSet(phoneAccessView.Owner); phoneAccessView.Wallpaper = service.Wallpaper(phoneAccessView.Owner); if (lease != null) { phoneAccessView.Token = lease.Token; if (!light) { phoneAccessView.Profile = service.PrivateView(lease.Owner); phoneAccessView.Profile.BankReady = ServerEconomy.IsHosting; phoneAccessView.Profile.Balance = ((phoneAccessView.BankUnlocked && phoneAccessView.Profile.BankReady) ? ServerEconomy.GetBalance(lease.Owner) : 0); if (!phoneAccessView.BankUnlocked) { phoneAccessView.Profile.Transactions.Clear(); } phoneAccessView.Windy = windy?.View(lease.Owner, windyOffsets.TryGetValue(actor, out var value3) ? value3 : 0); phoneAccessView.Gallery = service.Gallery(lease.Owner); phoneAccessView.Deleted = service.DeletedPhotos(lease.Owner); if (phoneAccessView.Windy != null) { phoneAccessView.Windy.Player = actor; phoneAccessView.Windy.Session = session; phoneAccessView.Windy.Revision = ++windyRevision; } } } if (playerID.IsMe) { AcceptAccess(phoneAccessView); } else { PhoneLiveTransport.Access(phoneAccessView, playerID.SmallID); } } private void AcceptAccess(PhoneAccessView v) { if (v.Protocol != 16 || v.Player != PlayerIDManager.LocalPlatformID || v.Session != View.Session || v.Device != bound || v.Owner != ScreenOwner || v.Epoch != accessEpoch || v.Revision <= lastAccessRevision || (v.Token != "" && !Guid.TryParseExact(v.Token, "N", out var _))) { return; } if (v.Token != "" && v.Token == Access.Token && v.Profile == null) { v.Profile = Access.Profile; v.Gallery = Access.Gallery; v.Deleted = Access.Deleted; } bool flag = v.BankUnlocked != Access.BankUnlocked || v.BankPinSet != Access.BankPinSet || v.Wallpaper != Access.Wallpaper || v.PinSet != Access.PinSet || !v.Gallery.SequenceEqual(Access.Gallery) || v.Token != Access.Token || (v.Profile != null && JsonSerializer.Serialize(v.Profile) != JsonSerializer.Serialize(Access.Profile)); lastAccessRevision = v.Revision; PhoneView old = Access.Profile; Access = v; accessAt = Time.realtimeSinceStartup; if (v.Token == "" && old != null) { app?.ForgetPrivate(); Media?.ClearPrivate(); } if (v.Windy != null) { AcceptWindy(v.Windy); } if (old != null && v.Profile != null) { foreach (PhoneSms item in v.Profile.Messages.Where((PhoneSms m) => m.To == v.Profile.Number && !old.Messages.Any((PhoneSms o) => o.Id == m.Id))) { LocalPopup("Messages", PhoneService.Display(item.From) + " : " + ((item.Voice != "") ? "Message vocal. " : ((item.Photo != "") ? "Photo reçue. " : "")) + item.Text, null, v.Owner); } } if (v.Notice != "") { app?.LockStatus(v.Notice); Notice(v.Notice); } if (flag || v.Notice != "") { app?.Refresh(); } } private void PublishReaders(ulong owner) { ulong[] array = access.Readers(owner); foreach (ulong actor in array) { PublishAccess(actor); } } private void SelectSurface() { if (loading || View.Scene != scene) { if (surface != null) { Unbind(); } return; } if (Time.realtimeSinceStartup >= nextSelection) { nextSelection = Time.realtimeSinceStartup + 0.2f; PhoneDeviceInfo phoneDeviceInfo = (IsCasting ? View.Devices.FirstOrDefault((PhoneDeviceInfo d) => d.Device == Live.Active.Device) : View.Devices.FirstOrDefault(delegate(PhoneDeviceInfo d) { GameObject body = PhoneDevices.Resolve(d.Device); return HandHolds(Player.LeftHand, body) || HandHolds(Player.RightHand, body); })); if (phoneDeviceInfo == null && surface != null && bound.HasValue) { phoneDeviceInfo = View.Devices.FirstOrDefault((PhoneDeviceInfo d) => d.Device == bound.Value); } string text = JsonSerializer.Serialize(View.Configuration.Screen); if (surface != null && (phoneDeviceInfo == null || bound != phoneDeviceInfo.Device || (Object)(object)surface.Body == (Object)null || screenKey != text)) { Unbind(); } if (surface == null && phoneDeviceInfo != null) { GameObject val = PhoneDevices.Resolve(phoneDeviceInfo.Device); if ((Object)(object)val != (Object)null) { ScreenOwner = phoneDeviceInfo.Owner; bound = phoneDeviceInfo.Device; screenKey = text; Access = new PhoneAccessView(); app.ForgetPrivate(); surface = new PhoneSurface(val, View.Configuration.Screen, Warning); app.Bind(surface); } } } bool flag = surface != null && surface.IsHeld; if (!flag && !IsCasting && Access.Token != "") { Request("lock", "", "", "", "", enabled: false, 0L); ClearAccessLocal(); } if (IsCasting && Time.realtimeSinceStartup >= nextLivePulse) { nextLivePulse = Time.realtimeSinceStartup + 2f; Request("livePulse", "", "", "", Live.Active.Id, enabled: false, 0L); } if ((flag || IsCasting) && Access.Token != "" && Time.realtimeSinceStartup - accessAt >= 8f) { ClearAccessLocal(); } if ((flag || IsCasting) && Time.realtimeSinceStartup >= nextAccessPulse) { nextAccessPulse = Time.realtimeSinceStartup + 2f; Request((Access.Token != "") ? "phonePulse" : "phoneInspect", "", "", "", "", enabled: false, 0L); } } public void LockPhone() { Request("lock", "", "", "", "", enabled: false, 0L); ClearAccessLocal(); } private void ClearAccessLocal() { accessEpoch++; Access = new PhoneAccessView(); app?.ForgetPrivate(); Media?.ClearPrivate(); PhoneShutter.RestoreMenu(); } private void TickExperience(bool host) { if (host) { ulong[] array = access.Expire(Time.realtimeSinceStartup, (ulong actor2, PhoneAccess.Lease l) => LeaseHeld(actor2, l.Owner, l.Device)); ulong[] array2 = array; foreach (ulong actor in array2) { if (lives.Casting(actor) == "") { lives.Disconnect(actor); } CloseBorrowedCall(actor); PublishAccess(actor); } if (array.Length != 0) { PublishLives(); } if (lives.Tick(Time.realtimeSinceStartup, LiveSourceAvailable)) { PublishLives(); } devices.Tick(DeviceHeld); string text = string.Join("/", from d in devices.Catalog(service.PinSet) select d.Owner + ":" + d.Device + ":" + d.PinSet + ":" + d.Placement + ":" + d.Parked); if (text != catalogKey) { catalogKey = text; foreach (ulong item in online) { Publish(item); } } } if (!loading && View.Scene == scene) { PhoneParking phoneParking = parking; IEnumerable catalog; if (!host) { IEnumerable enumerable = View.Devices; catalog = enumerable; } else { IEnumerable enumerable = devices.Catalog(service.PinSet); catalog = enumerable; } phoneParking.Tick(catalog); } else { parking.Clear(); } SelectSurface(); PhoneAccessView result; while (accessViews.TryDequeue(out result)) { AcceptAccess(result); } WindyLiveView result2; while (liveViews.TryDequeue(out result2)) { AcceptLive(result2); } int num2 = 64; (ulong, string, uint, byte[], bool) result3; while (num2-- > 0 && liveAudio.TryDequeue(out result3)) { if (result3.Item5 && host) { ForwardLiveVoice(result3.Item1, result3.Item2, result3.Item3, result3.Item4); } else if (!result3.Item5) { voice.ReceiveLive(this, result3.Item1, result3.Item2, result3.Item3, result3.Item4); } } } private ulong ControllerFor(ulong owner) { ulong[] array = access.Readers(owner); foreach (ulong num in array) { if (LeaseValid(num)) { return num; } } return owner; } private void CloseBorrowedCall(ulong actor) { if (CallIdentity(actor) != actor) { ulong[] array = calls.Disconnect(actor); foreach (ulong id in array) { PublishCall(id, "Téléphone reverrouillé : appel terminé."); } callIdentities.Remove(actor); } } private ulong CallIdentity(ulong actor) { ulong value; return callIdentities.TryGetValue(actor, out value) ? value : actor; } private void HandleLive(ulong actor, ulong owner, PhoneCommand c) { float realtimeSinceStartup = Time.realtimeSinceStartup; string handle = windy.View(owner, 0).Handle; switch (c.Operation) { case "liveStart": { if (calls.View(actor, (ulong _) => "").State != "Idle") { throw new InvalidOperationException("Terminer l'appel avant de passer en live."); } if (!voiceReady.TryGetValue(actor, out var value) || !value) { throw new InvalidOperationException("Activer la voix Fusion pour passer en live."); } lives.Start(actor, owner, c.Device.Value, handle, c.Enabled, realtimeSinceStartup); NotifyWindy("windyLive", actor, owner, handle, "", lives.Casting(actor)); break; } case "liveJoin": if (calls.View(actor, (ulong _) => "").State != "Idle") { throw new InvalidOperationException("Terminer l'appel avant de rejoindre un live."); } lives.Join(actor, c.Call, realtimeSinceStartup); break; case "liveLeave": lives.Leave(actor); break; case "liveStop": lives.Stop(actor, c.Call); break; case "liveCamera": lives.Camera(actor, c.Call, c.Enabled); break; case "liveLike": lives.Like(actor, c.Call); break; case "liveComment": lives.Comment(actor, c.Call, handle, c.Text, realtimeSinceStartup); break; default: throw new InvalidOperationException("Action live inconnue."); } PublishLives(); } private void PublishLive(ulong actor) { PlayerID playerID = PlayerIDManager.GetPlayerID(actor); if (playerID != null && playerID.IsValid) { WindyLiveView windyLiveView = lives.View(actor); windyLiveView.Session = session; windyLiveView.Revision = ++liveRevision; if (playerID.IsMe) { AcceptLive(windyLiveView); } else { PhoneLiveTransport.State(windyLiveView, playerID.SmallID); } } } private void PublishLives() { foreach (ulong item in online.Where((ulong a) => listenReady.ContainsKey(a))) { PublishLive(item); } } private void AcceptLive(WindyLiveView v) { if (v.Protocol == 16 && v.Player == PlayerIDManager.LocalPlatformID && !(v.Session != View.Session) && v.Revision > Live.Revision && v.Available != null && v.Available.Count <= 4 && v.Comments != null && v.Comments.Count <= 12 && !v.Available.Any((WindyLiveSummary s) => s == null || !Guid.TryParseExact(s.Id, "N", out var _) || s.Handle == null || s.Handle.Length > 20) && !v.Comments.Any((WindyLiveComment c) => c == null || c.Text == null || c.Text.Length > 140)) { bool flag = JsonSerializer.Serialize(v.Active) != JsonSerializer.Serialize(Live.Active) || v.Casting != Live.Casting || v.Liked != Live.Liked || JsonSerializer.Serialize(v.Comments) != JsonSerializer.Serialize(Live.Comments) || JsonSerializer.Serialize(v.Available) != JsonSerializer.Serialize(Live.Available); Live = v; liveAt = Time.realtimeSinceStartup; if (flag) { app?.Refresh(); } } } public ulong[] LiveTargets(ulong actor, string id) { return CastingValid(actor, id) ? lives.Targets(actor, id, Time.realtimeSinceStartup) : Array.Empty(); } public bool LiveRouteValid(string id, ulong actor) { return LeaseValid(actor) && lives.Routes(id, actor, Time.realtimeSinceStartup); } public void SendLiveVoice(string id, uint sequence, byte[] bytes) { if (NetworkInfo.IsHost) { EnqueueLiveVoice(PlayerIDManager.LocalPlatformID, id, sequence, bytes, host: true); } else { PhoneLiveTransport.Send(PhoneVoicePacket.Create(id, 0uL, sequence, bytes)); } } private void ForwardLiveVoice(ulong actor, string id, uint seq, byte[] bytes) { if (!PhoneVoice.ValidCompressedFrame(bytes) || !voiceReady.TryGetValue(actor, out var value) || !value) { return; } if (!liveSequences.TryGetValue(actor, out (string, PhoneAudioWindow) value2) || value2.Item1 != id) { value2 = (id, new PhoneAudioWindow()); } if (!value2.Item2.Accept(seq)) { return; } ulong[] array = LiveTargets(actor, id); if (array.Length == 0) { return; } liveSequences[actor] = value2; ulong[] array2 = array; foreach (ulong num in array2) { if (!LeaseValid(num) || !listenReady.TryGetValue(num, out var value3) || !value3) { continue; } PlayerID playerID = PlayerIDManager.GetPlayerID(num); if (playerID != null && playerID.IsValid) { if (playerID.IsMe) { voice.ReceiveLive(this, actor, id, seq, bytes); } else { PhoneLiveTransport.Voice(PhoneVoicePacket.Create(id, actor, seq, bytes), playerID.SmallID); } } } } private void ResetExperience() { heldChecks.Clear(); access.Clear(); bankAttempts.Clear(); lives.Clear(); inspected.Clear(); inspectedEpoch.Clear(); accessEpoch = 0; lastAccessRevision = 0L; callIdentities.Clear(); liveSequences.Clear(); accessViews.Clear(); liveViews.Clear(); liveAudio.Clear(); Access = new PhoneAccessView(); Live = new WindyLiveView(); ScreenOwner = 0uL; accessAt = (liveAt = (nextAccessPulse = (nextSelection = (nextLivePulse = 0f)))); accessRevision = (liveRevision = 0L); catalogKey = ""; } public void EnqueueWindy(WindyView v) { if (windyViews.Count < 32) { windyViews.Enqueue(v); } } public void EnqueueNotice(PhoneNotice n) { if (notices.Count < 32) { notices.Enqueue(n); } } public void EnqueueSpeaker(PhoneSpeakerPacket p) { if (speakers.Count < 64) { speakers.Enqueue(p); } } public override void OnInitializeMelon() { calls.Clock = () => Time.realtimeSinceStartup; calls.Finished += SaveCallHistory; Instance = this; devices.Parked = delegate(PhoneDeviceInfo info, GameObject body) { parking.Hold(info, body); }; app = new PhoneApp(this); directory = Path.Combine(MelonEnvironment.UserDataDirectory, "BonelabRP", "Telephone"); Sounds = new PhoneSounds(Path.Combine(directory, "preferences.json")); voice = new PhoneVoice(Warning); Media = new PhoneMedia(this, directory); Notes = new PhoneVoiceNotes(this, directory); PhoneTransport.Register(); PhoneVoiceTransport.Register(); PhoneNearbyTransport.Register(); PhoneMediaTransport.Register(); PhoneWindyTransport.Register(); PhoneLiveTransport.Register(); ((MelonBase)this).HarmonyInstance.PatchAll(typeof(PhoneMod).Assembly); BuildMenu(); ((MelonBase)this).LoggerInstance.Msg("Téléphone 0.6 : Galerie privée, code PIN, notifications intégrées et lives Windy. Même version requise chez tous les participants."); } public void Enqueue(ulong player, PhoneCommand command) { if (commands.Count < 128) { commands.Enqueue((player, command)); } } public void Enqueue(PhoneView view) { if (snapshots.Count < 16) { snapshots.Enqueue(view); } } public void EnqueueCall(PhoneCallUpdate update) { if (callUpdates.Count < 64) { callUpdates.Enqueue(update); } } public void EnqueueVoice(ulong actor, string call, uint sequence, byte[] audio, bool host) { if (voiceFrames.Count < 64) { voiceFrames.Enqueue((actor, call, sequence, audio, host)); } } private bool DeviceHeld(ulong owner, ushort id) { return PlayerIDManager.PlayerIDs.Any((PlayerID p) => p != null && p.IsValid && ServerHolds(p.PlatformID, owner, id)); } public static bool VoiceHead(PlayerID id, out Vector3 head) { return Head(id, out head); } public bool InterceptVoice(byte[] audio) { return voice?.Intercept(audio, this) ?? true; } public void ToggleCallMicrophone() { CallMicMuted = !CallMicMuted; app.Refresh(); } public void SendVoice(string call, uint sequence, byte[] audio) { if (NetworkInfo.IsHost) { EnqueueVoice(PlayerIDManager.LocalPlatformID, call, sequence, audio, host: true); } else { PhoneVoiceTransport.Send(PhoneVoicePacket.Create(call, 0uL, sequence, audio)); } } public void Request(string operation, string number = "", string name = "", string text = "", string call = "", bool enabled = false, long amount = 0L, string photo = "", int offset = 0, string voice = "") { try { if (!NetworkInfo.HasServer || PlayerIDManager.LocalID == null) { throw new InvalidOperationException("Rejoindre un lobby Fusion."); } if (operation != "sync" && View.Session == "") { throw new InvalidOperationException("Attendre l'attribution du numéro."); } PhoneCommand command = new PhoneCommand { Operation = operation, Session = View.Session, Nonce = Guid.NewGuid().ToString("N"), Number = number, Name = name, Text = text, Call = call, VoiceReady = PhoneVoice.Ready, ListenReady = PhoneVoice.CanListen, Enabled = enabled, Amount = amount, Photo = photo, Voice = voice, Offset = offset, Owner = ScreenOwner, Device = bound, Access = Access.Token, Epoch = accessEpoch }; if (NetworkInfo.IsHost) { Enqueue(PlayerIDManager.LocalPlatformID, command); } else { PhoneTransport.Send(command); } } catch (Exception ex) { Notice(ex.Message); } } public void LocalStatus(string text) { Notice(text); } private void Notice(string text) { Status = text; app.Refresh(); if (menuStatus != null) { ((Element)menuStatus).ElementName = text; } } public override void OnUpdate() { heldChecks.Clear(); try { Notes?.Tick(); if (!NetworkInfo.HasServer || PlayerIDManager.LocalID == null) { if (connected) { Reset(); } return; } bool isHost = NetworkInfo.IsHost; ShowAdmin(isHost); if (!connected || isHost != wasHost) { Reset(); connected = true; wasHost = isHost; if (isHost) { try { service = new PhoneService(Path.Combine(directory, "accounts.json")); windy = new PhoneWindy(Path.Combine(directory, "windy.json")); string path = Path.Combine(directory, "configuration.json"); configuration = (File.Exists(path) ? (JsonSerializer.Deserialize(File.ReadAllText(path)) ?? throw new InvalidOperationException("Configuration vide.")) : new PhoneConfiguration()); configuration.Validate(); session = Guid.NewGuid().ToString("N"); Media.BeginHost(); SaveConfig(); } catch { service = null; throw; } } nextTick = (nextSync = 0f); } if (isHost && service == null) { return; } PhoneView result; while (snapshots.TryDequeue(out result)) { Accept(result); } PhoneCallUpdate result2; while (callUpdates.TryDequeue(out result2)) { AcceptCall(result2); } WindyView result3; while (windyViews.TryDequeue(out result3)) { AcceptWindy(result3); } bool flag = FusionSceneManager.IsLoading(); string text = FusionSceneManager.Barcode ?? ""; if (scene != text || loading != flag) { loading = flag; scene = text; if (isHost) { ulong[] array = calls.StopAll(); foreach (ulong id in array) { PublishCall(id, "Appel terminé : changement de map."); } } StopLocalCall("Appel terminé : changement de map."); access.Clear(); lives.Clear(); PublishLives(); ClearAccessLocal(); Media.Reset(); if (isHost) { Media.BeginHost(); } devices.Reset(); Unbind(); View.Device = null; if (isHost) { ulong[] array2 = online.ToArray(); foreach (ulong id2 in array2) { Publish(id2); } } nextSync = 0f; } if (isHost) { if (Time.realtimeSinceStartup >= nextTick) { nextTick = Time.realtimeSinceStartup + 1f; PlayerID[] array3 = PlayerIDManager.PlayerIDs.Where((PlayerID p) => p != null && p.IsValid).ToArray(); HashSet active = array3.Select((PlayerID p) => p.PlatformID).ToHashSet(); ulong[] array4 = online.Where((ulong item) => !active.Contains(item)).ToArray(); foreach (ulong num2 in array4) { ulong[] array5 = calls.Disconnect(num2); foreach (ulong id3 in array5) { PublishCall(id3, "Correspondant déconnecté."); } access.Lock(num2); lives.Disconnect(num2); devices.Remove(num2); online.Remove(num2); lastCommands.Remove(num2); deviceStatuses.Remove(num2); listenReady.Remove(num2); lastShares.Remove(num2); windyOffsets.Remove(num2); lastWindyPosts.Remove(num2); voiceReady.Remove(num2); callNonces.Remove(num2); audioSequences.Remove(num2); } PlayerID[] array6 = array3; foreach (PlayerID val in array6) { bool flag2 = online.Add(val.PlatformID); service.Ensure(val.PlatformID); deviceStatuses.TryGetValue(val.PlatformID, out var value); if (!loading && configuration.AutoSpawn) { devices.Ensure(val, configuration); } string text2 = devices.Status(val.PlatformID); if (flag2 || value != text2) { deviceStatuses[val.PlatformID] = text2; Publish(val.PlatformID); } } } ulong[] array7 = calls.Tick(Time.realtimeSinceStartup); foreach (ulong id4 in array7) { PublishCall(id4, "Appel terminé : aucune réponse ou liaison interrompue."); } int num6 = 32; (ulong, PhoneCommand) result4; while (num6-- > 0 && commands.TryDequeue(out result4)) { Handle(result4.Item1, result4.Item2); } } if (Time.realtimeSinceStartup >= nextSync) { nextSync = Time.realtimeSinceStartup + 10f; Request("sync", "", "", "", "", enabled: false, 0L); } TickExperience(isHost); if (View.Call.State != "Idle") { if (Time.realtimeSinceStartup - lastCallSync >= 12f) { StopLocalCall("Liaison avec l'hôte interrompue."); } else if (Time.realtimeSinceStartup >= nextHeartbeat) { nextHeartbeat = Time.realtimeSinceStartup + 2f; Request("heartbeat", "", "", "", View.Call.Id, enabled: false, 0L); } } int num7 = 64; (ulong, string, uint, byte[], bool) result5; while (num7-- > 0 && voiceFrames.TryDequeue(out result5)) { if (result5.Item5 && isHost) { ForwardVoice(result5.Item1, result5.Item2, result5.Item3, result5.Item4); } else if (!result5.Item5) { voice.Receive(this, result5.Item1, result5.Item2, result5.Item3, result5.Item4); } } PhoneNotice result6; while (notices.TryDequeue(out result6)) { AcceptNotice(result6); } PhoneSpeakerPacket result7; while (speakers.TryDequeue(out result7)) { voice.ReceiveSpeaker(this, result7); } voice.Tick(this); surface?.Tick(); app.Tick(); Media.Tick(surface); } catch (Exception ex) { if (Time.realtimeSinceStartup >= nextWarning) { nextWarning = Time.realtimeSinceStartup + 5f; Warning(ex.ToString()); Notice(ex.Message); } } } private void Handle(ulong id, PhoneCommand command) { PlayerID playerID = PlayerIDManager.GetPlayerID(id); if (playerID == null || !playerID.IsValid || command.Protocol != 16) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; voiceReady[id] = command.VoiceReady; listenReady[id] = command.ListenReady; if (command.Operation == "livePulse") { if (command.Session == session && CastingValid(id, command.Call)) { lives.Pulse(id, realtimeSinceStartup); PublishLive(id); } else { PublishLive(id); } return; } if (command.Operation == "phonePulse") { try { if (command.Session == session) { HandleAccess(id, command); } return; } catch (Exception ex) { access.Lock(id); lives.Disconnect(id); PublishAccess(id, ex.Message); return; } } if (command.Operation == "heartbeat") { try { calls.Heartbeat(id, command.Call, realtimeSinceStartup); if (!command.VoiceReady) { ulong[] array = calls.Disconnect(id); foreach (ulong id2 in array) { PublishCall(id2, "Voix Fusion désactivée : appel terminé."); } } else { PublishCall(id); } return; } catch (Exception) { PublishCall(id); return; } } bool flag = command.Operation == "bankLock" || command.Operation == "sync" || command.Operation == "phoneInspect" || command.Operation == "photoFetch" || command.Operation == "voiceFetch" || command.Operation == "wallpaperFetch" || command.Operation == "windySync" || command.Operation == "galleryAdd" || command.Operation == "galleryDelete"; if (!flag && lastCommands.TryGetValue(id, out var value) && realtimeSinceStartup - value < 0.25f) { Publish(id, "Action trop rapide, réessayer."); return; } if (!flag) { lastCommands[id] = realtimeSinceStartup; } try { if (command.Operation == "sync") { Publish(id); PublishWindy(id); PublishLive(id); if (LeaseValid(id)) { PublishAccess(id); } } else if (command.Session != session) { Publish(id, "Session actualisée, réessayer."); } else { if (HandleAccess(id, command)) { return; } if (command.Operation == "spawn") { if (loading) { throw new InvalidOperationException("Attendre le chargement."); } devices.Ensure(playerID, configuration, replace: true, DeviceHeld); access.Lock(id); Publish(id); return; } if (command.Operation == "wallpaperFetch") { if (!command.Device.HasValue || !ServerHolds(id, command.Owner, command.Device.Value) || !service.IsWallpaper(command.Owner, command.Photo)) { throw new InvalidOperationException("Fond d’écran indisponible."); } Media.Fetch(id, command.Photo); return; } ulong num = AuthorizedOwner(id, command); if (HandleBankAccess(id, num, command)) { return; } if (command.Operation == "wallpaper") { if (command.Photo != "" && !Media.Owns(num, command.Photo)) { throw new InvalidOperationException("Photo indisponible."); } service.SetWallpaper(num, command.Photo); PublishReaders(num); return; } if (command.Operation == "voiceFetch") { Notes.Fetch(id, command.Voice); return; } if (command.Operation.StartsWith("live", StringComparison.Ordinal)) { HandleLive(id, num, command); return; } if (command.Operation == "galleryAdd") { if (service.PhotoDeleted(num, command.Photo)) { SendNotice(playerID, new PhoneNotice { Kind = "galleryRemoved", Number = command.Photo }); return; } if (!Media.Owns(num, command.Photo)) { throw new InvalidOperationException("Photo indisponible."); } service.AddPhoto(num, command.Photo); PublishReaders(num); return; } if (command.Operation == "galleryDelete") { service.DeletePhoto(num, command.Photo); ulong[] array2 = access.Readers(num); foreach (ulong num2 in array2) { PlayerID playerID2 = PlayerIDManager.GetPlayerID(num2); if (playerID2 != null && playerID2.IsValid) { SendNotice(playerID2, new PhoneNotice { Kind = "galleryRemoved", Number = command.Photo }); } } PublishReaders(num); return; } if (command.Operation == "call" || command.Operation == "answer" || command.Operation == "reject" || command.Operation == "hangup") { HandleCall(id, command); return; } if (command.Operation == "windySync" || command.Operation == "windyJoin" || command.Operation == "windyPost") { HandleWindy(id, command, num); return; } if (command.Operation == "photoFetch") { Media.Fetch(id, command.Photo); return; } if (command.Operation == "video") { ulong[] array3 = calls.SetVideo(id, command.Call, command.Enabled); foreach (ulong id3 in array3) { PublishCall(id3, command.Enabled ? "Caméra de visio activée." : "Caméra de visio coupée."); } return; } if (command.Operation == "share") { ulong result = 0uL; if (command.Text != "" && !ulong.TryParse(command.Text, out result)) { throw new InvalidOperationException("Destinataire AirDrop invalide."); } ShareNumber(playerID, num, result); return; } if (command.Operation == "transfer") { PhoneAccess.Lease lease = access.Get(id); if (lease == null || !lease.BankAuthorized) { throw new InvalidOperationException("Déverrouiller l’application Banque avant un virement."); } Transfer(playerID, command, num); return; } if (command.Operation == "speaker") { if (command.Enabled && !devices.Id(num).HasValue) { throw new InvalidOperationException("Téléphone absent."); } ulong[] array4 = calls.SetSpeaker(id, command.Call, command.Enabled); foreach (ulong id4 in array4) { PublishCall(id4, command.Enabled ? "Haut-parleur activé : conversation audible à 2 m." : "Haut-parleur désactivé."); } return; } if (command.Operation == "sms" && !string.IsNullOrEmpty(command.Photo) && !Media.Owns(num, command.Photo)) { throw new InvalidOperationException("Photo non disponible pour cet expéditeur."); } if (command.Operation == "contactPhoto" && !string.IsNullOrEmpty(command.Photo) && !Media.Owns(num, command.Photo)) { throw new InvalidOperationException("Photo de contact indisponible pour ce téléphone."); } if (command.Operation == "sms" && !string.IsNullOrEmpty(command.Voice)) { command.VoiceSeconds = Notes.Duration(num, command.Voice); } PhoneChange phoneChange = service.Apply(num, command, DateTime.UtcNow); Publish(id, phoneChange.Notice); PublishReaders(num); if (phoneChange.Recipient.HasValue && phoneChange.Recipient.Value != num) { Publish(phoneChange.Recipient.Value, "Nouveau SMS."); PublishReaders(phoneChange.Recipient.Value); } } } catch (Exception ex3) { Publish(id, ex3.Message); PublishAccess(id, ex3.Message); } } private void HandleCall(ulong id, PhoneCommand command) { if (!Guid.TryParseExact(command.Nonce, "N", out var _)) { throw new InvalidOperationException("Commande appel invalide."); } if (!callNonces.TryGetValue(id, out var value)) { value = (callNonces[id] = new Queue()); } if (value.Contains(command.Nonce)) { PublishCall(id); return; } double now = Time.realtimeSinceStartup; ulong[] array; string notice; if (command.Operation == "call") { if (loading || !NetworkSceneManager.IsLevelNetworked) { throw new InvalidOperationException("Attendre une map chargée dans Fusion."); } if (!command.VoiceReady) { throw new InvalidOperationException("Activer la voix Fusion et vérifier le microphone."); } bool masked; string input = PhoneDial.Parse(command.Number, out masked); ulong? num = service.FindPlayer(input); if (!num.HasValue) { throw new InvalidOperationException("Ce numéro n'est pas attribué."); } PlayerID playerID = PlayerIDManager.GetPlayerID(ControllerFor(num.Value)); if (playerID == null || !playerID.IsValid || !online.Contains(num.Value)) { throw new InvalidOperationException("Ce correspondant est hors ligne."); } if (!voiceReady.TryGetValue(playerID.PlatformID, out var value2) || !value2) { throw new InvalidOperationException("Correspondant indisponible : vérifier sa version du mod et sa voix Fusion."); } if (!NetworkSceneManager.InCurrentLevel(playerID) || ((MetadataVariableT)(object)playerID.Metadata.Loading).GetValue()) { throw new InvalidOperationException("Le correspondant charge une map."); } if (lives.Casting(id) != "" || lives.Watching(id) != "" || lives.Casting(playerID.PlatformID) != "" || lives.Watching(playerID.PlatformID) != "") { throw new InvalidOperationException("Quitter le live avant l’appel."); } if (calls.Busy(id) || calls.Busy(playerID.PlatformID)) { throw new InvalidOperationException("Ce correspondant est occupé."); } callIdentities[id] = AuthorizedOwner(id, command); callIdentities[playerID.PlatformID] = num.Value; array = calls.Start(id, playerID.PlatformID, now, command.Enabled, masked); notice = "Appel en cours…"; } else if (command.Operation == "answer") { if (!command.VoiceReady || loading) { throw new InvalidOperationException("Activer la voix Fusion avant d'accepter."); } array = calls.Answer(id, command.Call, now, command.Enabled); notice = "Appel connecté."; } else if (command.Operation == "reject") { array = calls.Reject(id, command.Call); notice = "Appel refusé."; } else { array = calls.Hangup(id, command.Call); notice = "Appel terminé."; } value.Enqueue(command.Nonce); while (value.Count > 128) { value.Dequeue(); } ulong[] array2 = array; foreach (ulong id2 in array2) { PublishCall(id2, notice); } } private void SaveCallHistory(PhoneCallResult result) { if (service == null) { return; } try { ulong num = CallIdentity(result.Caller); ulong num2 = CallIdentity(result.Callee); service.RecordCall(num, num2, result); PublishReaders(num); PublishReaders(num2); } catch (Exception ex) { Warning("Historique appel : " + ex.Message); } } private void PublishCall(ulong id, string notice = "") { PlayerID playerID = PlayerIDManager.GetPlayerID(id); if (playerID != null && playerID.IsValid && service != null) { PhoneCallUpdate update = new PhoneCallUpdate { Session = session, Player = id, Revision = ++callRevision, Call = calls.View(id, (ulong peer) => service.Ensure(CallIdentity(peer)).Number), Notice = notice }; if (playerID.IsMe) { AcceptCall(update); } else { PhoneVoiceTransport.State(update, playerID.SmallID); } } } private void AcceptCall(PhoneCallUpdate update) { if (update.Protocol != 16 || update.Player != PlayerIDManager.LocalPlatformID || update.Session != View.Session || update.Revision <= View.CallRevision || update.Call == null) { return; } PhoneCallView call = update.Call; if ((call.State != "Idle" && call.State != "Ringing" && call.State != "Active") || (call.State != "Idle" && (!Guid.TryParseExact(call.Id, "N", out var _) || retiredCalls.Contains(call.Id)))) { return; } if (call.Id != View.Call.Id) { if (View.Call.Id != "") { retiredCalls.Add(View.Call.Id); } CallMicMuted = false; } bool flag = View.Call.Id != call.Id || View.Call.State != call.State || View.Call.Peer != call.Peer || View.Call.Masked != call.Masked || View.Call.Number != call.Number || View.Call.Incoming != call.Incoming || View.Call.Speaker != call.Speaker || View.Call.PeerSpeaker != call.PeerSpeaker || View.Call.Video != call.Video || View.Call.PeerVideo != call.PeerVideo || View.Call.VideoRequested != call.VideoRequested; View.Call = call; View.CallRevision = update.Revision; lastCallSync = Time.realtimeSinceStartup; if (update.Notice != "") { Notice(update.Notice); } if (flag) { app.Refresh(); } } private void StopLocalCall(string reason) { if (View.Call.Id != "") { retiredCalls.Add(View.Call.Id); View.Call = new PhoneCallView(); Notice(reason); } voice?.Dispose(); CallMicMuted = false; } private void ForwardVoice(ulong actor, string call, uint sequence, byte[] audio) { //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) if (!calls.TryRoute(actor, call, Time.realtimeSinceStartup, out var recipient) || !voiceReady.TryGetValue(actor, out var value) || !value) { return; } if (!audioSequences.TryGetValue(actor, out (string, PhoneAudioWindow) value2) || value2.Item1 != call) { value2 = (call, new PhoneAudioWindow()); } if (!value2.Item2.Accept(sequence) || !PhoneVoice.ValidCompressedFrame(audio)) { return; } audioSequences[actor] = value2; PlayerID playerID = PlayerIDManager.GetPlayerID(recipient); if (playerID == null || !playerID.IsValid) { return; } if (playerID.IsMe) { voice.Receive(this, actor, call, sequence, audio); } else { PhoneVoiceTransport.Reply(PhoneVoicePacket.Create(call, actor, sequence, audio), playerID.SmallID); } (ulong, string)[] array = calls.Speakers(actor, call); for (int i = 0; i < array.Length; i++) { (ulong, string) tuple = array[i]; PhoneAccess.Lease lease = access.Get(tuple.Item1); ushort? num = ((lease != null && LeaseValid(tuple.Item1)) ? new ushort?(lease.Device) : devices.Id(CallIdentity(tuple.Item1))); GameObject val = (num.HasValue ? PhoneDevices.Resolve(num.Value) : null); if ((Object)(object)val == (Object)null) { continue; } foreach (PlayerID playerID2 in PlayerIDManager.PlayerIDs) { if (playerID2 != null && playerID2.IsValid && playerID2.PlatformID != actor && playerID2.PlatformID != recipient && listenReady.TryGetValue(playerID2.PlatformID, out var value3) && value3 && Head(playerID2, out var head) && Within(head, val.transform.position, 2f)) { PhoneSpeakerPacket p = PhoneSpeakerPacket.Create(tuple.Item2, num.Value, PhoneVoicePacket.Create(call, actor, sequence, audio)); if (playerID2.IsMe) { EnqueueSpeaker(p); } else { PhoneNearbyTransport.Speaker(p, playerID2.SmallID); } } } } } private void Publish(ulong id, string notice = "") { PlayerID playerID = PlayerIDManager.GetPlayerID(id); if (playerID != null && playerID.IsValid && service != null) { PhoneView phoneView = service.PrivateView(id); phoneView.BankReady = ServerEconomy.IsHosting; phoneView.Balance = (phoneView.BankReady ? ServerEconomy.GetBalance(id) : 0); phoneView.Call = calls.View(id, (ulong peer) => service.Ensure(CallIdentity(peer)).Number); phoneView.CallRevision = ++callRevision; phoneView.Scene = scene; phoneView.Session = session; phoneView.Revision = ++revision; phoneView.Device = (loading ? ((ushort?)null) : devices.Id(id)); phoneView.Devices = devices.Catalog(service.PinSet).ToList(); phoneView.DeviceStatus = devices.Status(id); phoneView.Configuration = configuration; phoneView.Notice = notice; if (playerID.IsMe) { Accept(phoneView); } else { PhoneTransport.Reply(phoneView, playerID.SmallID); } } } private void Accept(PhoneView view) { if (view.Protocol != 16 || view.Player != PlayerIDManager.LocalPlatformID || string.IsNullOrEmpty(view.Session) || retiredSessions.Contains(view.Session) || (view.Session == View.Session && view.Revision <= View.Revision)) { return; } if (view.Session != View.Session && View.Session != "") { retiredSessions.Add(View.Session); Windy = new WindyView(); publicPhotos.Clear(); Unbind(); StopLocalCall("Session actualisée."); View.CallRevision = 0L; retiredCalls.Clear(); } PhoneSms[] array = ((View.Session == view.Session && View.Number != "") ? view.Messages.Where((PhoneSms m) => m.To == view.Number && !View.Messages.Any((PhoneSms old) => old.Id == m.Id)).ToArray() : Array.Empty()); view.Configuration.Validate(); PhoneCallUpdate update = new PhoneCallUpdate { Session = view.Session, Player = view.Player, Revision = view.CallRevision, Call = view.Call }; PhoneCallView call = View.Call; long num = View.CallRevision; view.Call = call; view.CallRevision = num; View = view; AcceptCall(update); Status = ((view.Notice != "") ? view.Notice : view.DeviceStatus); if (menuNumber != null) { ((Element)menuNumber).ElementName = "Mon numéro : " + PhoneService.Display(view.Number); } if (menuStatus != null) { ((Element)menuStatus).ElementName = Status; } if (!ScreenUnlocked || ScreenOwner != PlayerIDManager.LocalPlatformID) { PhoneSms[] array2 = array; foreach (PhoneSms phoneSms in array2) { LocalPopup("Messages", PhoneService.Display(phoneSms.From) + " : " + ((phoneSms.Photo != "") ? "Photo reçue. " : "") + phoneSms.Text, null, 0uL); } } app.Refresh(); } public bool OwnsPhotoController(BaseController controller) { return (Object)(object)controller != (Object)null && Media != null && Media.PhotoPage && surface != null && surface.PlacementReady && ((surface.HoldsLeft && (Object)(object)controller == (Object)(object)Player.LeftController) || (surface.HoldsRight && (Object)(object)controller == (Object)(object)Player.RightController)); } public void ShutterPhoto() { if (Media != null && Media.PhotoPage) { Media.CapturePhoto(surface); } } private void HandleWindy(ulong id, PhoneCommand c, ulong owner) { if (windy == null) { throw new InvalidOperationException("Windy indisponible."); } if (c.Operation == "windySync") { if (c.Offset < 0 || c.Offset >= 300) { throw new InvalidOperationException("Page Windy invalide."); } windyOffsets[id] = c.Offset; PublishWindy(id); return; } if (c.Operation == "windyJoin") { windy.Register(owner, c.Name, c.Nonce); PublishWindy(id, "Compte Windy prêt."); return; } if (!string.IsNullOrEmpty(c.Photo) && !Media.Owns(owner, c.Photo)) { throw new InvalidOperationException("Photo non disponible pour cet auteur."); } float realtimeSinceStartup = Time.realtimeSinceStartup; if (lastWindyPosts.TryGetValue(id, out var value) && realtimeSinceStartup - value < 3f) { throw new InvalidOperationException("Attendre trois secondes entre deux publications."); } if (windy.Post(owner, c.Text, c.Photo, c.Nonce, DateTime.UtcNow)) { lastWindyPosts[id] = realtimeSinceStartup; WindyPostView windyPostView = windy.View(owner, 0).Posts.First(); NotifyWindy("windyPost", id, owner, windyPostView.Handle, windyPostView.Text, windyPostView.Id, windyPostView.Photo != ""); } windyOffsets[id] = 0; ulong[] array = online.Where((ulong peer) => listenReady.ContainsKey(peer)).ToArray(); foreach (ulong num2 in array) { PublishWindy(num2, (num2 == id) ? "Publication Windy envoyée." : ""); } } private void PublishWindy(ulong id, string notice = "") { if (windy == null) { return; } PlayerID playerID = PlayerIDManager.GetPlayerID(id); if (playerID != null && playerID.IsValid) { windyOffsets.TryGetValue(id, out var value); WindyView windyView = windy.View(LeaseValid(id) ? access.Get(id).Owner : id, value); windyView.Player = id; windyView.Session = session; windyView.Revision = ++windyRevision; windyView.Notice = notice; if (playerID.IsMe) { AcceptWindy(windyView); } else { PhoneWindyTransport.Reply(windyView, playerID.SmallID); } } } private void AcceptWindy(WindyView v) { if (v.Protocol != 16 || v.Player != PlayerIDManager.LocalPlatformID || v.Session != View.Session || (ScreenUnlocked && v.Owner != ScreenOwner) || v.Revision <= Windy.Revision || v.Posts == null || v.Posts.Count > 1 || v.Total < 0 || v.Total > 300) { return; } if (publicPhotos.Count > 1000) { publicPhotos.Clear(); } foreach (WindyPostView post in v.Posts) { if (post == null || !Guid.TryParseExact(post.Id, "N", out var result) || post.Text == null || post.Text.Length > 280 || post.Photo == null || (post.Photo != "" && !Guid.TryParseExact(post.Photo, "N", out result))) { return; } if (post.Photo != "") { publicPhotos.Add(post.Photo); } } Windy = v; if (v.Notice == "Publication Windy envoyée.") { app.WindyPosted(); } if (v.Notice != "") { Notice(v.Notice); } app.Refresh(); } public void VoiceSent() { app.VoiceSent(); } public void RefreshApp() { app?.Refresh(); } public void MediaWarning(string text) { Warning(text); } public void MediaStatus(ulong actor, string text) { Publish(actor, text); } public bool PhotoReferenced(string id) { return (service != null && service.PhotoReferenced(id)) || (windy != null && windy.PhotoReferenced(id)); } public void MediaUploadFailed(ulong actor, string id, string text) { PlayerID playerID = PlayerIDManager.GetPlayerID(actor); if (playerID != null && playerID.IsValid) { SendNotice(playerID, new PhoneNotice { Kind = "uploadFailed", Number = id, Text = text }); } } public bool CanReadPhoto(ulong actor, string id) { PhoneDeviceInfo value; return (service != null && inspected.TryGetValue(actor, out value) && service.IsWallpaper(value.Owner, id) && ServerHolds(actor, value.Owner, value.Device)) || (LeaseValid(actor) && ((service != null && service.CanReadPhoto(access.Get(actor).Owner, id)) || (windy != null && windy.PhotoReferenced(id)))); } public bool CanReadVoice(ulong actor, string id) { return LeaseValid(actor) && service != null && service.CanReadVoice(access.Get(actor).Owner, id); } public bool IsVoiceKnown(string id) { return ScreenUnlocked && ScreenProfile.Messages.Any((PhoneSms m) => m.Voice == id); } public bool IsPhotoKnown(string id) { return (!string.IsNullOrEmpty(id) && id == VisibleWallpaper) || (ScreenUnlocked && (Gallery.Contains(id) || ScreenProfile.Contacts.Any((PhoneContact c) => c.Photo == id) || ScreenProfile.Messages.Any((PhoneSms m) => m.Photo == id) || publicPhotos.Contains(id))); } public bool MediaVideoRoute(ulong actor, string token, out ulong peer) { peer = 0uL; return calls.VideoEnabled(actor, token) && calls.TryRoute(actor, token, Time.realtimeSinceStartup, out peer); } public bool MediaRouteValid(string token, ulong target) { ulong recipient; return calls.TryRoute(target, token, Time.realtimeSinceStartup, out recipient) && calls.VideoEnabled(recipient, token); } public void MediaUploadReady(PlayerID player, string id) { SendNotice(player, new PhoneNotice { Kind = "uploadReady", Number = id }); } public static bool Within(Vector3 a, Vector3 b, float radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) return PhoneProximity.Within(a.x, a.y, a.z, b.x, b.y, b.z, radius); } private static bool Head(PlayerID id, out Vector3 head) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) head = default(Vector3); if (!NetworkSceneManager.InCurrentLevel(id) || ((MetadataVariableT)(object)id.Metadata.Loading).GetValue()) { return false; } NetworkPlayer val = default(NetworkPlayer); if (!NetworkPlayerManager.TryGetPlayer(id.SmallID, ref val) || !val.HasRig || (Object)(object)val.RigRefs.Head == (Object)null) { return false; } head = val.RigRefs.Head.position; return true; } public (ulong Id, string Name, float Distance)[] NearbyPeople() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Player.Head == (Object)null) { return Array.Empty<(ulong, string, float)>(); } Vector3 pos = Player.Head.position; Vector3 head; return (from p in PlayerIDManager.PlayerIDs.Where((PlayerID p) => p != null && p.IsValid && !p.IsMe && View.Devices.Any((PhoneDeviceInfo d) => d.Owner == p.PlatformID) && Head(p, out head) && Within(pos, head, 3f)).Select(delegate(PlayerID p) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) Head(p, out head); ulong platformID = p.PlatformID; string item = ServerEconomy.ResolveName(p) ?? "Joueur"; Vector3 val = head - pos; return (PlatformID: platformID, item, magnitude: ((Vector3)(ref val)).magnitude); }) orderby p.magnitude select p).ToArray(); } private void ShareNumber(PlayerID actor, ulong owner, ulong selected = 0uL) { //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) float realtimeSinceStartup = Time.realtimeSinceStartup; if (lastShares.TryGetValue(actor.PlatformID, out var value) && realtimeSinceStartup - value < 5f) { throw new InvalidOperationException("Attendre 5 secondes entre deux partages."); } if (!Head(actor, out var head)) { throw new InvalidOperationException("Attendre une map chargée."); } lastShares[actor.PlatformID] = realtimeSinceStartup; int num = 0; foreach (PlayerID playerID in PlayerIDManager.PlayerIDs) { if (playerID != null && playerID.IsValid && (selected == 0L || playerID.PlatformID == selected) && playerID.PlatformID != actor.PlatformID && listenReady.ContainsKey(playerID.PlatformID) && Head(playerID, out var head2) && Within(head, head2, 3f)) { string text = ServerEconomy.ResolveName(PlayerIDManager.GetPlayerID(owner) ?? actor) ?? "Joueur"; if (text.Length > 32) { text = text.Substring(0, 32); } SendNotice(playerID, new PhoneNotice { Owner = (LeaseValid(playerID.PlatformID) ? access.Get(playerID.PlatformID).Owner : playerID.PlatformID), Kind = "share", Name = text, Number = service.Ensure(owner).Number, Text = text + " partage son numéro." }); num++; } } Publish(actor.PlatformID, (num == 0) ? "Aucun joueur compatible à moins de 3 m." : ("Numéro partagé avec " + num + " joueur(s).")); } private void SendNotice(PlayerID peer, PhoneNotice n) { n.Player = peer.PlatformID; if (n.Owner == 0) { n.Owner = peer.PlatformID; } n.Session = session; if (peer.IsMe) { EnqueueNotice(n); } else { PhoneNearbyTransport.Notice(n, peer.SmallID); } } private void NotifyWindy(string kind, ulong author, ulong authorOwner, string handle, string text, string content, bool photo = false) { ulong[] array = online.Where((ulong id) => id != author && listenReady.ContainsKey(id)).ToArray(); foreach (ulong num2 in array) { ulong num3 = (LeaseValid(num2) ? access.Get(num2).Owner : num2); if (num3 != authorOwner) { PlayerID playerID = PlayerIDManager.GetPlayerID(num2); if (playerID != null && playerID.IsValid) { SendNotice(playerID, new PhoneNotice { Kind = kind, Owner = num3, Name = handle, Text = text, Number = content }); } } } } private void AcceptNotice(PhoneNotice n) { if (n.Protocol != 16 || n.Player != PlayerIDManager.LocalPlatformID || n.Session != View.Session || !Guid.TryParseExact(n.Id, "N", out var _) || !seenNotices.Add(n.Id)) { return; } if (seenNotices.Count > 256) { seenNotices.Clear(); } if (Notes.Notice(n) || Media.HandleNotice(n)) { return; } if (n.Kind == "windyPost" || n.Kind == "windyLive") { string text = "@" + n.Name + " " + PhoneLanguage.Text(Sounds.Preferences.Language, (n.Kind == "windyLive") ? "vient de lancer un live" : "a publié sur Windy"); if (n.Kind == "windyPost" && n.Text != "") { text = text + " • " + ((n.Text.Length > 80) ? (n.Text.Substring(0, 79) + "…") : n.Text); } LocalPopup("Windy", text, delegate { app.OpenWindyNotification(n.Kind == "windyLive"); }, n.Owner); return; } if (n.Kind == "share") { SharedNumbers.Insert(0, n); if (SharedNumbers.Count > 10) { SharedNumbers.RemoveAt(10); } } LocalPopup((n.Kind == "share") ? "AirDrop" : "Banque", n.Text, (n.Kind == "share") ? ((Action)delegate { app.OpenShared(n); }) : null, n.Owner); app.Refresh(); } private void LocalPopup(string title, string text, Action accept = null, ulong owner = 0uL) { Sounds.Message(); app.PushNotification(title, text, accept, (owner == 0L) ? PlayerIDManager.LocalPlatformID : owner); } private void Transfer(PlayerID actor, PhoneCommand c, ulong owner) { if (!ServerEconomy.IsHosting) { throw new InvalidOperationException("Banque LabRP indisponible."); } ulong num = service.FindPlayer(c.Number) ?? throw new InvalidOperationException("Numéro inconnu."); PlayerID playerID = PlayerIDManager.GetPlayerID(num); if (playerID == null || !playerID.IsValid || num == owner) { throw new InvalidOperationException("Choisir un autre joueur connecté."); } long balance = ServerEconomy.GetBalance(owner); long balance2 = ServerEconomy.GetBalance(num); if (c.Amount <= 0 || c.Amount > 999999999999L || balance < c.Amount || balance2 > 999999999999L - c.Amount) { throw new InvalidOperationException("Montant invalide, fonds insuffisants ou compte destinataire plein."); } if (!service.ReserveExternal(owner, c.Nonce)) { Publish(actor.PlatformID, "Commande déjà traitée."); return; } ServerEconomy.TryTransfer((PlayerIDManager.GetPlayerID(owner) ?? throw new InvalidOperationException("Propriétaire hors ligne.")).SmallID, num, c.Amount); bool flag = ServerEconomy.GetBalance(owner) == balance - c.Amount && ServerEconomy.GetBalance(num) == balance2 + c.Amount; if (flag) { ServerEconomy.SaveIfHosting(); try { service.BankMovement(owner, num, c.Amount, c.Nonce, DateTime.UtcNow); } catch (Exception ex) { Warning("Virement effectué, historique indisponible : " + ex.Message); } Publish(num); PublishReaders(num); SendNotice(PlayerIDManager.GetPlayerID(ControllerFor(num)) ?? playerID, new PhoneNotice { Owner = num, Kind = "bank", Text = "Virement reçu : " + c.Amount + " $ de " + PhoneService.Display(service.Ensure(owner).Number) }); } PublishReaders(owner); Publish(actor.PlatformID, flag ? ("Virement effectué : " + c.Amount + " $.") : "Virement refusé par LabRP. Attendre puis réessayer."); } private void Warning(string text) { ((MelonBase)this).LoggerInstance.Warning(text); } private void Unbind() { ClearAccessLocal(); PhoneShutter.RestoreMenu(); surface?.Dispose(); surface = null; bound = null; app?.Bind(null); } private void Reset() { parking.Clear(); Notes?.Clear(); calls.StopAll(); ShowAdmin(host: false); Unbind(); ResetExperience(); windy = null; Windy = new WindyView(); windyViews.Clear(); windyOffsets.Clear(); lastWindyPosts.Clear(); publicPhotos.Clear(); windyRevision = 0L; Media?.Reset(); devices.Reset(); service = null; online.Clear(); lastCommands.Clear(); deviceStatuses.Clear(); commands.Clear(); snapshots.Clear(); retiredSessions.Clear(); callUpdates.Clear(); voiceFrames.Clear(); calls.StopAll(); listenReady.Clear(); lastShares.Clear(); SharedNumbers.Clear(); seenNotices.Clear(); notices.Clear(); speakers.Clear(); voiceReady.Clear(); callNonces.Clear(); audioSequences.Clear(); retiredCalls.Clear(); voice?.Dispose(); CallMicMuted = false; callRevision = 0L; nextHeartbeat = (lastCallSync = 0f); View = new PhoneView(); connected = (wasHost = (loading = false)); scene = (session = ""); revision = 0L; if (app != null) { app = new PhoneApp(this); Notice("Rejoindre un lobby Fusion."); } } public override void OnDeinitializeMelon() { Reset(); Sounds?.Dispose(); Media?.Dispose(); Notes?.Dispose(); Instance = null; } private void SaveConfig() { configuration.Validate(); Directory.CreateDirectory(directory); string text = Path.Combine(directory, "configuration.json"); string text2 = text + ".tmp"; File.WriteAllText(text2, JsonSerializer.Serialize(configuration, new JsonSerializerOptions { WriteIndented = true })); File.Move(text2, text, overwrite: true); } private void Configure(Action change) { if (!NetworkInfo.HasServer || !NetworkInfo.IsHost || service == null) { Notice("Réglages réservés à l'hôte."); return; } string json = JsonSerializer.Serialize(configuration); try { change(configuration.Screen); SaveConfig(); foreach (ulong item in online) { Publish(item); } } catch (Exception ex) { configuration = JsonSerializer.Deserialize(json); Notice(ex.Message); } } private void ShowAdmin(bool host) { if (host == adminVisible) { return; } if (host) { menuRoot.CreatePageLink(menuAdmin); } else { menuRoot.RemovePageLinks(menuAdmin); if (Menu.CurrentPage == menuAdmin) { Menu.OpenPage(menuRoot); } } adminVisible = host; } private void BuildMenu() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Unknown result type (might be due to invalid IL or missing references) //IL_02f8: Unknown result type (might be due to invalid IL or missing references) //IL_0315: Unknown result type (might be due to invalid IL or missing references) Menu.Initialize(); Page val = (menuRoot = Page.Root.CreatePage("Téléphone", Color.cyan, 0, true)); menuNumber = val.CreateFunction("Mon numéro : en attente", Color.white, (Action)delegate { }); menuStatus = val.CreateFunction(Status, Color.white, (Action)delegate { }); val.CreateFunction("Synchroniser", Color.cyan, (Action)delegate { Request("sync", "", "", "", "", enabled: false, 0L); }); val.CreateFunction("Rappeler mon téléphone devant moi", Color.green, (Action)delegate { Request("spawn", "", "", "", "", enabled: false, 0L); }); val.CreateFunction("Accepter l'appel", Color.green, (Action)delegate { Request("answer", "", "", "", View.Call.Id, enabled: false, 0L); }); val.CreateFunction("Refuser l'appel", Color.red, (Action)delegate { Request("reject", "", "", "", View.Call.Id, enabled: false, 0L); }); val.CreateFunction("Raccrocher / annuler", Color.red, (Action)delegate { Request("hangup", "", "", "", View.Call.Id, enabled: false, 0L); }); val.CreateFunction("Activer / couper mon micro d'appel", Color.yellow, (Action)ToggleCallMicrophone); val.CreateFunction("Activer / couper ma caméra", Color.cyan, (Action)delegate { Request("video", "", "", "", View.Call.Id, !View.Call.Video, 0L); }); val.CreateFunction("Diagnostic audio", Color.white, (Action)delegate { ((MelonBase)this).LoggerInstance.Msg($"Voix prête={PhoneVoice.Ready}, appel={View.Call.State}, pair={View.Call.Number}, micro coupé={CallMicMuted}"); }); val.CreateFunction("Diagnostic écran", Color.white, (Action)delegate { ((MelonBase)this).LoggerInstance.Msg($"Session={View.Session}, téléphone={View.Device}, écran={surface != null}, placement={surface?.PlacementInfo}, état={Status}"); }); Page val2 = (menuAdmin = val.CreatePage("Écran — hôte uniquement", Color.yellow, 0, false)); val2.CreateFunction("Inverser la face", Color.cyan, (Action)delegate { Configure(delegate(ScreenSettings s) { s.ReverseFace = !s.ReverseFace; }); }); val2.CreateFunction("Taille +10 %", Color.cyan, (Action)delegate { Configure(delegate(ScreenSettings s) { s.SizeMultiplier *= 1.1f; }); }); val2.CreateFunction("Taille -10 %", Color.cyan, (Action)delegate { Configure(delegate(ScreenSettings s) { s.SizeMultiplier /= 1.1f; }); }); val2.CreateFunction("X +1 cm", Color.white, (Action)delegate { Configure(delegate(ScreenSettings s) { s.X += 0.01f; }); }); val2.CreateFunction("X -1 cm", Color.white, (Action)delegate { Configure(delegate(ScreenSettings s) { s.X -= 0.01f; }); }); val2.CreateFunction("Y +1 cm", Color.white, (Action)delegate { Configure(delegate(ScreenSettings s) { s.Y += 0.01f; }); }); val2.CreateFunction("Y -1 cm", Color.white, (Action)delegate { Configure(delegate(ScreenSettings s) { s.Y -= 0.01f; }); }); val2.CreateFunction("Z +1 mm", Color.white, (Action)delegate { Configure(delegate(ScreenSettings s) { s.Z += 0.001f; }); }); val2.CreateFunction("Z -1 mm", Color.white, (Action)delegate { Configure(delegate(ScreenSettings s) { s.Z -= 0.001f; }); }); val2.CreateFunction("Rotation X +90°", Color.white, (Action)delegate { Configure(delegate(ScreenSettings s) { s.RotationX += 90f; }); }); val2.CreateFunction("Rotation Y +90°", Color.white, (Action)delegate { Configure(delegate(ScreenSettings s) { s.RotationY += 90f; }); }); val2.CreateFunction("Rotation Z +90°", Color.white, (Action)delegate { Configure(delegate(ScreenSettings s) { s.RotationZ += 90f; }); }); val2.CreateFunction("Réinitialiser l'écran", Color.red, (Action)delegate { Configure(delegate { configuration.Screen = new ScreenSettings(); }); }); } } public sealed class PhoneHighQualityMicrophone { private static readonly FieldInfo clipField = typeof(UnityVoiceReceiver).GetField("_voiceClip", BindingFlags.Instance | BindingFlags.NonPublic); private AudioClip clip; private int cursor; private string session = ""; private float last; private float warning; private PhoneStreamEncoder encoder = new PhoneStreamEncoder(); public void Tick(string id, Action send, Action warn, Func mix) { if (id != session) { session = id; clip = null; encoder = new PhoneStreamEncoder(); last = Time.realtimeSinceStartup; } if (id == "") { return; } try { IVoiceManager voiceManager = VoiceInfo.VoiceManager; IVoiceReceiver val = ((voiceManager != null) ? voiceManager.GetReceiver() : null); AudioClip val2 = (AudioClip)((val == null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val2 == (Object)null) { if (Time.realtimeSinceStartup - last > 2f) { throw new InvalidOperationException("Micro Fusion indisponible pour l’audio HD."); } return; } string validMicrophoneName = UnityVoiceReceiver.GetValidMicrophoneName(); if (!Microphone.IsRecording(validMicrophoneName)) { return; } int position = Microphone.GetPosition(validMicrophoneName); if (position < 0 || position >= val2.samples) { return; } if ((Object)(object)clip != (Object)(object)val2) { clip = val2; cursor = position; last = Time.realtimeSinceStartup; encoder = new PhoneStreamEncoder(); return; } int num = PhoneMicrophoneRules.Available(cursor, position, val2.samples); if (num == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - last > 0.2f) { cursor = position; last = realtimeSinceStartup; encoder = new PhoneStreamEncoder(); return; } Il2CppStructArray val3 = new Il2CppStructArray((long)(num * val2.channels)); if (!val2.GetData(val3, cursor)) { throw new InvalidOperationException("Lecture micro HD impossible."); } float[] array = new float[((Il2CppArrayBase)(object)val3).Length]; InteropUtilities.Copy(((Il2CppObjectBase)val3).Pointer, IntPtr.Size, ((Il2CppArrayBase)(object)val3).Length, array); cursor = position; last = realtimeSinceStartup; short[] arg = PhoneMicrophoneRules.Mono(array, val2.channels); encoder.Push(mix(arg, val2.frequency), val2.frequency, send); } catch (Exception ex) { if (Time.realtimeSinceStartup >= warning) { warning = Time.realtimeSinceStartup + 5f; warn(ex.Message); } clip = null; encoder = new PhoneStreamEncoder(); } } } public static class PhoneIcons { private static bool Line(float x, float y, float ax, float ay, float bx, float by, float w = 2.8f) { float num = bx - ax; float num2 = by - ay; float num3 = Math.Max(0f, Math.Min(1f, ((x - ax) * num + (y - ay) * num2) / (num * num + num2 * num2))); return (x - ax - num3 * num) * (x - ax - num3 * num) + (y - ay - num3 * num2) * (y - ay - num3 * num2) < w * w; } public static Sprite Create(string key) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_10c7: Unknown result type (might be due to invalid IL or missing references) //IL_10d6: Unknown result type (might be due to invalid IL or missing references) //IL_10f6: Unknown result type (might be due to invalid IL or missing references) //IL_1079: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(64, 64, (TextureFormat)4, false); ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)1; for (int i = 0; i < 64; i++) { for (int j = 0; j < 64; j++) { float num = j; float num2 = i; float num3 = (float)Math.Sqrt((num - 32f) * (num - 32f) + (num2 - 32f) * (num2 - 32f)); bool flag = false; switch (key) { case "windy": flag = Line(num, num2, 8f, 39f, 44f, 39f) || Line(num, num2, 44f, 39f, 51f, 45f) || Line(num, num2, 51f, 45f, 48f, 53f) || Line(num, num2, 48f, 53f, 40f, 53f) || Line(num, num2, 8f, 27f, 51f, 27f) || Line(num, num2, 51f, 27f, 56f, 34f) || Line(num, num2, 56f, 34f, 51f, 39f) || Line(num, num2, 8f, 16f, 34f, 16f) || Line(num, num2, 34f, 16f, 41f, 9f) || Line(num, num2, 41f, 9f, 34f, 5f); break; case "camera": flag = Line(num, num2, 10f, 16f, 54f, 16f) || Line(num, num2, 54f, 16f, 54f, 45f) || Line(num, num2, 54f, 45f, 10f, 45f) || Line(num, num2, 10f, 45f, 10f, 16f) || Line(num, num2, 23f, 45f, 27f, 52f) || Line(num, num2, 27f, 52f, 39f, 52f) || Line(num, num2, 39f, 52f, 43f, 45f) || Math.Abs((float)Math.Sqrt((num - 32f) * (num - 32f) + (num2 - 30f) * (num2 - 30f)) - 10f) < 2.5f; break; case "gallery": flag = Line(num, num2, 15f, 10f, 49f, 10f) || Line(num, num2, 49f, 10f, 49f, 43f) || Line(num, num2, 49f, 43f, 39f, 53f) || Line(num, num2, 39f, 53f, 15f, 53f) || Line(num, num2, 15f, 53f, 15f, 10f) || Line(num, num2, 39f, 53f, 39f, 43f) || Line(num, num2, 39f, 43f, 49f, 43f) || Line(num, num2, 23f, 21f, 41f, 21f) || Line(num, num2, 23f, 31f, 41f, 31f); break; case "send": flag = Line(num, num2, 9f, 12f, 55f, 32f) || Line(num, num2, 55f, 32f, 9f, 52f) || Line(num, num2, 9f, 52f, 19f, 32f) || Line(num, num2, 19f, 32f, 9f, 12f) || Line(num, num2, 19f, 32f, 48f, 32f); break; case "clip": flag = Line(num, num2, 22f, 18f, 43f, 39f) || Line(num, num2, 43f, 39f, 43f, 47f) || Line(num, num2, 43f, 47f, 35f, 51f) || Line(num, num2, 35f, 51f, 12f, 28f) || Line(num, num2, 12f, 28f, 12f, 18f) || Line(num, num2, 12f, 18f, 22f, 8f) || Line(num, num2, 22f, 8f, 31f, 8f) || Line(num, num2, 31f, 8f, 51f, 28f) || Line(num, num2, 22f, 18f, 26f, 14f) || Line(num, num2, 26f, 14f, 46f, 34f); break; case "contacts": flag = (num - 32f) * (num - 32f) + (num2 - 43f) * (num2 - 43f) < 81f || (num2 > 12f && num2 < 29f && (num - 32f) * (num - 32f) + (num2 - 12f) * (num2 - 12f) < 300f); break; case "messages": flag = Line(num, num2, 12f, 45f, 52f, 45f) || Line(num, num2, 52f, 45f, 52f, 23f) || Line(num, num2, 52f, 23f, 27f, 23f) || Line(num, num2, 27f, 23f, 16f, 13f) || Line(num, num2, 16f, 13f, 16f, 23f) || Line(num, num2, 16f, 23f, 12f, 23f) || Line(num, num2, 12f, 23f, 12f, 45f) || (num2 > 31f && num2 < 36f && (Math.Abs(num - 23f) < 2f || Math.Abs(num - 32f) < 2f || Math.Abs(num - 41f) < 2f)); break; case "microphone": flag = Line(num, num2, 26f, 44f, 26f, 28f) || Line(num, num2, 26f, 44f, 38f, 44f) || Line(num, num2, 38f, 44f, 38f, 28f) || Line(num, num2, 38f, 28f, 26f, 28f) || Line(num, num2, 19f, 31f, 19f, 22f) || Line(num, num2, 19f, 22f, 32f, 17f) || Line(num, num2, 32f, 17f, 45f, 22f) || Line(num, num2, 45f, 22f, 45f, 31f) || Line(num, num2, 32f, 17f, 32f, 9f) || Line(num, num2, 23f, 9f, 41f, 9f); break; case "speaker": flag = Line(num, num2, 12f, 23f, 23f, 23f) || Line(num, num2, 23f, 23f, 36f, 12f) || Line(num, num2, 36f, 12f, 36f, 52f) || Line(num, num2, 36f, 52f, 23f, 41f) || Line(num, num2, 23f, 41f, 12f, 41f) || Line(num, num2, 12f, 41f, 12f, 23f) || Line(num, num2, 43f, 23f, 49f, 32f) || Line(num, num2, 49f, 32f, 43f, 41f) || Line(num, num2, 48f, 15f, 58f, 32f) || Line(num, num2, 58f, 32f, 48f, 49f); break; case "phone": flag = Line(num, num2, 18f, 48f, 14f, 39f, 5f) || Line(num, num2, 14f, 39f, 24f, 24f, 5f) || Line(num, num2, 24f, 24f, 39f, 14f, 5f) || Line(num, num2, 39f, 14f, 48f, 18f, 5f) || Line(num, num2, 18f, 48f, 25f, 42f, 5f) || Line(num, num2, 48f, 18f, 42f, 25f, 5f); break; case "bank": flag = Line(num, num2, 10f, 41f, 32f, 54f) || Line(num, num2, 32f, 54f, 54f, 41f) || Line(num, num2, 12f, 39f, 52f, 39f) || Line(num, num2, 12f, 12f, 52f, 12f) || Line(num, num2, 18f, 18f, 18f, 33f) || Line(num, num2, 32f, 18f, 32f, 33f) || Line(num, num2, 46f, 18f, 46f, 33f); break; case "nearby": flag = num3 < 5f || (Math.Abs(num3 - 15f) < 2.5f && num2 > 24f) || (Math.Abs(num3 - 26f) < 2.5f && num2 > 24f) || Line(num, num2, 23f, 10f, 32f, 21f) || Line(num, num2, 32f, 21f, 41f, 10f); break; case "settings": flag = Math.Abs(num3 - 13f) < 3f || (num3 > 22f && num3 < 28f) || (num3 > 25f && num3 < 32f && Math.Abs(Math.Sin(Math.Atan2(num2 - 32f, num - 32f) * 4.0)) > 0.7); break; } val.SetPixel(j, i, new Color(1f, 1f, 1f, (float)(flag ? 1 : 0))); } } val.Apply(false, false); return Sprite.Create(val, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, new Vector4(0f, 0f, 0f, 0f)); } } public static class PhoneLanguage { private static readonly Dictionary English = new Dictionary(StringComparer.Ordinal) { [" contacts • Carnet personnel"] = " contacts • Personal address book", [" i/s"] = " fps", [" images/s"] = " fps", [" notification(s)"] = " notification(s)", [" photos • Photos de ce téléphone"] = " photos • This phone's photos", [" spectateurs • "] = " viewers • ", ["+ Ajouter"] = "+ Add", ["0 contact"] = "0 contacts", ["APPEL TÉLÉPHONIQUE"] = "PHONE CALL", ["APPEL VIDÉO"] = "VIDEO CALL", ["Accueil"] = "Home", ["Actualiser"] = "Refresh", ["Aimé"] = "Liked", ["Ajouter une photo"] = "Add a photo", ["Annuler"] = "Cancel", ["Aperçu photo : "] = "Photo preview: ", ["Appel en cours…"] = "Calling…", ["Appel entrant"] = "Incoming call", ["Appel terminé"] = "Call ended", ["Appeler en visio"] = "Video call", ["Appeler"] = "Call", ["Appels : "] = "Calls: ", ["Appuyer sur B de la main porteuse"] = "Press B on the hand holding the phone", ["Appuyer sur Y de la main porteuse"] = "Press Y on the hand holding the phone", ["Attendre le chargement de la photo."] = "Wait for the photo to load.", ["Aucun contact trouvé"] = "No contacts found", ["Aucun live en cours"] = "No live streams right now", ["Aucun message"] = "No messages", ["Aucun virement enregistré"] = "No transfers yet", ["Avec caméra"] = "With camera", ["Banque"] = "Bank", ["BÉNÉFICIAIRE"] = "RECIPIENT", ["COMPTE COURANT"] = "CURRENT ACCOUNT", ["Cadence cible : le PC et le réseau déterminent la fluidité réelle."] = "Target frame rate: actual smoothness depends on your PC and network.", ["Caméra arrière"] = "Rear camera", ["Caméra avant"] = "Front camera", ["Caméra du correspondant coupée"] = "The other person's camera is off", ["Caméra"] = "Camera", ["Changer la photo jointe"] = "Change attachment", ["Changer la photo"] = "Change photo", ["Changer le code"] = "Change PIN", ["Chargement…"] = "Loading…", ["Choisir le destinataire"] = "Choose recipient", ["Choisir un numéro"] = "Choose a number", ["Choisir un pseudo"] = "Choose a username", ["Commencer le live"] = "Start live stream", ["Commenter"] = "Comment", ["Confirme le nouveau code"] = "Confirm the new PIN", ["Confirme ton code"] = "Confirm your PIN", ["Confirmer la suppression"] = "Confirm deletion", ["Confirmer le virement"] = "Confirm transfer", ["Confirmer"] = "Confirm", ["Connexion au direct…"] = "Connecting to live stream…", ["Connexion vidéo…"] = "Connecting video…", ["Continuer"] = "Continue", ["Correspondant sur haut-parleur"] = "The other person is on speakerphone", ["Crée ton code à quatre chiffres"] = "Create your four-digit PIN", ["Créer mon compte"] = "Create account", ["DERNIÈRES OPÉRATIONS"] = "RECENT TRANSACTIONS", ["Effacer"] = "Delete", ["Enregistrer"] = "Save", ["Envoi de la photo…"] = "Sending photo…", ["Envoi…"] = "Sending…", ["Envoyer cette photo"] = "Send this photo", ["Envoyer le commentaire"] = "Send comment", ["Envoyer le message"] = "Send message", ["Espace"] = "Space", ["Faire un virement"] = "Make a transfer", ["Fluidité caméra / visio"] = "Camera / video frame rate", ["Galerie"] = "Gallery", ["Haut-parleur"] = "Speaker", ["Historique des virements"] = "Transfer history", ["Indisponible"] = "Unavailable", ["Joindre une photo"] = "Attach a photo", ["J’aime"] = "Like", ["La caméra et le micro sont diffusés sur Windy"] = "Your camera and microphone are broadcast on Windy", ["Le code est réservé au propriétaire."] = "Only the owner can change the PIN.", ["Le fil est calme.\nPublie le premier message !"] = "The feed is quiet.\nWrite the first post!", ["Les codes ne correspondent pas."] = "The PINs do not match.", ["Lives • "] = "Live streams • ", ["MONTANT"] = "AMOUNT", ["Maj"] = "Shift", ["Mes comptes / Banque personnelle"] = "My accounts / Personal banking", ["Messages : "] = "Messages: ", ["Micro coupé"] = "Muted", ["Micro"] = "Microphone", ["Modifier le contact"] = "Edit contact", ["Modifier"] = "Edit", ["NOM"] = "NAME", ["NUMÉRO"] = "NUMBER", ["Nom et numéro de téléphone"] = "Name and phone number", ["Nouveau code à quatre chiffres"] = "New four-digit PIN", ["Nouveau contact"] = "New contact", ["Nouveau message"] = "New message", ["Nouveau"] = "New", ["Nouvelle publication"] = "New post", ["Numéro : "] = "Number: ", ["Opérations"] = "Transactions", ["Partager mon numéro à 3 m"] = "Share my number within 3 m", ["Passer en live"] = "Go live", ["Photo du contact"] = "Contact photo", ["Photo en cours de synchronisation. Réessayer dans quelques secondes."] = "Photo is syncing. Try again in a few seconds.", ["Plus ancien"] = "Older", ["Plus récent"] = "Newer", ["Pour vous / @"] = "For you / @", ["Préparation de la caméra…"] = "Preparing camera…", ["Publier"] = "Post", ["Quitter"] = "Leave", ["Quoi de neuf ?"] = "What's happening?", ["Raccrocher"] = "End call", ["Recherche : "] = "Search: ", ["Rechercher"] = "Search", ["Refuser"] = "Decline", ["Regarde, commente et partage le moment"] = "Watch, comment and share the moment", ["Rejoins la conversation"] = "Join the conversation", ["Retirer ce contact de ton carnet ?\nLes conversations resteront disponibles."] = "Remove this contact?\nYour conversations will remain available.", ["Retirer la photo"] = "Remove photo", ["Retirer"] = "Remove", ["Retour au contact"] = "Back to contact", ["Retour au fil"] = "Back to feed", ["Retour aux comptes"] = "Back to accounts", ["Retour aux contacts"] = "Back to contacts", ["Retour"] = "Back", ["Reçu"] = "Received", ["Réglages"] = "Settings", ["Répondre en audio"] = "Answer with audio", ["Répondre"] = "Answer", ["Saisir le nom"] = "Enter name", ["Saisir le numéro"] = "Enter number", ["Saisir un montant entier positif."] = "Enter a positive whole amount.", ["Saisir un montant"] = "Enter amount", ["Saisis le code du téléphone"] = "Enter the phone PIN", ["Saisis les quatre chiffres du code."] = "Enter the four-digit PIN.", ["Solde disponible : "] = "Available balance: ", ["Supprimer cette photo de Galerie ?\nLes copies déjà envoyées restent chez leurs destinataires."] = "Delete this photo from Gallery?\nCopies already sent stay with their recipients.", ["Supprimer de Galerie"] = "Delete from Gallery", ["Supprimer le contact"] = "Delete contact", ["Supprimer"] = "Delete", ["Ta Galerie est vide.\nPrends ta première photo dans Photo."] = "Your Gallery is empty.\nTake your first photo in Camera.", ["Terminer"] = "End", ["Terminé"] = "Done", ["Tiens le téléphone pour rester en direct."] = "Hold the phone to stay live.", ["Ton carnet est vide.\nAjoute ton premier contact."] = "Your address book is empty.\nAdd your first contact.", ["Ton live"] = "Your live stream", ["Tout l’historique"] = "Full history", ["Tu pourras choisir une photo après\nl’enregistrement du contact."] = "You can choose a photo after\nsaving the contact.", ["Téléphone verrouillé\nLe propriétaire doit créer son code."] = "Phone locked\nThe owner must create a PIN.", ["Téléphone"] = "Phone", ["Un transfert entre comptes LabRP"] = "A transfer between LabRP accounts", ["Valider"] = "Confirm", ["Verrouiller"] = "Lock", ["Vers "] = "To ", ["Virement"] = "Transfer", ["Visio : "] = "Video call: ", ["Visio"] = "Video", ["Voir la photo"] = "View photo", ["Volume des sonneries : "] = "Ringtone volume: ", ["Vous"] = "You", ["Vérifie le bénéficiaire et le montant"] = "Check the recipient and amount", ["Écrire / modifier"] = "Write / edit", ["Écrire le texte"] = "Write text", ["Écrire un message ou joindre une photo."] = "Write a message or attach a photo.", ["Écris ton message…"] = "Write your message…", ["Classique"] = "Classic", ["Cristal"] = "Crystal", ["Carillon"] = "Chime", ["Silencieux"] = "Silent", ["Langue"] = "Language", ["Français"] = "French", ["Anglais"] = "English", ["Contact enregistré."] = "Contact saved.", ["Contact supprimé."] = "Contact deleted.", ["Photo du contact retirée."] = "Contact photo removed.", ["Photo du contact enregistrée."] = "Contact photo saved.", ["Nouveau SMS."] = "New message.", ["Message envoyé."] = "Message sent.", ["Action déjà enregistrée."] = "Action already saved.", ["Action trop rapide, réessayer."] = "Too fast. Please try again.", ["Session actualisée, réessayer."] = "Session updated. Please try again.", ["Numéro invalide."] = "Invalid number.", ["Saisir un numéro RP de 10 chiffres."] = "Enter a 10-digit RP phone number.", ["C'est ton propre numéro."] = "This is your own number.", ["Impossible d'envoyer un SMS à soi-même."] = "You cannot message yourself.", ["Ce numéro n'est pas attribué."] = "This number is not assigned.", ["Ce correspondant est hors ligne."] = "This person is offline.", ["Appel connecté."] = "Call connected.", ["Appel refusé."] = "Call declined.", ["Téléphone absent."] = "Phone not available.", ["Choisir une photo de la Galerie de ce téléphone."] = "Choose a photo from this phone's Gallery.", ["Enregistrer le contact avant de choisir sa photo."] = "Save the contact before choosing a photo.", ["Photo invalide."] = "Invalid photo.", ["Déverrouiller le téléphone."] = "Unlock the phone.", ["Une photo est déjà en cours d'envoi."] = "A photo is already being sent.", ["Photo illisible."] = "Photo could not be read.", ["Photo reçue invalide."] = "Invalid received photo.", ["Photo non disponible pour cet expéditeur."] = "Photo not available for this sender.", ["Photo de contact indisponible pour ce téléphone."] = "Contact photo not available for this phone.", ["Galerie pleine (100 photos). Supprimer une photo."] = "Gallery full (100 photos). Delete a photo.", ["Prise de photo…"] = "Taking photo…", ["Tenir le téléphone et attendre l'écran."] = "Hold the phone and wait for the screen.", ["Code incorrect."] = "Incorrect PIN.", ["Le code doit contenir quatre chiffres."] = "The PIN must contain four digits.", ["Nom de contact : 1 à 32 caractères."] = "Contact name: 1 to 32 characters.", ["SMS : 1 à 320 caractères."] = "Message: 1 to 320 characters.", ["Quitter le live avant l’appel."] = "Leave the live stream before calling.", ["Haut-parleur activé : conversation audible à 2 m."] = "Speakerphone on: conversation audible within 2 m.", ["Haut-parleur désactivé."] = "Speakerphone off.", ["Caméra de visio activée."] = "Video camera on.", ["Caméra de visio coupée."] = "Video camera off.", ["Photo"] = "Camera", ["SMS enregistré et envoyé."] = "Message saved and sent.", ["Transfert photo expiré, réessayer."] = "Photo transfer timed out. Try again.", ["Photo prise • enregistrement dans la Galerie…"] = "Photo taken • saving to Gallery…", ["Photo conservée localement ; synchronisation au prochain déverrouillage."] = "Photo saved locally; it will sync when you next unlock.", ["Tenir le téléphone pour prendre la photo."] = "Hold the phone to take a photo.", ["Compte Windy prêt."] = "Windy account ready.", ["Publication Windy envoyée."] = "Windy post sent.", ["Commande déjà traitée."] = "Command already processed.", ["Aucun joueur compatible à moins de 3 m."] = "No compatible player within 3 m.", ["Virement refusé par LabRP. Attendre puis réessayer."] = "Transfer declined by LabRP. Wait and try again.", ["Téléphone reverrouillé : appel terminé."] = "Phone locked again: call ended.", ["Appel terminé : changement de map."] = "Call ended: map changed.", ["Correspondant déconnecté."] = "The other person disconnected.", ["Appel terminé : aucune réponse ou liaison interrompue."] = "Call ended: no answer or connection lost.", ["Voix Fusion désactivée : appel terminé."] = "Fusion voice disabled: call ended.", ["Téléphone déverrouillé."] = "Phone unlocked.", ["Téléphone verrouillé."] = "Phone locked.", ["Code enregistré."] = "PIN saved.", ["Clavier"] = "Keypad", ["Récents"] = "Recents", ["Historique des appels"] = "Call history", ["Ajoute # avant le numéro pour appeler en masqué"] = "Add # before the number to hide caller ID", ["Appel masqué"] = "Private call", ["Masqué"] = "Private", ["Numéro masqué"] = "Private number", ["Impossible de rappeler un numéro masqué."] = "You cannot call back a private number.", ["Manqué"] = "Missed", ["Entrant"] = "Incoming", ["Sortant"] = "Outgoing", ["Refusé"] = "Declined", ["Annulé"] = "Cancelled", ["Aucun appel récent"] = "No recent calls", ["Apparence"] = "Appearance", ["Thème"] = "Theme", ["Clair"] = "Light", ["Sombre"] = "Dark", ["Fond d’écran"] = "Wallpaper", ["Choisir dans la Galerie"] = "Choose from Gallery", ["Même image sur le verrouillage et l’accueil."] = "Same image on the lock screen and home screen.", ["Retirer le fond d’écran"] = "Remove wallpaper", ["Fond d’écran indisponible."] = "Wallpaper unavailable.", ["Tes conversations"] = "Your conversations", ["Message"] = "Message", ["Conversation"] = "Conversation", ["Message vocal"] = "Voice message", ["Vocal"] = "Voice note", ["Lu"] = "Read", ["Envoyé"] = "Sent", ["Commence la conversation"] = "Start the conversation", ["Écrire"] = "Write", ["Enregistrement…"] = "Recording…", ["Vocal prêt"] = "Voice note ready", ["Enregistre ta voix"] = "Record your voice", ["Arrêter"] = "Stop", ["Réenregistrer"] = "Record again", ["Démarrage du micro…"] = "Starting microphone…", ["Micro actif"] = "Microphone active", ["Fusion n’a pas ouvert le micro. Vérifier le périphérique sélectionné."] = "Fusion has not opened the microphone. Check the selected device.", ["Le micro Fusion n’est pas en cours de capture."] = "The Fusion microphone is not recording.", ["Le curseur du micro reste immobile. Vérifier le périphérique Fusion."] = "The microphone cursor is not moving. Check the Fusion input device.", ["Chargement du vocal…"] = "Loading voice message…", ["Téléchargement du vocal expiré. Réessayer."] = "Voice message download timed out. Try again.", ["vient de lancer un live"] = "just went live", ["a publié sur Windy"] = "posted on Windy", ["Niveau micro"] = "Microphone level", ["Aucune voix capturée. Vérifier le micro Fusion puis réenregistrer."] = "No voice captured. Check the Fusion microphone and record again.", ["Activer le micro Fusion avant d’enregistrer."] = "Enable the Fusion microphone before recording.", ["Lecture du micro impossible."] = "Unable to read the microphone.", ["Écouter"] = "Listen", ["Réécouter"] = "Listen again", ["Envoyer"] = "Send", ["Micro Fusion • 15 secondes maximum"] = "Fusion microphone • 15 seconds maximum", ["Partager ton numéro à proximité"] = "Share your number nearby", ["Aucun joueur à moins de 3 m"] = "No player within 3 m", ["Numéros reçus"] = "Received numbers", ["Aucun numéro reçu"] = "No numbers received", ["Partager à tous • 3 m"] = "Share with all • 3 m", ["Activer le micro Fusion et déverrouiller le téléphone."] = "Enable the Fusion microphone and unlock the phone.", ["Quitter l’appel ou le live avant d’enregistrer."] = "Leave the call or live stream before recording.", ["Enregistrer un vocal avant de l’envoyer."] = "Record a voice note before sending.", ["Vocal : 0,5 à 15 secondes."] = "Voice note: 0.5 to 15 seconds.", ["Vocal invalide."] = "Invalid voice note.", ["Vocal indisponible."] = "Voice note unavailable.", ["Vocal indisponible pour cet expéditeur."] = "Voice note not available for this sender.", ["Envoi du vocal expiré."] = "Voice note upload timed out.", ["Stockage des vocaux plein."] = "Voice note storage full.", ["Accès à ce vocal refusé."] = "Access to this voice note denied.", ["Attendre le chargement du vocal."] = "Wait for the voice note to load.", ["Choisir une photo ou un vocal."] = "Choose a photo or a voice note." }; public static bool Valid(string language) { return language == "fr" || language == "en"; } public static string Text(string language, string text) { string value; return (language == "en" && text != null && English.TryGetValue(text, out value)) ? value : (text ?? ""); } public static string Status(string language, string text) { if (language != "en") { return text ?? ""; } string text2 = Text(language, text); if (text2 != text) { return text2; } if (text == null) { return ""; } Match match = Regex.Match(text, "^Numéro partagé avec ([0-9]+) joueur\\(s\\)\\.$"); if (match.Success) { return "Number shared with " + match.Groups[1].Value + " player(s)."; } Match match2 = Regex.Match(text, "^Virement effectué : ([0-9]+) \\$\\.$"); if (match2.Success) { return "Transfer completed: " + match2.Groups[1].Value + " $."; } Match match3 = Regex.Match(text, "^Virement reçu : ([0-9]+) \\$ de ([0-9 ]+)$"); if (match3.Success) { return "Transfer received: " + match3.Groups[1].Value + " $ from " + match3.Groups[2].Value; } Match match4 = Regex.Match(text, "^(De|Vers) ([0-9 ]+)$"); if (match4.Success) { return ((match4.Groups[1].Value == "De") ? "From " : "To ") + match4.Groups[2].Value; } return text; } public static string Date(string language, DateTime date) { if (language == "en") { return date.ToString("dddd d MMMM", CultureInfo.InvariantCulture); } string[] array = new string[7] { "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi" }; string[] array2 = new string[12] { "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre" }; return array[(int)date.DayOfWeek] + " " + date.Day.ToString(CultureInfo.InvariantCulture) + " " + array2[date.Month - 1]; } } public sealed class WindyLiveSummary { public string Id { get; set; } = ""; public ulong Host { get; set; } public string Handle { get; set; } = ""; public ulong Owner { get; set; } public ushort Device { get; set; } public bool Front { get; set; } public int Viewers { get; set; } public int Likes { get; set; } } public sealed class WindyLiveComment { public string Handle { get; set; } = ""; public string Text { get; set; } = ""; } public sealed class WindyLiveView { public int Protocol { get; set; } = 16; public ulong Player { get; set; } public string Session { get; set; } = ""; public long Revision { get; set; } public WindyLiveSummary Active { get; set; } public bool Casting { get; set; } public bool Liked { get; set; } public List Available { get; set; } = new List(); public List Comments { get; set; } = new List(); } public sealed class PhoneLive { private sealed class Stream { public string Id; public string Handle; public ulong Host; public ulong Owner; public ushort Device; public bool Front; public double Until; public Dictionary Viewers = new Dictionary(); public HashSet Likes = new HashSet(); public List Comments = new List(); public Dictionary CommentTimes = new Dictionary(); } public const int MaxStreams = 4; public const int MaxViewers = 16; public const int MaxComment = 140; private readonly Dictionary streams = new Dictionary(); private readonly Dictionary watching = new Dictionary(); public void Clear() { streams.Clear(); watching.Clear(); } public string Casting(ulong actor) { return streams.Values.FirstOrDefault((Stream s) => s.Host == actor)?.Id ?? ""; } public bool SourceValid(ulong actor, string id, Func available) { Stream value; return streams.TryGetValue(id ?? "", out value) && value.Host == actor && available(value.Host, value.Owner, value.Device); } public string Watching(ulong actor) { string value; return watching.TryGetValue(actor, out value) ? value : ""; } public string Start(ulong actor, ulong owner, ushort device, string handle, bool front, double now) { handle = PhoneWindy.Handle(handle); if (Casting(actor) != "") { return Casting(actor); } if (streams.Count >= 4) { throw new InvalidOperationException("Quatre lives simultanés maximum."); } Leave(actor); Stream stream = new Stream { Id = Guid.NewGuid().ToString("N"), Host = actor, Owner = owner, Device = device, Handle = handle, Front = front, Until = now + 10.0 }; streams[stream.Id] = stream; return stream.Id; } public void Join(ulong actor, string id, double now) { if (Casting(actor) != "") { throw new InvalidOperationException("Arrêter ton live avant d'en rejoindre un autre."); } Stream stream = Find(id); if (stream.Host == actor) { throw new InvalidOperationException("Tu diffuses déjà ce live."); } if (!stream.Viewers.ContainsKey(actor) && stream.Viewers.Count >= 16) { throw new InvalidOperationException("Ce live est complet (16 spectateurs)."); } Leave(actor); watching[actor] = id; stream.Viewers[actor] = now + 10.0; } public void Leave(ulong actor) { if (watching.Remove(actor, out var value) && streams.TryGetValue(value, out var value2)) { value2.Viewers.Remove(actor); } } public void Stop(ulong actor, string id) { Stream stream = Find(id); if (stream.Host != actor) { throw new InvalidOperationException("Seul l'auteur peut arrêter ce live."); } ulong[] array = stream.Viewers.Keys.ToArray(); foreach (ulong key in array) { watching.Remove(key); } streams.Remove(id); } public void Camera(ulong actor, string id, bool front) { Stream stream = Find(id); if (stream.Host != actor) { throw new InvalidOperationException("Caméra réservée à l'auteur."); } stream.Front = front; } public void Pulse(ulong actor, double now) { string value = Casting(actor); if (value != "") { streams[value].Until = now + 10.0; } if (watching.TryGetValue(actor, out value) && streams.TryGetValue(value, out var value2)) { value2.Viewers[actor] = now + 10.0; } } public void Like(ulong actor, string id) { Stream stream = Participant(actor, id); stream.Likes.Add(actor); } public void Comment(ulong actor, string id, string handle, string text, double now) { Stream stream = Participant(actor, id); text = (text ?? "").Trim(); if (text.Length < 1 || text.Length > 140 || text.Any(char.IsControl)) { throw new InvalidOperationException("Commentaire : 1 à 140 caractères."); } if (stream.CommentTimes.TryGetValue(actor, out var value) && now - value < 1.0) { throw new InvalidOperationException("Attendre une seconde entre deux commentaires."); } handle = PhoneWindy.Handle(handle); stream.CommentTimes[actor] = now; stream.Comments.Add(new WindyLiveComment { Handle = handle, Text = text }); while (stream.Comments.Count > 12) { stream.Comments.RemoveAt(0); } } private Stream Find(string id) { if (id == null || !streams.TryGetValue(id, out var value)) { throw new InvalidOperationException("Ce live est terminé."); } return value; } private Stream Participant(ulong actor, string id) { Stream stream = Find(id); if (stream.Host != actor && Watching(actor) != id) { throw new InvalidOperationException("Rejoindre le live avant d'interagir."); } return stream; } public ulong[] Targets(ulong actor, string id, double now) { Stream value; return (streams.TryGetValue(id, out value) && value.Host == actor && now < value.Until) ? (from p in value.Viewers where now < p.Value select p.Key).ToArray() : Array.Empty(); } public bool Routes(string id, ulong target, double now) { Stream value; double value2; return streams.TryGetValue(id, out value) && now < value.Until && value.Viewers.TryGetValue(target, out value2) && now < value2; } public bool Tick(double now, Func available) { bool result = false; Stream[] array = streams.Values.ToArray(); foreach (Stream stream in array) { if (now >= stream.Until || !available(stream.Host, stream.Owner, stream.Device)) { Stop(stream.Host, stream.Id); result = true; continue; } ulong[] array2 = (from p in stream.Viewers where now >= p.Value select p.Key).ToArray(); foreach (ulong actor in array2) { Leave(actor); result = true; } } return result; } public void Disconnect(ulong actor) { Leave(actor); string text = Casting(actor); if (text != "") { Stop(actor, text); } } private WindyLiveSummary Summary(Stream s) { return new WindyLiveSummary { Id = s.Id, Host = s.Host, Owner = s.Owner, Device = s.Device, Handle = s.Handle, Front = s.Front, Viewers = s.Viewers.Count, Likes = s.Likes.Count }; } public WindyLiveView View(ulong actor) { WindyLiveView windyLiveView = new WindyLiveView { Player = actor, Available = streams.Values.Select(Summary).ToList() }; string text = Casting(actor); windyLiveView.Casting = text != ""; if (text == "") { text = Watching(actor); } if (text != "" && streams.TryGetValue(text, out var value)) { windyLiveView.Active = Summary(value); windyLiveView.Liked = value.Likes.Contains(actor); windyLiveView.Comments = value.Comments.Select((WindyLiveComment c) => new WindyLiveComment { Handle = c.Handle, Text = c.Text }).ToList(); } return windyLiveView; } } public sealed class PhoneAccessMessage : ModuleMessageHandler { public override ExpectedReceiverType ExpectedReceiver => (ExpectedReceiverType)2; protected override void OnHandleMessage(ReceivedMessage m) { if (NetworkInfo.IsHost || ((ReceivedMessage)(ref m)).IsServerHandled || ((ReceivedMessage)(ref m)).Sender != 0 || ((ReceivedMessage)(ref m)).Bytes.Length > 262144) { return; } try { PhoneAccessView phoneAccessView = JsonSerializer.Deserialize(((ReceivedMessage)(ref m)).ReadData().Payload); if (phoneAccessView != null && phoneAccessView.Player == PlayerIDManager.LocalPlatformID && phoneAccessView.Protocol == 16) { PhoneMod.Instance?.EnqueueAccess(phoneAccessView); } } catch (Exception) { } } } public sealed class WindyLiveMessage : ModuleMessageHandler { public override ExpectedReceiverType ExpectedReceiver => (ExpectedReceiverType)2; protected override void OnHandleMessage(ReceivedMessage m) { if (NetworkInfo.IsHost || ((ReceivedMessage)(ref m)).IsServerHandled || ((ReceivedMessage)(ref m)).Sender != 0 || ((ReceivedMessage)(ref m)).Bytes.Length > 16000) { return; } try { WindyLiveView windyLiveView = JsonSerializer.Deserialize(((ReceivedMessage)(ref m)).ReadData().Payload); if (windyLiveView != null && windyLiveView.Player == PlayerIDManager.LocalPlatformID && windyLiveView.Protocol == 16) { PhoneMod.Instance?.EnqueueLive(windyLiveView); } } catch (Exception) { } } } public sealed class WindyLiveVoiceHost : ModuleMessageHandler { public override ExpectedReceiverType ExpectedReceiver => (ExpectedReceiverType)1; protected override void OnHandleMessage(ReceivedMessage m) { if (!NetworkInfo.IsHost || !((ReceivedMessage)(ref m)).IsServerHandled || !((ReceivedMessage)(ref m)).Sender.HasValue || ((ReceivedMessage)(ref m)).Bytes.Length > 50000) { return; } try { PlayerID playerID = PlayerIDManager.GetPlayerID(((ReceivedMessage)(ref m)).Sender.Value); PhoneVoicePacket phoneVoicePacket = ((ReceivedMessage)(ref m)).ReadData(); if (playerID != null && playerID.IsValid && phoneVoicePacket.Read(out var call, out var _, out var sequence, out var audio)) { PhoneMod.Instance?.EnqueueLiveVoice(playerID.PlatformID, call, sequence, audio, host: true); } } catch (Exception) { } } } public sealed class WindyLiveVoiceClient : ModuleMessageHandler { public override ExpectedReceiverType ExpectedReceiver => (ExpectedReceiverType)2; protected override void OnHandleMessage(ReceivedMessage m) { if (NetworkInfo.IsHost || ((ReceivedMessage)(ref m)).IsServerHandled || ((ReceivedMessage)(ref m)).Sender != 0 || ((ReceivedMessage)(ref m)).Bytes.Length > 50000) { return; } try { PhoneVoicePacket phoneVoicePacket = ((ReceivedMessage)(ref m)).ReadData(); if (phoneVoicePacket.Read(out var call, out var speaker, out var sequence, out var audio)) { PhoneMod.Instance?.EnqueueLiveVoice(speaker, call, sequence, audio, host: false); } } catch (Exception) { } } } public static class PhoneLiveTransport { public static void Register() { if (ModuleMessageManager.GetHandlerByType(typeof(PhoneAccessMessage)) == null) { ModuleMessageManager.RegisterHandler(); } if (ModuleMessageManager.GetHandlerByType(typeof(WindyLiveMessage)) == null) { ModuleMessageManager.RegisterHandler(); } if (ModuleMessageManager.GetHandlerByType(typeof(WindyLiveVoiceHost)) == null) { ModuleMessageManager.RegisterHandler(); } if (ModuleMessageManager.GetHandlerByType(typeof(WindyLiveVoiceClient)) == null) { ModuleMessageManager.RegisterHandler(); } } public static void Access(PhoneAccessView v, byte target) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) MessageRelay.RelayModule(PhonePacket.From(v), new MessageRoute(target, (NetworkChannel)0)); } public static void State(WindyLiveView v, byte target) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) MessageRelay.RelayModule(PhonePacket.From(v), new MessageRoute(target, (NetworkChannel)0)); } public static void Send(PhoneVoicePacket p) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) MessageRelay.RelayModule(p, new MessageRoute((RelayType)1, (NetworkChannel)1)); } public static void Voice(PhoneVoicePacket p, byte target) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) MessageRelay.RelayModule(p, new MessageRoute(target, (NetworkChannel)1)); } } public sealed class PhoneMedia : IDisposable { private readonly PhoneMod mod; private string localLive = ""; private uint liveFrame; private uint receivedLive; private float liveAt; private float nextFetch; private readonly Dictionary thumbnails = new Dictionary(); private readonly Dictionary fetchAttempts = new Dictionary(); private readonly Queue fetchQueue = new Queue(); private readonly HashSet wanted = new HashSet(); private readonly Queue<(string Id, ulong Owner)> captured = new Queue<(string, ulong)>(); private float nextUpload; private string galleryPending = ""; private float galleryRetry; private ulong galleryOwner; private ulong uploadOwner; private ushort? uploadDevice; private string uploadAccess = ""; private readonly string folder; private PhoneMediaStore hostStore; private readonly PhoneMediaAssembler assembler = new PhoneMediaAssembler(); private readonly ConcurrentQueue<(ulong Actor, PhoneMediaChunk Chunk, bool Host)> incoming = new ConcurrentQueue<(ulong, PhoneMediaChunk, bool)>(); private readonly Queue<(byte? Target, PhoneMediaChunk Chunk)> reliable = new Queue<(byte?, PhoneMediaChunk)>(); private readonly Queue<(byte? Target, PhoneMediaChunk Chunk)> video = new Queue<(byte?, PhoneMediaChunk)>(); private readonly Dictionary budgets = new Dictionary(); private readonly Dictionary frames = new Dictionary(); private readonly Dictionary fetches = new Dictionary(); private float nextSend; private float nextCapture; private float nextPreview; private float remoteAt; private float uploadDeadline; private float nextCleanup; private bool photoRequested; private readonly Dictionary uploads = new Dictionary(); private uint sendFrame; private uint receivedFrame; private string currentCall = ""; private string remoteCall = ""; private string uploadId = ""; private string uploadNumber = ""; private string uploadText = ""; private string uploadOperation = "sms"; private string pendingPhoto = ""; public Texture2D RemoteLive { get; private set; } public PhoneMediaStore Photos { get; } public PhoneCamera Camera { get; private set; } public Texture2D RemoteVideo { get; private set; } public Texture2D OpenPhoto { get; private set; } public bool PreviewPage { get; set; } public bool PhotoPage { get; set; } public bool FrontCamera { get; set; } public bool UploadBusy => uploadId != ""; public PhoneMedia(PhoneMod mod, string folder) { this.mod = mod; this.folder = folder; Photos = new PhoneMediaStore(Path.Combine(folder, "photos"), 800); Camera = new PhoneCamera(mod.MediaWarning); } public void BeginHost() { hostStore = new PhoneMediaStore(Path.Combine(folder, "server-media")); } public bool Owns(ulong actor, string id) { return hostStore != null && hostStore.Owns(actor, id); } public void Enqueue(ulong actor, PhoneMediaChunk c, bool host) { if (incoming.Count < (PhoneMediaRules.Stream(c.Kind) ? 256 : 8192)) { incoming.Enqueue((actor, c, host)); } } public void Tick(PhoneSurface screen) { PhoneShutter.Poll(screen); float realtimeSinceStartup = Time.realtimeSinceStartup; int num = 64; (ulong, PhoneMediaChunk, bool) result; while (num-- > 0 && incoming.TryDequeue(out result)) { try { if (result.Item3 && NetworkInfo.IsHost) { ReceiveHost(result.Item1, result.Item2, realtimeSinceStartup); } else if (!result.Item3) { ReceiveLocal(result.Item2, realtimeSinceStartup); } } catch (Exception ex) { if (result.Item3) { if (result.Item2.Kind == "upload" || result.Item2.Kind == "voiceUpload") { mod.MediaUploadFailed(result.Item1, result.Item2.Id, ex.Message); } else { mod.MediaStatus(result.Item1, ex.Message); } } else { mod.LocalStatus(ex.Message); } } } assembler.Expire(realtimeSinceStartup); if (mod.ScreenUnlocked && !UploadBusy && captured.Count == 0) { string text = Photos.Captured(mod.ScreenOwner).FirstOrDefault((string id) => !mod.Access.Gallery.Contains(id) && !mod.Access.Deleted.Contains(id) && id != galleryPending); if (text != null) { captured.Enqueue((text, mod.ScreenOwner)); } } if (galleryPending != "" && mod.ScreenUnlocked && galleryOwner == mod.ScreenOwner) { if (mod.Access.Gallery.Contains(galleryPending)) { galleryPending = ""; } else if (realtimeSinceStartup >= galleryRetry) { galleryRetry = realtimeSinceStartup + 2f; mod.Request("galleryAdd", "", "", "", "", enabled: false, 0L, galleryPending); } } if (mod.ScreenUnlocked || mod.VisibleWallpaper != "") { KeyValuePair[] array = fetchAttempts.ToArray(); for (int num2 = 0; num2 < array.Length; num2++) { KeyValuePair keyValuePair = array[num2]; if (realtimeSinceStartup - keyValuePair.Value.Item1 > 4f && keyValuePair.Value.Item2 < 3) { fetchAttempts[keyValuePair.Key] = (realtimeSinceStartup, keyValuePair.Value.Item2); if (!fetchQueue.Contains(keyValuePair.Key)) { fetchQueue.Enqueue(keyValuePair.Key); } } } if (realtimeSinceStartup >= nextFetch && fetchQueue.Count > 0) { nextFetch = realtimeSinceStartup + 1.1f; string text2 = fetchQueue.Dequeue(); fetchAttempts.TryGetValue(text2, out (float, int) value); fetchAttempts[text2] = (realtimeSinceStartup, value.Item2 + 1); if (mod.ScreenUnlocked) { mod.Request("photoFetch", "", "", "", "", enabled: false, 0L, text2); } else if (text2 == mod.VisibleWallpaper) { mod.Request("wallpaperFetch", "", "", "", "", enabled: false, 0L, text2); } } } if (mod.ScreenUnlocked && !UploadBusy && captured.Count > 0 && realtimeSinceStartup >= nextUpload) { (string, ulong) tuple = captured.Dequeue(); if (tuple.Item2 == mod.ScreenOwner) { nextUpload = realtimeSinceStartup + 2.1f; StartUpload(tuple.Item1, "", "", "galleryAdd"); } } string text3 = mod.Live.Active?.Id ?? ""; if (text3 != localLive) { localLive = text3; liveFrame = (receivedLive = 0u); ClearLive(); } if (!mod.IsWatching || realtimeSinceStartup - liveAt > 3f) { ClearLive(); } if (NetworkInfo.IsHost && hostStore != null && realtimeSinceStartup >= nextCleanup) { nextCleanup = realtimeSinceStartup + 60f; hostStore.Prune(mod.PhotoReferenced, DateTime.UtcNow.AddMinutes(-15.0)); } if (uploadId != "" && realtimeSinceStartup >= uploadDeadline) { uploadId = ""; mod.LocalStatus("Transfert photo expiré, réessayer."); } if (realtimeSinceStartup >= nextSend) { nextSend = realtimeSinceStartup + 0.025f; for (int num3 = 0; num3 < 20; num3++) { Queue<(byte?, PhoneMediaChunk)> queue = ((num3 < 4 && video.Count > 0) ? video : ((reliable.Count > 0) ? reliable : video)); if (queue.Count == 0) { break; } (byte?, PhoneMediaChunk) tuple2 = queue.Dequeue(); PhoneMediaChunk item = tuple2.Item2; if (item.Session != mod.View.Session || (item.Kind == "live" && !tuple2.Item1.HasValue && (!mod.IsCasting || item.Call != mod.Live.Active.Id)) || (item.Kind == "video" && !tuple2.Item1.HasValue && (!mod.CallActive || !mod.View.Call.Video || item.Call != mod.View.Call.Id))) { continue; } if (tuple2.Item1.HasValue) { PlayerID playerID = PlayerIDManager.GetPlayerID(tuple2.Item1.Value); if (playerID != null && playerID.IsValid && (!(item.Kind == "live") || mod.LiveRouteValid(item.Call, playerID.PlatformID)) && (!(item.Kind == "video") || mod.MediaRouteValid(item.Call, playerID.PlatformID)) && (!(item.Kind == "download") || mod.CanReadPhoto(playerID.PlatformID, item.Id)) && (!(item.Kind == "voiceDownload") || mod.CanReadVoice(playerID.PlatformID, item.Id))) { if (playerID.IsMe) { Enqueue(0uL, item, host: false); } else { PhoneMediaTransport.Reply(item, playerID.SmallID); } } } else if (NetworkInfo.IsHost) { Enqueue(PlayerIDManager.LocalPlatformID, item, host: true); } else { PhoneMediaTransport.Send(item); } } } PhoneCallView call = mod.View.Call; if (currentCall != call.Id) { currentCall = call.Id; sendFrame = (receivedFrame = 0u); ClearRemote(); } if (!mod.CallActive || !call.PeerVideo || realtimeSinceStartup - remoteAt > 3f) { ClearRemote(); } if (photoRequested && !Camera.Busy) { photoRequested = false; ulong photoOwner = mod.ScreenOwner; string photoToken = mod.Access.Token; if (!Camera.Capture(screen, large: true, FrontCamera, encode: true, delegate(byte[] bytes) { string text4 = Guid.NewGuid().ToString("N"); Photos.Save(text4, photoOwner, bytes); Photos.MarkCaptured(text4, photoOwner); if (mod.ScreenUnlocked && mod.ScreenOwner == photoOwner && mod.Access.Token == photoToken) { captured.Enqueue((text4, photoOwner)); mod.LocalStatus("Photo prise • enregistrement dans la Galerie…"); } else { mod.LocalStatus("Photo conservée localement ; synchronisation au prochain déverrouillage."); } mod.RefreshApp(); })) { mod.LocalStatus("Tenir le téléphone pour prendre la photo."); } nextCapture = realtimeSinceStartup + 0.03f; } bool flag = mod.ScreenUnlocked && mod.CallActive && call.Video; if (screen == null || (!screen.IsHeld && !mod.IsCasting) || Camera.Busy) { return; } bool firstPreview = (Object)(object)Camera.Preview == (Object)null; if (mod.IsCasting && realtimeSinceStartup >= nextCapture) { nextCapture = realtimeSinceStartup + 1f / Math.Min(mod.Sounds.Preferences.VideoFps, Math.Min(15f, 60f / (float)Math.Max(1, mod.Live.Active.Viewers))); string captureLive = mod.Live.Active.Id; bool firstLive = (Object)(object)Camera.Live == (Object)null; Camera.Capture(screen, large: false, mod.Live.Active.Front, encode: true, delegate(byte[] bytes) { if (bytes != null && mod.IsCasting && mod.Live.Active.Id == captureLive) { Queue(null, PhoneMediaRules.Split(mod.View.Session, Guid.NewGuid().ToString("N"), "live", bytes, captureLive, ++liveFrame)); } if (firstLive) { mod.RefreshApp(); } }, photoPreview: false, allowUnheld: true); } else if (flag && realtimeSinceStartup >= nextCapture) { nextCapture = realtimeSinceStartup + 1f / (float)mod.Sounds.Preferences.VideoFps; string captureCall = call.Id; Camera.Capture(screen, large: false, front: true, encode: true, delegate(byte[] bytes) { if (bytes != null && mod.CallActive && mod.View.Call.Video && mod.View.Call.Id == captureCall) { uint frame = ++sendFrame; Queue(null, PhoneMediaRules.Split(mod.View.Session, Guid.NewGuid().ToString("N"), "video", bytes, captureCall, frame)); } if (firstPreview) { mod.RefreshApp(); } }); } else { if ((!PhotoPage && !PreviewPage) || !(realtimeSinceStartup >= nextPreview)) { return; } nextPreview = realtimeSinceStartup + 1f / (float)mod.Sounds.Preferences.PhotoFps; Camera.Capture(screen, large: false, FrontCamera, encode: false, delegate { if (firstPreview) { mod.RefreshApp(); } }, photoPreview: true); } } public void QueueVoice(byte? target, PhoneMediaChunk[] chunks) { Queue(target, chunks); } private void Queue(byte? target, PhoneMediaChunk[] chunks) { Queue<(byte?, PhoneMediaChunk)> queue = (PhoneMediaRules.Stream(chunks[0].Kind) ? video : reliable); if (queue.Count + chunks.Length > (PhoneMediaRules.Stream(chunks[0].Kind) ? 256 : (PhoneMediaRules.Voice(chunks[0].Kind) ? 4096 : 512))) { if (!PhoneMediaRules.Stream(chunks[0].Kind)) { throw new InvalidOperationException("Transfert occupé, réessayer."); } return; } foreach (PhoneMediaChunk phoneMediaChunk in chunks) { if (!target.HasValue && (phoneMediaChunk.Kind == "upload" || phoneMediaChunk.Kind == "voiceUpload")) { phoneMediaChunk.Owner = mod.ScreenOwner; phoneMediaChunk.Device = mod.ScreenDevice; phoneMediaChunk.Access = mod.Access.Token; } queue.Enqueue((target, phoneMediaChunk)); } } private void ForgetGalleryPhoto(string id) { Photos.UnmarkCaptured(id); if (galleryPending == id) { galleryPending = ""; } (string, ulong)[] array = captured.Where(((string Id, ulong Owner) c) => c.Id != id).ToArray(); captured.Clear(); (string, ulong)[] array2 = array; foreach ((string, ulong) item in array2) { captured.Enqueue(item); } if (uploadOperation == "galleryAdd" && uploadId == id) { uploadId = ""; } } public void DeleteGalleryPhoto(string id) { ForgetGalleryPhoto(id); Photos.Delete(id); mod.Request("galleryDelete", "", "", "", "", enabled: false, 0L, id); } public void CapturePhoto(PhoneSurface screen) { if (!mod.ScreenUnlocked || screen == null || !screen.IsHeld || !screen.PlacementReady) { mod.LocalStatus("Tenir le téléphone et attendre l'écran."); return; } if (mod.Gallery.Count >= 100) { mod.LocalStatus("Galerie pleine (100 photos). Supprimer une photo."); return; } photoRequested = true; mod.LocalStatus("Prise de photo…"); } public void SendPhoto(string id, string number, string text) { StartUpload(id, PhoneService.Normalize(number), text, "sms"); } public void SendWindyPhoto(string id, string text) { StartUpload(id, "", text, "windyPost"); } private void StartUpload(string id, string number, string text, string operation) { if (!mod.ScreenUnlocked) { throw new InvalidOperationException("Déverrouiller le téléphone."); } if (UploadBusy) { throw new InvalidOperationException("Une photo est déjà en cours d'envoi."); } byte[] array = Photos.Read(id); if (!PhoneMediaRules.Jpeg(array)) { throw new InvalidOperationException("Photo invalide."); } string id2 = ((operation == "galleryAdd") ? id : Guid.NewGuid().ToString("N")); Photos.Save(id2, mod.ScreenOwner, array); Queue(null, PhoneMediaRules.Split(mod.View.Session, id2, "upload", array)); uploadOwner = mod.ScreenOwner; uploadDevice = mod.ScreenDevice; uploadAccess = mod.Access.Token; uploadId = id2; uploadNumber = number; uploadText = text; uploadOperation = operation; uploadDeadline = Time.realtimeSinceStartup + 25f; mod.LocalStatus("Envoi de la photo…"); } public bool HandleNotice(PhoneNotice n) { if (n.Kind == "galleryRemoved") { if (Guid.TryParseExact(n.Number, "N", out var _)) { ForgetGalleryPhoto(n.Number); mod.RefreshApp(); } return true; } if (n.Kind == "uploadFailed") { if (n.Number == uploadId) { uploadId = ""; mod.LocalStatus(n.Text); } return true; } if (n.Kind != "uploadReady") { return false; } if (n.Number == uploadId) { string photo = uploadId; uploadId = ""; if (mod.ScreenUnlocked && mod.ScreenOwner == uploadOwner && mod.ScreenDevice == uploadDevice && mod.Access.Token == uploadAccess) { if (uploadOperation == "galleryAdd") { galleryPending = photo; galleryOwner = uploadOwner; galleryRetry = Time.realtimeSinceStartup + 2f; } mod.Request(uploadOperation, uploadNumber, "", uploadText, "", enabled: false, 0L, photo); } } return true; } public Texture2D PictureFor(string id) { if (!mod.IsPhotoKnown(id)) { return null; } if (thumbnails.TryGetValue(id, out var value) && (Object)(object)value != (Object)null) { return value; } try { byte[] bytes = Photos.Read(id); return DecodePhoto(id, bytes); } catch (IOException) { } catch (InvalidOperationException) { } if (wanted.Add(id)) { fetchQueue.Enqueue(id); } return null; } public void ViewPhoto(string id) { pendingPhoto = id; OpenPhoto = PictureFor(id); } private Texture2D DecodePhoto(string id, byte[] bytes) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown if (!mod.IsPhotoKnown(id) || !PhoneMediaRules.Jpeg(bytes)) { throw new InvalidOperationException("Photo reçue invalide."); } Texture2D val = new Texture2D(2, 2, (TextureFormat)3, false); try { if (!ImageConversion.LoadImage(val, Il2CppStructArray.op_Implicit(bytes), false)) { throw new InvalidOperationException("Photo illisible."); } } catch { Object.Destroy((Object)(object)val); throw; } if (thumbnails.TryGetValue(id, out var value) && (Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } if (thumbnails.Count >= 18) { string key = thumbnails.Keys.First(); Object.Destroy((Object)(object)thumbnails[key]); thumbnails.Remove(key); } thumbnails[id] = val; wanted.Remove(id); fetchAttempts.Remove(id); if (pendingPhoto == id) { OpenPhoto = val; } mod.RefreshApp(); return val; } public void Fetch(ulong actor, string id) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (fetches.TryGetValue(actor, out var value) && realtimeSinceStartup - value < 1f) { throw new InvalidOperationException("Attendre avant de recharger une photo."); } if (hostStore == null || !mod.CanReadPhoto(actor, id)) { throw new InvalidOperationException("Accès à cette photo refusé."); } fetches[actor] = realtimeSinceStartup; PlayerID playerID = PlayerIDManager.GetPlayerID(actor); if (playerID != null && playerID.IsValid) { Queue(playerID.SmallID, PhoneMediaRules.Split(mod.ServerSession, id, "download", hostStore.Read(id))); } } private void ReceiveHost(ulong actor, PhoneMediaChunk c, float now) { if (c.Session != mod.ServerSession || c.Kind == "download" || c.Kind == "voiceDownload" || hostStore == null) { return; } PlayerID playerID = PlayerIDManager.GetPlayerID(actor); if (playerID == null || !playerID.IsValid) { return; } budgets.TryGetValue(actor, out (float, int) value); if (now - value.Item1 >= 1f) { value = (now, 0); } if (++value.Item2 > 900) { return; } budgets[actor] = value; if (c.Kind == "voiceUpload") { mod.UploadOwner(actor, c); } ulong peer = 0uL; if ((c.Kind == "live" && mod.LiveTargets(actor, c.Call).Length == 0) || (c.Kind == "video" && !mod.MediaVideoRoute(actor, c.Call, out peer)) || (PhoneMediaRules.Stream(c.Kind) && frames.TryGetValue(actor, out (string, uint, float) value2) && value2.Item1 == c.Call && c.Frame <= value2.Item2)) { return; } byte[] array = assembler.Add(actor, c, now); if (array == null) { return; } if (c.Kind == "voiceUpload") { mod.Notes.SaveUpload(actor, c, array); return; } if (!PhoneMediaRules.Jpeg(array, PhoneMediaRules.Stream(c.Kind) ? 320 : 640, PhoneMediaRules.Stream(c.Kind) ? 240 : 480)) { throw new InvalidOperationException("Image réseau invalide."); } if (c.Kind == "upload") { ulong owner = mod.UploadOwner(actor, c); if (!hostStore.Owns(owner, c.Id)) { if (uploads.TryGetValue(actor, out var value3) && now - value3 < 2f) { throw new InvalidOperationException("Attendre deux secondes entre deux photos envoyées."); } if (hostStore.OwnedCount(owner) >= 200) { throw new InvalidOperationException("Quota serveur de photos atteint pour ce joueur."); } uploads[actor] = now; } hostStore.Save(c.Id, owner, array); mod.MediaUploadReady(playerID, c.Id); } else { if (frames.TryGetValue(actor, out (string, uint, float) value4) && value4.Item1 == c.Call && (c.Frame <= value4.Item2 || now - value4.Item3 < 0.035f)) { return; } frames[actor] = (c.Call, c.Frame, now); if (c.Kind == "live") { ulong[] array2 = mod.LiveTargets(actor, c.Call); foreach (ulong num in array2) { PlayerID playerID2 = PlayerIDManager.GetPlayerID(num); if (playerID2 != null && playerID2.IsValid) { Queue(playerID2.SmallID, PhoneMediaRules.Split(mod.ServerSession, c.Id, "live", array, c.Call, c.Frame)); } } } else { PlayerID playerID3 = PlayerIDManager.GetPlayerID(peer); if (playerID3 != null && playerID3.IsValid) { Queue(playerID3.SmallID, PhoneMediaRules.Split(mod.ServerSession, c.Id, "video", array, c.Call, c.Frame)); } } } } private void ReceiveLocal(PhoneMediaChunk c, float now) { //IL_035d: Unknown result type (might be due to invalid IL or missing references) //IL_0363: Expected O, but got Unknown //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Expected O, but got Unknown if (c.Session != mod.View.Session || c.Kind == "upload" || c.Kind == "voiceUpload" || (c.Kind == "voiceDownload" && !mod.IsVoiceKnown(c.Id)) || (c.Kind == "live" && (!mod.IsWatching || c.Call != mod.Live.Active.Id || c.Frame <= receivedLive)) || (c.Kind == "video" && (!mod.CallActive || !mod.View.Call.PeerVideo || c.Call != mod.View.Call.Id || c.Frame <= receivedFrame)) || (c.Kind == "download" && !mod.IsPhotoKnown(c.Id))) { return; } byte[] array = assembler.Add(0uL, c, now); if (array == null) { return; } if (c.Kind == "voiceDownload") { mod.Notes.Receive(c.Id, array); } else { if (!PhoneMediaRules.Jpeg(array, PhoneMediaRules.Stream(c.Kind) ? 320 : 640, PhoneMediaRules.Stream(c.Kind) ? 240 : 480)) { return; } if (c.Kind == "download") { Photos.Save(c.Id, 0uL, array); DecodePhoto(c.Id, array); } else if (c.Kind == "live") { if (c.Frame <= receivedLive) { return; } Texture2D val = RemoteLive; bool flag = (Object)(object)val == (Object)null; if (flag) { val = new Texture2D(2, 2, (TextureFormat)3, false); } if (!ImageConversion.LoadImage(val, Il2CppStructArray.op_Implicit(array), false)) { if (flag) { Object.Destroy((Object)(object)val); } return; } RemoteLive = val; receivedLive = c.Frame; liveAt = now; if (flag) { mod.RefreshApp(); } } else { if (c.Frame <= receivedFrame) { return; } Texture2D val2 = RemoteVideo; bool flag2 = (Object)(object)val2 == (Object)null; if (flag2) { val2 = new Texture2D(2, 2, (TextureFormat)3, false); } if (!ImageConversion.LoadImage(val2, Il2CppStructArray.op_Implicit(array), false)) { if (flag2) { Object.Destroy((Object)(object)val2); } return; } RemoteVideo = val2; receivedFrame = c.Frame; remoteAt = now; remoteCall = c.Call; if (flag2) { mod.RefreshApp(); } } } } private void ClearRemote() { if ((Object)(object)RemoteVideo != (Object)null) { Object.Destroy((Object)(object)RemoteVideo); RemoteVideo = null; mod.RefreshApp(); } remoteCall = ""; } private void ClearLive() { if ((Object)(object)RemoteLive != (Object)null) { Object.Destroy((Object)(object)RemoteLive); RemoteLive = null; mod.RefreshApp(); } } public void ClearPrivate() { foreach (Texture2D value in thumbnails.Values) { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } } thumbnails.Clear(); fetchQueue.Clear(); fetchAttempts.Clear(); wanted.Clear(); captured.Clear(); OpenPhoto = null; pendingPhoto = (uploadId = (galleryPending = "")); bool photoPage = (PreviewPage = false); PhotoPage = photoPage; photoRequested = false; (byte?, PhoneMediaChunk)[] array = reliable.Where(((byte? Target, PhoneMediaChunk Chunk) x) => x.Target.HasValue).ToArray(); reliable.Clear(); if (NetworkInfo.IsHost) { (byte?, PhoneMediaChunk)[] array2 = array; foreach ((byte?, PhoneMediaChunk) item in array2) { reliable.Enqueue(item); } } (ulong, PhoneMediaChunk, bool)[] array3 = incoming.Where(((ulong Actor, PhoneMediaChunk Chunk, bool Host) x) => x.Host).ToArray(); incoming.Clear(); if (NetworkInfo.IsHost) { (ulong, PhoneMediaChunk, bool)[] array4 = array3; foreach ((ulong, PhoneMediaChunk, bool) item2 in array4) { incoming.Enqueue(item2); } } ClearLive(); } public void Reset() { PhoneShutter.RestoreMenu(); ClearPrivate(); localLive = ""; liveFrame = (receivedLive = 0u); incoming.Clear(); reliable.Clear(); video.Clear(); assembler.Clear(); budgets.Clear(); frames.Clear(); fetches.Clear(); uploads.Clear(); photoRequested = false; nextCleanup = 0f; hostStore = null; uploadId = (pendingPhoto = (currentCall = "")); sendFrame = (receivedFrame = 0u); nextCapture = (nextPreview = (nextSend = 0f)); PhotoPage = false; ClearRemote(); OpenPhoto = null; Camera.Dispose(); Camera = new PhoneCamera(mod.MediaWarning); } public void Dispose() { Reset(); Camera.Dispose(); } } public sealed class PhoneMediaChunk { public int Protocol { get; set; } = 16; public string Session { get; set; } = ""; public string Id { get; set; } = ""; public string Call { get; set; } = ""; public ulong Owner { get; set; } public ushort? Device { get; set; } public string Access { get; set; } = ""; public string Kind { get; set; } = ""; public uint Frame { get; set; } public int Index { get; set; } public int Count { get; set; } public int Total { get; set; } public byte[] Bytes { get; set; } = Array.Empty(); } public static class PhoneMediaRules { public const int ChunkBytes = 600; public const int PhotoBytes = 65536; public const int VideoBytes = 16384; public static bool Voice(string kind) { return kind == "voiceUpload" || kind == "voiceDownload"; } public static bool Stream(string kind) { return kind == "video" || kind == "live"; } public static bool ValidChunk(PhoneMediaChunk c) { if (c == null || c.Protocol != 16 || !Guid.TryParseExact(c.Session, "N", out var result) || !Guid.TryParseExact(c.Id, "N", out result) || c.Bytes == null) { return false; } if (c.Kind != "upload" && c.Kind != "download" && c.Kind != "video" && c.Kind != "live" && !Voice(c.Kind)) { return false; } int num = (Voice(c.Kind) ? 1440016 : (Stream(c.Kind) ? 16384 : 65536)); if (Stream(c.Kind) && (!Guid.TryParseExact(c.Call, "N", out result) || c.Frame == 0)) { return false; } if (c.Total < 16 || c.Total > num || c.Count != (c.Total + 600 - 1) / 600 || c.Index < 0 || c.Index >= c.Count) { return false; } return c.Bytes.Length == Math.Min(600, c.Total - c.Index * 600); } public static PhoneMediaChunk[] Split(string session, string id, string kind, byte[] bytes, string call = "", uint frame = 0u) { if (bytes == null || bytes.Length < 16 || bytes.Length > (Voice(kind) ? 1440016 : (Stream(kind) ? 16384 : 65536))) { throw new InvalidOperationException("Image absente ou trop volumineuse."); } int num = (bytes.Length + 600 - 1) / 600; PhoneMediaChunk[] array = new PhoneMediaChunk[num]; for (int i = 0; i < num; i++) { PhoneMediaChunk phoneMediaChunk = new PhoneMediaChunk { Session = session, Id = id, Kind = kind, Call = call, Frame = frame, Index = i, Count = num, Total = bytes.Length, Bytes = bytes.AsSpan(i * 600, Math.Min(600, bytes.Length - i * 600)).ToArray() }; if (!ValidChunk(phoneMediaChunk)) { throw new InvalidOperationException("Image trop volumineuse ou enveloppe invalide."); } array[i] = phoneMediaChunk; } return array; } public static bool Jpeg(byte[] bytes, int maxWidth = 640, int maxHeight = 480) { if (bytes == null || bytes.Length < 16 || bytes.Length > 65536 || bytes[0] != byte.MaxValue || bytes[1] != 216 || bytes[^2] != byte.MaxValue || bytes[^1] != 217) { return false; } int i = 2; bool result = false; while (i + 1 < bytes.Length) { if (bytes[i++] != byte.MaxValue) { return false; } for (; i < bytes.Length && bytes[i] == byte.MaxValue; i++) { } if (i >= bytes.Length) { return false; } int num = bytes[i++]; switch (num) { case 218: return result; default: if (num != 0) { if (num >= 208 && num <= 215) { break; } if (i + 2 > bytes.Length) { return false; } int num2 = (bytes[i] << 8) | bytes[i + 1]; if (num2 < 2 || i + num2 > bytes.Length) { return false; } if (num >= 192 && num <= 207 && num != 192 && num != 194 && num != 196 && num != 200 && num != 204) { return false; } if (num == 192 || num == 194) { if (num2 < 8 || bytes[i + 2] != 8) { return false; } int num3 = (bytes[i + 3] << 8) | bytes[i + 4]; int num4 = (bytes[i + 5] << 8) | bytes[i + 6]; if (num4 < 1 || num3 < 1 || num4 > maxWidth || num3 > maxHeight) { return false; } result = true; } i += num2; break; } goto case 216; case 216: case 217: return false; } } return false; } } public sealed class PhoneMediaAssembler { private sealed class Pending { public PhoneMediaChunk First; public byte[][] Parts; public int Received; public double Started; } private readonly Dictionary pending = new Dictionary(); public void Clear() { pending.Clear(); } public void Expire(double now) { string[] array = (from p in pending where now - p.Value.Started > (double)(PhoneMediaRules.Stream(p.Value.First.Kind) ? 2 : 15) select p.Key).ToArray(); foreach (string key in array) { pending.Remove(key); } } public byte[] Add(ulong actor, PhoneMediaChunk c, double now) { Expire(now); if (!PhoneMediaRules.ValidChunk(c)) { return null; } string prefix = actor + "/"; string key = prefix + c.Session + "/" + c.Kind + "/" + c.Id; if (!this.pending.TryGetValue(key, out var value)) { if (PhoneMediaRules.Stream(c.Kind)) { KeyValuePair[] array = (from x in this.pending where x.Key.StartsWith(prefix, StringComparison.Ordinal) && PhoneMediaRules.Stream(x.Value.First.Kind) && x.Value.First.Call == c.Call orderby x.Value.First.Frame select x).ToArray(); if (array.Length >= 2) { if (c.Frame <= array[0].Value.First.Frame) { return null; } this.pending.Remove(array[0].Key); } } if (this.pending.Count >= 32 || this.pending.Keys.Count((string k) => k.StartsWith(prefix, StringComparison.Ordinal)) >= 3) { return null; } value = (this.pending[key] = new Pending { First = c, Parts = new byte[c.Count][], Started = now }); } PhoneMediaChunk first = value.First; if (c.Count != first.Count || c.Total != first.Total || c.Call != first.Call || c.Frame != first.Frame || c.Owner != first.Owner || c.Device != first.Device || c.Access != first.Access) { this.pending.Remove(key); return null; } if (value.Parts[c.Index] != null) { if (!value.Parts[c.Index].SequenceEqual(c.Bytes)) { this.pending.Remove(key); } return null; } value.Parts[c.Index] = (byte[])c.Bytes.Clone(); value.Received++; if (value.Received != c.Count) { return null; } byte[] array2 = new byte[c.Total]; for (int num = 0; num < c.Count; num++) { value.Parts[num].CopyTo(array2, num * 600); } this.pending.Remove(key); return array2; } } public sealed class PhoneMediaStore { private readonly string folder; private readonly int limit; private readonly Dictionary capturedCache = new Dictionary(); public PhoneMediaStore(string folder, int limit = 1000) { this.folder = folder; this.limit = limit; Directory.CreateDirectory(folder); } public string PathFor(string id) { if (!Guid.TryParseExact(id, "N", out var _)) { throw new InvalidOperationException("Identifiant photo invalide."); } return Path.Combine(folder, id + ".jpg"); } public bool Owns(ulong owner, string id) { string text = PathFor(id); return File.Exists(text) && File.Exists(text + ".owner") && File.ReadAllText(text + ".owner") == owner.ToString(CultureInfo.InvariantCulture); } public void Save(string id, ulong owner, byte[] data) { if (!PhoneMediaRules.Jpeg(data)) { throw new InvalidOperationException("JPEG invalide ou dimensions excessives."); } string text = PathFor(id); if (File.Exists(text)) { if (!Owns(owner, id) || !Read(id).SequenceEqual(data)) { throw new InvalidOperationException("Identifiant photo déjà utilisé."); } return; } if (Directory.EnumerateFiles(folder, "*.jpg").Take(limit).Count() >= limit) { throw new InvalidOperationException("Stockage photo plein."); } string text2 = text + ".tmp"; File.WriteAllBytes(text2, data); try { File.WriteAllText(text + ".owner", owner.ToString(CultureInfo.InvariantCulture)); File.Move(text2, text); capturedCache.Clear(); } catch { if (!File.Exists(text) && File.Exists(text + ".owner")) { File.Delete(text + ".owner"); } throw; } finally { if (File.Exists(text2)) { File.Delete(text2); } } } public byte[] Read(string id) { string text = PathFor(id); if (!File.Exists(text) || new FileInfo(text).Length > 65536) { throw new InvalidOperationException("Photo indisponible."); } return File.ReadAllBytes(text); } public string[] List() { Guid result; return (from id in Directory.EnumerateFiles(folder, "*.jpg").Select(Path.GetFileNameWithoutExtension) where Guid.TryParseExact(id, "N", out result) orderby File.GetLastWriteTimeUtc(PathFor(id)) descending select id).Take(limit).ToArray(); } public void Prune(Func referenced, DateTime olderThanUtc) { string[] array = List(); foreach (string text in array) { if (File.GetLastWriteTimeUtc(PathFor(text)) < olderThanUtc && !referenced(text)) { Delete(text); } } } public void MarkCaptured(string id, ulong owner) { if (!Owns(owner, id)) { throw new InvalidOperationException("Photo source absente."); } File.WriteAllText(PathFor(id) + ".captured", owner.ToString(CultureInfo.InvariantCulture)); capturedCache.Clear(); } public string[] Captured(ulong owner) { if (!capturedCache.TryGetValue(owner, out var value)) { value = (from id in List() where Owns(owner, id) && File.Exists(PathFor(id) + ".captured") && File.ReadAllText(PathFor(id) + ".captured") == owner.ToString(CultureInfo.InvariantCulture) select id).ToArray(); capturedCache[owner] = value; } return (string[])value.Clone(); } public void UnmarkCaptured(string id) { File.Delete(PathFor(id) + ".captured"); capturedCache.Clear(); } public int OwnedCount(ulong owner) { return List().Count((string id) => Owns(owner, id)); } public DateTime Date(string id) { return File.GetLastWriteTime(PathFor(id)); } public void Delete(string id) { string text = PathFor(id); File.Delete(text); File.Delete(text + ".owner"); File.Delete(text + ".captured"); capturedCache.Clear(); } } public sealed class PhoneMediaUploadMessage : ModuleMessageHandler { public override ExpectedReceiverType ExpectedReceiver => (ExpectedReceiverType)1; protected override void OnHandleMessage(ReceivedMessage message) { if (!NetworkInfo.IsHost || !((ReceivedMessage)(ref message)).IsServerHandled || !((ReceivedMessage)(ref message)).Sender.HasValue || ((ReceivedMessage)(ref message)).Bytes.Length > 4096) { return; } try { PlayerID playerID = PlayerIDManager.GetPlayerID(((ReceivedMessage)(ref message)).Sender.Value); if (playerID == null || !playerID.IsValid) { return; } byte[] payload = ((ReceivedMessage)(ref message)).ReadData().Payload; if (payload != null && payload.Length <= 3800) { PhoneMediaChunk c = JsonSerializer.Deserialize(payload); if (PhoneMediaRules.ValidChunk(c)) { PhoneMod.Instance?.Media?.Enqueue(playerID.PlatformID, c, host: true); } } } catch (Exception) { } } } public sealed class PhoneMediaDownloadMessage : ModuleMessageHandler { public override ExpectedReceiverType ExpectedReceiver => (ExpectedReceiverType)2; protected override void OnHandleMessage(ReceivedMessage message) { if (NetworkInfo.IsHost || ((ReceivedMessage)(ref message)).IsServerHandled || ((ReceivedMessage)(ref message)).Sender != 0 || ((ReceivedMessage)(ref message)).Bytes.Length > 4096) { return; } try { byte[] payload = ((ReceivedMessage)(ref message)).ReadData().Payload; if (payload != null && payload.Length <= 3800) { PhoneMediaChunk c = JsonSerializer.Deserialize(payload); if (PhoneMediaRules.ValidChunk(c)) { PhoneMod.Instance?.Media?.Enqueue(0uL, c, host: false); } } } catch (Exception) { } } } public static class PhoneMediaTransport { public static void Register() { if (ModuleMessageManager.GetHandlerByType(typeof(PhoneMediaUploadMessage)) == null) { ModuleMessageManager.RegisterHandler(); } if (ModuleMessageManager.GetHandlerByType(typeof(PhoneMediaDownloadMessage)) == null) { ModuleMessageManager.RegisterHandler(); } } public static void Send(PhoneMediaChunk c) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) MessageRelay.RelayModule(PhonePacket.From(c), (MessageRoute)(PhoneMediaRules.Stream(c.Kind) ? new MessageRoute((RelayType)1, (NetworkChannel)1) : CommonMessageRoutes.ReliableToServer)); } public static void Reply(PhoneMediaChunk c, byte id) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) MessageRelay.RelayModule(PhonePacket.From(c), new MessageRoute(id, (NetworkChannel)(PhoneMediaRules.Stream(c.Kind) ? 1 : 0))); } } public static class PhoneMicrophoneRules { public static int Available(int cursor, int position, int capacity) { if (capacity <= 0 || cursor < 0 || position < 0 || cursor >= capacity || position >= capacity) { return 0; } return (position >= cursor) ? (position - cursor) : (capacity - cursor + position); } public static short[] Mono(float[] raw, int channels) { if (raw == null || channels < 1 || channels > 8 || raw.Length % channels != 0) { throw new InvalidDataException("Micro invalide."); } short[] array = new short[raw.Length / channels]; for (int i = 0; i < array.Length; i++) { double num = 0.0; for (int j = 0; j < channels; j++) { float num2 = raw[i * channels + j]; if (float.IsFinite(num2)) { num += (double)Math.Clamp(num2, -1f, 1f); } } array[i] = (short)Math.Clamp(num / (double)channels * 32767.0, -32767.0, 32767.0); } return array; } public static float Peak(short[] pcm) { int num = 0; foreach (short value in pcm) { num = Math.Max(num, Math.Abs((int)value)); } return (float)num / 32768f; } public static byte[] Audible(byte[] bytes) { float[] array = PhoneVoiceNoteRules.Decode(bytes); float num = 0f; float[] array2 = array; foreach (float value in array2) { num = Math.Max(num, Math.Abs(value)); } if (num < 0.001f) { throw new InvalidOperationException("Aucune voix capturée. Vérifier le micro Fusion puis réenregistrer."); } float num2 = Math.Min(4f, 0.85f / num); short[] array3 = new short[array.Length]; for (int j = 0; j < array3.Length; j++) { array3[j] = (short)Math.Clamp(array[j] * num2 * 32767f, -32767f, 32767f); } int num3 = PhoneVoiceNoteRules.SampleRate(bytes); int num4 = num3 / 100; for (int k = 0; k < Math.Min(num4, array3.Length / 2); k++) { array3[k] = (short)((float)(array3[k] * k) / (float)num4); array3[array3.Length - 1 - k] = (short)((float)(array3[array3.Length - 1 - k] * k) / (float)num4); } return PhoneVoiceNoteRules.Pack(array3, num3); } } public sealed class PhoneContact { public string Number { get; set; } = ""; public string Name { get; set; } = ""; public string Photo { get; set; } = ""; } public sealed class PhoneSms { public string Id { get; set; } = ""; public string From { get; set; } = ""; public string To { get; set; } = ""; public string Text { get; set; } = ""; public string Photo { get; set; } = ""; public string Voice { get; set; } = ""; public int VoiceSeconds { get; set; } public DateTime Utc { get; set; } public bool Read { get; set; } } public sealed class PhoneBankEntry { public string Id { get; set; } = ""; public long Amount { get; set; } public string Label { get; set; } = ""; public DateTime Utc { get; set; } } public sealed class PhoneCallEntry { public string Id { get; set; } = ""; public string Number { get; set; } = ""; public string Outcome { get; set; } = ""; public bool Incoming { get; set; } public bool Masked { get; set; } public int Seconds { get; set; } public DateTime Utc { get; set; } } public sealed class PhoneAccount { public ulong Player { get; set; } public string Number { get; set; } = ""; public List Contacts { get; set; } = new List(); public List Messages { get; set; } = new List(); public List Calls { get; set; } = new List(); public List Transactions { get; set; } = new List(); public string Wallpaper { get; set; } = ""; public string BankPinSalt { get; set; } = ""; public string BankPinHash { get; set; } = ""; public string PinSalt { get; set; } = ""; public string PinHash { get; set; } = ""; public int PinDigits { get; set; } = 6; public List GalleryDeleted { get; set; } = new List(); public List Gallery { get; set; } = new List(); public List RecentCommands { get; set; } = new List(); } public sealed class PhoneDatabase { public int Version { get; set; } = 1; public long NextNumber { get; set; } = 1L; public bool RandomNumbers { get; set; } public List Accounts { get; set; } = new List(); } public sealed class PhoneCommand { public int Protocol { get; set; } = 16; public string Session { get; set; } = ""; public ulong Owner { get; set; } public ushort? Device { get; set; } public string Access { get; set; } = ""; public int Epoch { get; set; } public string Operation { get; set; } = "sync"; public string Nonce { get; set; } = ""; public string Call { get; set; } = ""; public bool VoiceReady { get; set; } public bool ListenReady { get; set; } public bool Enabled { get; set; } public string Voice { get; set; } = ""; public int VoiceSeconds { get; set; } public int Offset { get; set; } public long Amount { get; set; } public string Number { get; set; } = ""; public string Name { get; set; } = ""; public string Text { get; set; } = ""; public string Photo { get; set; } = ""; } public sealed class ScreenSettings { public float X { get; set; } public float Y { get; set; } public float Z { get; set; } public float RotationX { get; set; } public float RotationY { get; set; } public float RotationZ { get; set; } public float SizeMultiplier { get; set; } = 1f; public bool ReverseFace { get; set; } public void Validate() { if (!float.IsFinite(X) || !float.IsFinite(Y) || !float.IsFinite(Z) || !float.IsFinite(RotationX) || !float.IsFinite(RotationY) || !float.IsFinite(RotationZ) || !float.IsFinite(SizeMultiplier) || Math.Abs(X) > 5f || Math.Abs(Y) > 5f || Math.Abs(Z) > 5f || SizeMultiplier < 0.1f || SizeMultiplier > 5f) { throw new InvalidOperationException("Réglage écran invalide."); } } } public sealed class PhoneConfiguration { public int Version { get; set; } = 1; public string Barcode { get; set; } = "CharaWhy.Phone.Spawnable.Phone"; public bool AutoSpawn { get; set; } = true; public ScreenSettings Screen { get; set; } = new ScreenSettings(); public void Validate() { if (Version != 1 || string.IsNullOrWhiteSpace(Barcode) || Barcode.Length > 256 || Screen == null) { throw new InvalidOperationException("Configuration téléphone invalide."); } Screen.Validate(); } } public sealed class PhoneView { public int Protocol { get; set; } = 16; public string Session { get; set; } = ""; public ulong Player { get; set; } public List Devices { get; set; } = new List(); public long Balance { get; set; } public List Calls { get; set; } = new List(); public List Transactions { get; set; } = new List(); public bool BankReady { get; set; } public PhoneCallView Call { get; set; } = new PhoneCallView(); public long CallRevision { get; set; } public long Revision { get; set; } public string Number { get; set; } = ""; public List Contacts { get; set; } = new List(); public List Messages { get; set; } = new List(); public ushort? Device { get; set; } public string Scene { get; set; } = ""; public string DeviceStatus { get; set; } = "En attente du téléphone."; public PhoneConfiguration Configuration { get; set; } = new PhoneConfiguration(); public string Notice { get; set; } = ""; } public sealed class PhoneChange { public ulong? Recipient { get; set; } public string Notice { get; set; } = ""; public bool Duplicate { get; set; } } public sealed class PhoneNotice { public int Protocol { get; set; } = 16; public string Id { get; set; } = Guid.NewGuid().ToString("N"); public string Session { get; set; } = ""; public string Kind { get; set; } = ""; public string Name { get; set; } = ""; public string Number { get; set; } = ""; public string Text { get; set; } = ""; public ulong Player { get; set; } public ulong Owner { get; set; } } public sealed class PhoneNoticeMessage : ModuleMessageHandler { public override ExpectedReceiverType ExpectedReceiver => (ExpectedReceiverType)2; protected override void OnHandleMessage(ReceivedMessage message) { if (NetworkInfo.IsHost || ((ReceivedMessage)(ref message)).IsServerHandled || ((ReceivedMessage)(ref message)).Sender != 0 || ((ReceivedMessage)(ref message)).Bytes.Length > 4096) { return; } try { byte[] payload = ((ReceivedMessage)(ref message)).ReadData().Payload; if (payload.Length <= 4000) { PhoneNotice phoneNotice = JsonSerializer.Deserialize(payload); if (phoneNotice != null) { PhoneMod.Instance?.EnqueueNotice(phoneNotice); } } } catch (Exception) { } } } public sealed class PhoneSpeakerPacket : INetSerializable { public byte[] Payload = Array.Empty(); public int? GetSize() { return 4 + Payload.Length; } public void Serialize(INetSerializer serializer) { serializer.SerializeValue(ref Payload); } public static PhoneSpeakerPacket Create(string token, ushort device, PhoneVoicePacket frame) { byte[] array = new byte[18 + frame.Payload.Length]; new Guid(token).ToByteArray().CopyTo(array, 0); BinaryPrimitives.WriteUInt16LittleEndian(array.AsSpan(16, 2), device); frame.Payload.CopyTo(array, 18); return new PhoneSpeakerPacket { Payload = array }; } public bool Read(out string token, out ushort device, out string call, out ulong actor, out uint seq, out byte[] audio) { token = (call = ""); device = 0; actor = 0uL; seq = 0u; audio = null; if (Payload == null || Payload.Length < 47 || Payload.Length > 49200) { return false; } token = new Guid(Payload.AsSpan(0, 16)).ToString("N"); device = BinaryPrimitives.ReadUInt16LittleEndian(Payload.AsSpan(16, 2)); return new PhoneVoicePacket { Payload = Payload.AsSpan(18).ToArray() }.Read(out call, out actor, out seq, out audio); } } public sealed class PhoneSpeakerMessage : ModuleMessageHandler { public override ExpectedReceiverType ExpectedReceiver => (ExpectedReceiverType)2; protected override void OnHandleMessage(ReceivedMessage message) { if (NetworkInfo.IsHost || ((ReceivedMessage)(ref message)).IsServerHandled || ((ReceivedMessage)(ref message)).Sender != 0 || ((ReceivedMessage)(ref message)).Bytes.Length > 50000) { return; } try { PhoneSpeakerPacket phoneSpeakerPacket = ((ReceivedMessage)(ref message)).ReadData(); if (phoneSpeakerPacket.Read(out var _, out var _, out var _, out var _, out var _, out var _)) { PhoneMod.Instance?.EnqueueSpeaker(phoneSpeakerPacket); } } catch (Exception) { } } } public static class PhoneNearbyTransport { public static void Register() { if (ModuleMessageManager.GetHandlerByType(typeof(PhoneNoticeMessage)) == null) { ModuleMessageManager.RegisterHandler(); } if (ModuleMessageManager.GetHandlerByType(typeof(PhoneSpeakerMessage)) == null) { ModuleMessageManager.RegisterHandler(); } } public static void Notice(PhoneNotice p, byte target) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) MessageRelay.RelayModule(PhonePacket.From(p), new MessageRoute(target, (NetworkChannel)0)); } public static void Speaker(PhoneSpeakerPacket p, byte target) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) MessageRelay.RelayModule(p, new MessageRoute(target, (NetworkChannel)1)); } } public sealed class PhoneParking { private sealed class State { public GameObject Body; public Rigidbody[] Bodies; public bool[] OriginalGravity; public long Revision; public bool Suspended; public bool Released; } private readonly Dictionary states = new Dictionary(); private static bool Holds(Hand hand, GameObject body) { GameObject val = ((hand != null) ? hand.m_CurrentAttachedGO : null); return (Object)(object)val != (Object)null && ((Object)(object)val == (Object)(object)body || val.transform.IsChildOf(body.transform) || body.transform.IsChildOf(val.transform)); } private static bool AnyoneHolds(GameObject body) { if (Holds(Player.LeftHand, body) || Holds(Player.RightHand, body)) { return true; } NetworkPlayer val = default(NetworkPlayer); foreach (PlayerID playerID in PlayerIDManager.PlayerIDs) { if (playerID != null && playerID.IsValid && NetworkPlayerManager.TryGetPlayer(playerID.SmallID, ref val) && val.HasRig && (Holds(val.RigRefs.LeftHand, body) || Holds(val.RigRefs.RightHand, body))) { return true; } } return false; } private static void Release(State s) { if (!s.Suspended) { return; } for (int i = 0; i < s.Bodies.Length; i++) { if ((Object)(object)s.Bodies[i] != (Object)null) { s.Bodies[i].useGravity = s.OriginalGravity[i]; } } s.Suspended = false; } public void Hold(PhoneDeviceInfo d, GameObject supplied = null) { //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) if (!states.TryGetValue(d.Device, out var value)) { if (!d.Parked) { return; } GameObject val = supplied ?? PhoneDevices.Resolve(d.Device); if ((Object)(object)val == (Object)null) { return; } Il2CppArrayBase componentsInChildren = val.GetComponentsInChildren(); value = (states[d.Device] = new State { Body = val, Bodies = Il2CppArrayBase.op_Implicit(componentsInChildren), OriginalGravity = ((IEnumerable)componentsInChildren).Select((Rigidbody rb) => rb.useGravity).ToArray(), Revision = d.Placement }); } if ((Object)(object)value.Body == (Object)null) { states.Remove(d.Device); return; } if (value.Revision != d.Placement) { Release(value); value.Revision = d.Placement; value.Released = false; value.OriginalGravity = value.Bodies.Select((Rigidbody rb) => (Object)(object)rb != (Object)null && rb.useGravity).ToArray(); } if (!d.Parked || AnyoneHolds(value.Body)) { Release(value); value.Released = true; } else { if (value.Released) { return; } if (value.Suspended) { Rigidbody[] bodies = value.Bodies; foreach (Rigidbody val2 in bodies) { if ((Object)(object)val2 != (Object)null) { val2.velocity = Vector3.zero; val2.angularVelocity = Vector3.zero; } } return; } Rigidbody[] bodies2 = value.Bodies; foreach (Rigidbody val3 in bodies2) { if ((Object)(object)val3 != (Object)null) { val3.velocity = Vector3.zero; val3.angularVelocity = Vector3.zero; val3.useGravity = false; } } value.Suspended = true; } } public void Tick(IEnumerable catalog) { PhoneDeviceInfo[] array = catalog.ToArray(); HashSet active = array.Select((PhoneDeviceInfo phoneDeviceInfo) => phoneDeviceInfo.Device).ToHashSet(); ushort[] array2 = states.Keys.Where((ushort id) => !active.Contains(id)).ToArray(); foreach (ushort key in array2) { Release(states[key]); states.Remove(key); } PhoneDeviceInfo[] array3 = array; foreach (PhoneDeviceInfo d in array3) { Hold(d); } } public void Clear() { foreach (State value in states.Values) { Release(value); } states.Clear(); } } public sealed class PhonePcmBuffer { private readonly object gate = new object(); private readonly float[] samples; private readonly int warmup; private readonly int fade; private readonly int maximum; private int read; private int count; private bool started; private bool disposed; private float previous; private float gain; public int Count { get { lock (gate) { return count; } } } public PhonePcmBuffer(int rate) { if (rate < 8000 || rate > 192000) { throw new ArgumentOutOfRangeException("rate"); } samples = new float[rate]; warmup = rate * 120 / 1000; fade = Math.Max(16, rate / 200); maximum = rate / 2; } public void Push(short[] pcm, float volume) { if (pcm == null || pcm.Length == 0) { return; } volume = ((float.IsNaN(volume) || float.IsInfinity(volume)) ? 0f : Math.Max(0f, Math.Min(1f, volume))); lock (gate) { if (!disposed) { int num = Math.Min(pcm.Length, maximum); if (count + num > maximum) { int num2 = count + num - maximum; read = (read + num2) % samples.Length; count -= num2; started = false; gain = 0f; } for (int i = pcm.Length - num; i < pcm.Length; i++) { samples[(read + count) % samples.Length] = PhoneAudioBuffer.Limit((float)pcm[i] / 32768f * volume); count++; } } } } public void Read(float[] output) { lock (gate) { for (int i = 0; i < output.Length; i++) { if (!started && count >= warmup) { started = true; gain = 0f; } if (disposed || !started || count == 0) { previous *= 0.96f; if (Math.Abs(previous) < 1E-05f) { previous = 0f; } output[i] = previous; if (started && count == 0) { started = false; gain = 0f; } } else { float num = samples[read]; read = (read + 1) % samples.Length; count--; gain = Math.Min(1f, gain + 1f / (float)fade); previous = num * gain; output[i] = previous; } } } } public void Dispose() { lock (gate) { disposed = true; count = 0; started = false; previous = (gain = 0f); } } } public sealed class PhonePreferences { public string Theme { get; set; } = "light"; public string Language { get; set; } = "fr"; public int VideoFps { get; set; } = 15; public int PhotoFps { get; set; } = 30; public float RingVolume { get; set; } = 1f; public int CallTone { get; set; } public int MessageTone { get; set; } public PhonePreferences Copy() { return new PhonePreferences { Theme = Theme, Language = Language, CallTone = CallTone, MessageTone = MessageTone, RingVolume = RingVolume, VideoFps = VideoFps, PhotoFps = PhotoFps }; } public void Validate() { if ((Theme != "light" && Theme != "dark") || !PhoneLanguage.Valid(Language) || (VideoFps != 8 && VideoFps != 15 && VideoFps != 24) || (PhotoFps != 15 && PhotoFps != 30 && PhotoFps != 45) || !float.IsFinite(RingVolume) || RingVolume < 0f || RingVolume > 1f || CallTone < 0 || CallTone > 3 || MessageTone < 0 || MessageTone > 3) { throw new InvalidOperationException("Sonnerie invalide."); } } } public static class PhoneProximity { public static bool Within(float ax, float ay, float az, float bx, float by, float bz, float radius) { if (!float.IsFinite(ax) || !float.IsFinite(ay) || !float.IsFinite(az) || !float.IsFinite(bx) || !float.IsFinite(by) || !float.IsFinite(bz) || !float.IsFinite(radius) || radius <= 0f) { return false; } double num = (double)ax - (double)bx; double num2 = (double)ay - (double)by; double num3 = (double)az - (double)bz; return num * num + num2 * num2 + num3 * num3 <= (double)radius * (double)radius; } } public sealed class PhoneService { public const int MaxContacts = 100; public const int MaxMessages = 100; public const int MaxText = 320; private const int MaxAccounts = 2048; private PhoneDatabase db; private Dictionary accounts; private Dictionary numbers; private readonly string file; public int Count => accounts.Count; public PhoneService(string file) { this.file = file; if (File.Exists(file) && new FileInfo(file).Length > 16777216) { throw new InvalidDataException("Base téléphone trop volumineuse."); } db = (File.Exists(file) ? JsonSerializer.Deserialize(File.ReadAllText(file)) : new PhoneDatabase()); Validate(); Index(); if (!db.RandomNumbers || db.Accounts.Any((PhoneAccount a) => a.PinHash != "" && a.PinDigits != 4)) { Migrate(); } } private static string RandomNumber(HashSet occupied) { string text; do { text = "06" + RandomNumberGenerator.GetInt32(10000000, 100000000).ToString("D8", CultureInfo.InvariantCulture); } while (occupied.Contains(text)); occupied.Add(text); return text; } private void Migrate() { Commit(delegate { if (!db.RandomNumbers) { HashSet occupied = new HashSet(db.Accounts.Select((PhoneAccount a) => a.Number)); Dictionary dictionary = db.Accounts.ToDictionary((PhoneAccount a) => a.Number, (PhoneAccount a) => RandomNumber(occupied)); foreach (PhoneAccount account in db.Accounts) { account.Number = dictionary[account.Number]; foreach (PhoneCallEntry call in account.Calls) { if (dictionary.TryGetValue(call.Number, out var value)) { call.Number = value; } } foreach (PhoneContact contact in account.Contacts) { if (dictionary.TryGetValue(contact.Number, out var value2)) { contact.Number = value2; } } foreach (PhoneSms message in account.Messages) { if (dictionary.TryGetValue(message.From, out var value3)) { message.From = value3; } if (dictionary.TryGetValue(message.To, out var value4)) { message.To = value4; } } foreach (PhoneBankEntry transaction in account.Transactions) { foreach (KeyValuePair item in dictionary) { if (transaction.Label == "De " + Display(item.Key)) { transaction.Label = "De " + Display(item.Value); } else if (transaction.Label == "Vers " + Display(item.Key)) { transaction.Label = "Vers " + Display(item.Value); } } } } db.RandomNumbers = true; } foreach (PhoneAccount account2 in db.Accounts) { if (account2.PinDigits != 4) { string pinSalt = (account2.PinHash = ""); account2.PinSalt = pinSalt; account2.PinDigits = 4; } } }); } public static string Normalize(string number) { if (number == null || number.Length > 40) { throw new InvalidOperationException("Numéro invalide."); } string text = string.Concat(number.Where((char c) => c != ' ' && c != '-' && c != '.')); if (text.Length != 10 || text.Any((char c) => c < '0' || c > '9')) { throw new InvalidOperationException("Saisir un numéro RP de 10 chiffres."); } return text; } public static string Display(string number) { return (number != null && number.Length == 10) ? string.Join(" ", from i in Enumerable.Range(0, 5) select number.Substring(i * 2, 2)) : (number ?? ""); } private void Index() { accounts = db.Accounts.ToDictionary((PhoneAccount a) => a.Player); numbers = db.Accounts.ToDictionary((PhoneAccount a) => a.Number, StringComparer.Ordinal); } private void Validate() { if (db == null || db.Version != 1 || db.NextNumber < 1 || db.NextNumber > 100000000 || db.Accounts == null || db.Accounts.Count > 2048 || db.Accounts.Any((PhoneAccount a) => a == null)) { throw new InvalidDataException("Base téléphone invalide. Conserver le fichier et vérifier son backup."); } if (db.Accounts.Select((PhoneAccount a) => a.Player).Distinct().Count() != db.Accounts.Count || db.Accounts.Select((PhoneAccount a) => a.Number).Distinct().Count() != db.Accounts.Count) { throw new InvalidDataException("Identités ou numéros dupliqués."); } foreach (PhoneAccount account in db.Accounts) { if (Normalize(account.Number) != account.Number || !account.Number.StartsWith("06") || account.Contacts == null || account.Messages == null || account.RecentCommands == null || account.Contacts.Count > 100 || account.Messages.Count > 100 || account.RecentCommands.Count > 128) { throw new InvalidDataException("Compte téléphone invalide."); } if (account.Wallpaper == null || (account.Wallpaper != "" && !Guid.TryParseExact(account.Wallpaper, "N", out var result))) { throw new InvalidDataException("Fond d’écran invalide."); } Guid result2; if (account.Calls == null || account.Calls.Count > 50 || account.Calls.Any(delegate(PhoneCallEntry c) { int result3; if (c != null && Guid.TryParseExact(c.Id, "N", out result2) && c.Number != null) { bool num; if (!(c.Number == "")) { num = Normalize(c.Number) != c.Number; } else { if (!c.Incoming) { goto IL_00b4; } num = !c.Masked; } if (!num && c.Seconds >= 0 && c.Seconds <= 86400) { result3 = ((!new string[4] { "completed", "missed", "cancelled", "declined" }.Contains(c.Outcome)) ? 1 : 0); goto IL_00b5; } } goto IL_00b4; IL_00b5: return (byte)result3 != 0; IL_00b4: result3 = 1; goto IL_00b5; })) { throw new InvalidDataException("Historique appels invalide."); } if (account.Transactions == null || account.Transactions.Count > 20 || account.Transactions.Any((PhoneBankEntry t) => t == null || !Guid.TryParseExact(t.Id, "N", out result2) || t.Label == null || t.Label.Length > 80)) { throw new InvalidDataException("Historique banque invalide."); } if (account.GalleryDeleted == null || account.GalleryDeleted.Count > 200 || account.GalleryDeleted.Any((string id) => !Guid.TryParseExact(id, "N", out result2))) { throw new InvalidDataException("Historique Galerie invalide."); } if (account.Gallery == null || account.Gallery.Count > 100 || account.Gallery.Any((string id) => !Guid.TryParseExact(id, "N", out result2)) || account.Gallery.Distinct().Count() != account.Gallery.Count) { throw new InvalidDataException("Galerie invalide."); } if (account.PinSalt == null || account.PinHash == null || account.PinSalt == "" != (account.PinHash == "")) { throw new InvalidDataException("Code téléphone invalide."); } foreach (PhoneContact contact in account.Contacts) { if (contact == null || contact.Photo == null || (contact.Photo != "" && !Guid.TryParseExact(contact.Photo, "N", out result)) || Normalize(contact.Number) != contact.Number || string.IsNullOrWhiteSpace(contact.Name) || contact.Name.Length > 32 || contact.Name.Any(char.IsControl)) { throw new InvalidDataException("Contact invalide."); } } if (account.Contacts.Select((PhoneContact c) => c.Number).Distinct().Count() != account.Contacts.Count) { throw new InvalidDataException("Contacts dupliqués."); } foreach (PhoneSms message in account.Messages) { if (message == null || message.Voice == null || (message.Voice != "" && (!Guid.TryParseExact(message.Voice, "N", out result) || message.VoiceSeconds < 1 || message.VoiceSeconds > 15)) || !Guid.TryParseExact(message.Id, "N", out result) || Normalize(message.From) != message.From || Normalize(message.To) != message.To || (message.From != account.Number && message.To != account.Number) || (string.IsNullOrWhiteSpace(message.Text) && string.IsNullOrEmpty(message.Photo) && string.IsNullOrEmpty(message.Voice)) || message.Text.Length > 320 || (!string.IsNullOrEmpty(message.Photo) && !Guid.TryParseExact(message.Photo, "N", out result))) { throw new InvalidDataException("Message invalide."); } } if (account.Messages.Select((PhoneSms m) => m.Id).Distinct().Count() != account.Messages.Count) { throw new InvalidDataException("Messages dupliqués."); } if (account.RecentCommands.Any((string s) => !Guid.TryParseExact(s, "N", out result2))) { throw new InvalidDataException("Commande sauvegardée invalide."); } } } private void Save() { Validate(); Directory.CreateDirectory(Path.GetDirectoryName(file)); byte[] array = JsonSerializer.SerializeToUtf8Bytes(db); if (array.Length > 16777216) { throw new InvalidOperationException("Base téléphone pleine."); } string text = file + ".tmp"; try { if (File.Exists(file)) { File.Copy(file, file + ".bak", overwrite: true); } using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None)) { fileStream.Write(array); fileStream.Flush(flushToDisk: true); } File.Move(text, file, overwrite: true); } finally { if (File.Exists(text)) { File.Delete(text); } } } private void Commit(Action action) { byte[] array = JsonSerializer.SerializeToUtf8Bytes(db); try { action(); Save(); Index(); } catch { db = JsonSerializer.Deserialize(array); Index(); throw; } } public PhoneAccount Ensure(ulong id) { if (accounts.TryGetValue(id, out var value)) { return value; } if (accounts.Count >= 2048) { throw new InvalidOperationException("Capacité des comptes téléphone atteinte."); } Commit(delegate { string number = RandomNumber(new HashSet(numbers.Keys)); db.NextNumber++; db.Accounts.Add(new PhoneAccount { Player = id, Number = number, PinDigits = 4 }); }); return accounts[id]; } public PhoneChange Apply(ulong id, PhoneCommand command, DateTime utc) { Ensure(id); if (command == null || !Guid.TryParseExact(command.Nonce, "N", out var _)) { throw new InvalidOperationException("Identifiant de commande invalide."); } PhoneAccount account = accounts[id]; if (account.RecentCommands.Contains(command.Nonce)) { return new PhoneChange { Duplicate = true, Notice = "Action déjà enregistrée." }; } PhoneChange change = new PhoneChange(); string number = Normalize(command.Number); Commit(delegate { Guid result2; switch (command.Operation) { case "contact": { string text2 = (command.Name ?? "").Trim(); if (text2.Length < 1 || text2.Length > 32 || text2.Any(char.IsControl)) { throw new InvalidOperationException("Nom de contact : 1 à 32 caractères."); } if (number == account.Number) { throw new InvalidOperationException("C'est ton propre numéro."); } PhoneContact phoneContact2 = account.Contacts.FirstOrDefault((PhoneContact c) => c.Number == number); if (phoneContact2 == null) { if (account.Contacts.Count >= 100) { throw new InvalidOperationException("100 contacts maximum."); } account.Contacts.Add(new PhoneContact { Number = number, Name = text2 }); } else { phoneContact2.Name = text2; } change.Notice = "Contact enregistré."; break; } case "contactPhoto": { PhoneContact phoneContact = account.Contacts.FirstOrDefault((PhoneContact c) => c.Number == number); if (phoneContact == null) { throw new InvalidOperationException("Enregistrer le contact avant de choisir sa photo."); } string text = command.Photo ?? ""; if (text != "" && (!Guid.TryParseExact(text, "N", out result2) || !account.Gallery.Contains(text))) { throw new InvalidOperationException("Choisir une photo de la Galerie de ce téléphone."); } phoneContact.Photo = text; change.Notice = ((text == "") ? "Photo du contact retirée." : "Photo du contact enregistrée."); break; } case "deleteContact": account.Contacts.RemoveAll((PhoneContact c) => c.Number == number); change.Notice = "Contact supprimé."; break; case "sms": { string text3 = (command.Text ?? "").Trim(); if ((text3.Length < 1 && string.IsNullOrEmpty(command.Photo) && string.IsNullOrEmpty(command.Voice)) || text3.Length > 320 || text3.Any((char c) => char.IsControl(c) && c != '\n')) { throw new InvalidOperationException("SMS : 1 à 320 caractères."); } if (number == account.Number) { throw new InvalidOperationException("Impossible d'envoyer un SMS à soi-même."); } if (!numbers.TryGetValue(number, out var value)) { throw new InvalidOperationException("Ce numéro n'est pas attribué sur ce serveur."); } if (!string.IsNullOrEmpty(command.Photo) && !Guid.TryParseExact(command.Photo, "N", out result2)) { throw new InvalidOperationException("Pièce jointe invalide."); } if (!string.IsNullOrEmpty(command.Voice) && !string.IsNullOrEmpty(command.Photo)) { throw new InvalidOperationException("Choisir une photo ou un vocal."); } if (!string.IsNullOrEmpty(command.Voice) && (!Guid.TryParseExact(command.Voice, "N", out result2) || command.VoiceSeconds < 1 || command.VoiceSeconds > 15)) { throw new InvalidOperationException("Vocal invalide."); } string id2 = Guid.NewGuid().ToString("N"); AddMessage(account, new PhoneSms { Id = id2, From = account.Number, To = number, Text = text3, Photo = (command.Photo ?? ""), Voice = (command.Voice ?? ""), VoiceSeconds = command.VoiceSeconds, Utc = utc.ToUniversalTime(), Read = true }); AddMessage(value, new PhoneSms { Id = id2, From = account.Number, To = number, Text = text3, Photo = (command.Photo ?? ""), Voice = (command.Voice ?? ""), VoiceSeconds = command.VoiceSeconds, Utc = utc.ToUniversalTime() }); change.Recipient = value.Player; change.Notice = "SMS enregistré et envoyé."; break; } case "read": foreach (PhoneSms item in account.Messages.Where((PhoneSms m) => m.To == account.Number && m.From == number)) { item.Read = true; } change.Notice = ""; break; default: throw new InvalidOperationException("Action téléphone inconnue."); } account.RecentCommands.Add(command.Nonce); if (account.RecentCommands.Count > 128) { account.RecentCommands.RemoveAt(0); } }); return change; } private static void AddMessage(PhoneAccount account, PhoneSms message) { account.Messages.Add(message); while (account.Messages.Count > 100) { account.Messages.RemoveAt(0); } } public bool ReserveExternal(ulong actor, string nonce) { PhoneAccount account = Ensure(actor); if (!Guid.TryParseExact(nonce, "N", out var _)) { throw new InvalidOperationException("Commande invalide."); } if (account.RecentCommands.Contains(nonce)) { return false; } Commit(delegate { account.RecentCommands.Add(nonce); while (account.RecentCommands.Count > 128) { account.RecentCommands.RemoveAt(0); } }); return true; } public void BankMovement(ulong from, ulong to, long amount, string nonce, DateTime utc) { PhoneAccount a = Ensure(from); PhoneAccount b = Ensure(to); if (a.Transactions.Any((PhoneBankEntry t) => t.Id == nonce)) { return; } Commit(delegate { a.Transactions.Insert(0, new PhoneBankEntry { Id = nonce, Amount = -amount, Label = "Vers " + Display(b.Number), Utc = utc }); b.Transactions.Insert(0, new PhoneBankEntry { Id = nonce, Amount = amount, Label = "De " + Display(a.Number), Utc = utc }); while (a.Transactions.Count > 20) { a.Transactions.RemoveAt(20); } while (b.Transactions.Count > 20) { b.Transactions.RemoveAt(20); } }); } public bool BankPinSet(ulong owner) { return Ensure(owner).BankPinHash != ""; } public bool VerifyBankPin(ulong owner, string pin) { PhoneAccount phoneAccount = Ensure(owner); return PhonePin.Verify(pin, phoneAccount.BankPinSalt, phoneAccount.BankPinHash); } public void SetBankPin(ulong owner, string pin) { PhoneAccount a = Ensure(owner); string salt = PhonePin.Salt(); string hash = PhonePin.Hash(pin, salt); Commit(delegate { a.BankPinSalt = salt; a.BankPinHash = hash; }); } public bool PinSet(ulong owner) { return Ensure(owner).PinHash != ""; } public bool VerifyPin(ulong owner, string pin) { PhoneAccount phoneAccount = Ensure(owner); return PhonePin.Verify(pin, phoneAccount.PinSalt, phoneAccount.PinHash); } public void SetPin(ulong owner, string pin) { PhoneAccount a = Ensure(owner); string salt = PhonePin.Salt(); string hash = PhonePin.Hash(pin, salt); Commit(delegate { a.PinSalt = salt; a.PinHash = hash; a.PinDigits = 4; }); } public List DeletedPhotos(ulong owner) { return Ensure(owner).GalleryDeleted.ToList(); } public List Gallery(ulong owner) { return Ensure(owner).Gallery.ToList(); } public void AddPhoto(ulong owner, string id) { if (!Guid.TryParseExact(id, "N", out var _)) { throw new InvalidOperationException("Photo invalide."); } PhoneAccount a = Ensure(owner); if (!a.GalleryDeleted.Contains(id) && !a.Gallery.Contains(id)) { if (a.Gallery.Count >= 100) { throw new InvalidOperationException("Galerie pleine (100 photos)."); } Commit(delegate { a.Gallery.Insert(0, id); }); } } public bool PhotoDeleted(ulong owner, string id) { return Ensure(owner).GalleryDeleted.Contains(id); } public void DeletePhoto(ulong owner, string id) { if (!Guid.TryParseExact(id, "N", out var _)) { throw new InvalidOperationException("Photo invalide."); } PhoneAccount a = Ensure(owner); if (a.GalleryDeleted.Contains(id)) { return; } Commit(delegate { a.Gallery.Remove(id); a.GalleryDeleted.Add(id); while (a.GalleryDeleted.Count > 200) { a.GalleryDeleted.RemoveAt(0); } }); } public string Wallpaper(ulong owner) { return Ensure(owner).Wallpaper; } public bool IsWallpaper(ulong owner, string id) { return !string.IsNullOrEmpty(id) && Wallpaper(owner) == id; } public void SetWallpaper(ulong owner, string id) { id = id ?? ""; PhoneAccount a = Ensure(owner); if (id != "" && (!Guid.TryParseExact(id, "N", out var _) || !a.Gallery.Contains(id))) { throw new InvalidOperationException("Choisir une photo de la Galerie de ce téléphone."); } Commit(delegate { a.Wallpaper = id; }); } public bool CanReadVoice(ulong owner, string id) { return Ensure(owner).Messages.Any((PhoneSms m) => m.Voice == id); } public bool PhotoReferenced(string photo) { return db.Accounts.Any((PhoneAccount a) => a.Wallpaper == photo || a.Gallery.Contains(photo) || a.Contacts.Any((PhoneContact c) => c.Photo == photo) || a.Messages.Any((PhoneSms m) => m.Photo == photo)); } public bool CanReadPhoto(ulong player, string photo) { return Ensure(player).Wallpaper == photo || Ensure(player).Gallery.Contains(photo) || Ensure(player).Contacts.Any((PhoneContact c) => c.Photo == photo) || Ensure(player).Messages.Any((PhoneSms m) => m.Photo == photo); } public ulong? FindPlayer(string input) { string key = Normalize(input); PhoneAccount value; return numbers.TryGetValue(key, out value) ? new ulong?(value.Player) : ((ulong?)null); } public void RecordCall(ulong caller, ulong callee, PhoneCallResult result) { PhoneAccount a = Ensure(caller); PhoneAccount b = Ensure(callee); if (a.Calls.Any((PhoneCallEntry c) => c.Id == result.Id) || b.Calls.Any((PhoneCallEntry c) => c.Id == result.Id)) { return; } Commit(delegate { a.Calls.Insert(0, new PhoneCallEntry { Id = result.Id, Number = b.Number, Outcome = result.Outcome, Incoming = false, Masked = result.Masked, Seconds = result.Seconds, Utc = result.Utc }); b.Calls.Insert(0, new PhoneCallEntry { Id = result.Id, Number = (result.Masked ? "" : a.Number), Outcome = result.Outcome, Incoming = true, Masked = result.Masked, Seconds = result.Seconds, Utc = result.Utc }); while (a.Calls.Count > 50) { a.Calls.RemoveAt(50); } while (b.Calls.Count > 50) { b.Calls.RemoveAt(50); } }); } public PhoneView PrivateView(ulong id) { PhoneAccount phoneAccount = Ensure(id); return new PhoneView { Player = id, Number = phoneAccount.Number, Calls = phoneAccount.Calls.Select((PhoneCallEntry c) => new PhoneCallEntry { Id = c.Id, Number = c.Number, Outcome = c.Outcome, Incoming = c.Incoming, Masked = c.Masked, Seconds = c.Seconds, Utc = c.Utc }).ToList(), Transactions = phoneAccount.Transactions.Select((PhoneBankEntry t) => new PhoneBankEntry { Id = t.Id, Amount = t.Amount, Label = t.Label, Utc = t.Utc }).ToList(), Contacts = phoneAccount.Contacts.Select((PhoneContact c) => new PhoneContact { Number = c.Number, Name = c.Name, Photo = c.Photo }).ToList(), Messages = phoneAccount.Messages.Select((PhoneSms m) => new PhoneSms { Id = m.Id, From = m.From, To = m.To, Text = m.Text, Photo = m.Photo, Voice = m.Voice, VoiceSeconds = m.VoiceSeconds, Utc = m.Utc, Read = m.Read }).ToList() }; } } public static class PhoneShutter { private static float nextShot; private static ControllerRig menuRig; private static bool previousMenuDisabled; public static bool Owns(BaseController controller) { return PhoneMod.Instance?.OwnsPhotoController(controller) ?? false; } public static void Poll(PhoneSurface screen) { BaseController val = null; BaseController val2 = null; if (screen != null && screen.PlacementReady) { PhoneMod instance = PhoneMod.Instance; if (instance != null && instance.Media?.PhotoPage == true) { if (screen.HoldsLeft) { val = Player.LeftController; } if (screen.HoldsRight) { val2 = Player.RightController; } } } if ((Object)(object)val == (Object)null && (Object)(object)val2 == (Object)null) { RestoreMenu(); return; } OpenControllerRig controllerRig = Player.ControllerRig; if ((Object)(object)menuRig != (Object)(object)controllerRig) { RestoreMenu(); if ((Object)(object)controllerRig != (Object)null) { menuRig = (ControllerRig)(object)controllerRig; previousMenuDisabled = ((ControllerRig)controllerRig).IsMenuTapDisabled; } } if ((Object)(object)menuRig != (Object)null) { menuRig.IsMenuTapDisabled = true; } if ((Object)(object)val != (Object)null && val.GetBButtonDown()) { Press(val); } if ((Object)(object)val2 != (Object)null && val2.GetBButtonDown()) { Press(val2); } } public static void Press(BaseController controller) { if (Owns(controller) && !(Time.realtimeSinceStartup < nextShot)) { nextShot = Time.realtimeSinceStartup + 0.3f; PhoneMod.Instance?.ShutterPhoto(); } } public static void RestoreMenu() { ControllerRig val = menuRig; menuRig = null; if ((Object)(object)val != (Object)null) { val.IsMenuTapDisabled = previousMenuDisabled; } } } public sealed class PhoneSounds : IDisposable { public static readonly string[] CallNames = new string[4] { "Classique", "Cristal", "Orbital", "Silencieux" }; public static readonly string[] MessageNames = new string[4] { "Note", "Duo", "Carillon", "Silencieux" }; private readonly string file; private readonly AudioClip[] clips = (AudioClip[])(object)new AudioClip[8]; private GameObject go; private AudioSource source; public PhonePreferences Preferences { get; private set; } = new PhonePreferences(); public PhoneSounds(string file) { this.file = file; if (File.Exists(file)) { try { Preferences = JsonSerializer.Deserialize(File.ReadAllText(file)) ?? new PhonePreferences(); Preferences.Validate(); } catch (Exception) { Preferences = new PhonePreferences(); } } } public void Select(bool call, int choice) { PhonePreferences phonePreferences = Preferences.Copy(); if (call) { phonePreferences.CallTone = choice; } else { phonePreferences.MessageTone = choice; } phonePreferences.Validate(); Directory.CreateDirectory(Path.GetDirectoryName(file)); File.WriteAllText(file + ".tmp", JsonSerializer.Serialize(phonePreferences)); File.Move(file + ".tmp", file, overwrite: true); Preferences = phonePreferences; Play(call, choice); } public void SetVolume(float value) { PhonePreferences phonePreferences = Preferences.Copy(); phonePreferences.RingVolume = Math.Max(0f, Math.Min(1f, value)); phonePreferences.Validate(); Directory.CreateDirectory(Path.GetDirectoryName(file)); File.WriteAllText(file + ".tmp", JsonSerializer.Serialize(phonePreferences)); File.Move(file + ".tmp", file, overwrite: true); Preferences = phonePreferences; Message(); } public void SetFps(bool video, int fps) { PhonePreferences phonePreferences = Preferences.Copy(); if (video) { phonePreferences.VideoFps = fps; } else { phonePreferences.PhotoFps = fps; } phonePreferences.Validate(); Directory.CreateDirectory(Path.GetDirectoryName(file)); File.WriteAllText(file + ".tmp", JsonSerializer.Serialize(phonePreferences)); File.Move(file + ".tmp", file, overwrite: true); Preferences = phonePreferences; } public void SetTheme(string theme) { PhonePreferences phonePreferences = Preferences.Copy(); phonePreferences.Theme = theme; phonePreferences.Validate(); Directory.CreateDirectory(Path.GetDirectoryName(file)); File.WriteAllText(file + ".tmp", JsonSerializer.Serialize(phonePreferences)); File.Move(file + ".tmp", file, overwrite: true); Preferences = phonePreferences; } public void SetLanguage(string language) { PhonePreferences phonePreferences = Preferences.Copy(); phonePreferences.Language = language; phonePreferences.Validate(); Directory.CreateDirectory(Path.GetDirectoryName(file)); File.WriteAllText(file + ".tmp", JsonSerializer.Serialize(phonePreferences)); File.Move(file + ".tmp", file, overwrite: true); Preferences = phonePreferences; } public AudioClip Clip(bool call, int choice) { if (choice == 3) { return null; } int num = ((!call) ? 4 : 0) + choice; if ((Object)(object)clips[num] != (Object)null) { return clips[num]; } float num2 = (call ? 2f : 0.65f); float[] array = new float[(int)(24000f * num2)]; for (int i = 0; i < array.Length; i++) { float num3 = (float)i / 24000f; float num4 = ((!call) ? (choice switch { 1 => 740, 0 => 880, _ => 1040, }) : (choice switch { 1 => 660, 0 => 440, _ => 520, })); bool flag = ((!call) ? (num3 < 0.45f) : (num3 < 0.3f || (num3 > 0.45f && num3 < 0.75f))); if (choice == 1) { num4 += (float)((int)(num3 * 8f) % 2 * 220); } if (choice == 2) { num4 += (float)((int)(num3 * 10f) % 4 * 110); } float num5 = Math.Min(1f, num3 * 80f) * Math.Min(1f, Math.Max(0f, (num2 - num3) * 80f)); array[i] = (flag ? (0.65f * num5 * (float)Math.Sin(Math.PI * 2.0 * (double)num4 * (double)num3)) : 0f); } clips[num] = AudioClip.Create("RP_Tone_" + num, array.Length, 1, 24000, false); clips[num].SetData(Il2CppStructArray.op_Implicit(array), 0); return clips[num]; } public void Message() { Play(call: false, Preferences.MessageTone); } public void Play(bool call, int choice) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown AudioClip val = Clip(call, choice); if (!((Object)(object)val == (Object)null)) { if ((Object)(object)source == (Object)null) { go = new GameObject("RP_LocalPhoneSounds"); source = go.AddComponent(); source.spatialBlend = 0f; source.playOnAwake = false; } source.PlayOneShot(val, Preferences.RingVolume); } } public void Dispose() { if ((Object)(object)go != (Object)null) { Object.Destroy((Object)(object)go); } source = null; go = null; AudioClip[] array = clips; foreach (AudioClip val in array) { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } Array.Clear(clips, 0, clips.Length); } } public static class PhoneStreamPcm { public const int Rate = 48000; public const int FrameSamples = 480; public static byte[] Pack(short[] samples, int rate = 48000) { if ((rate != 16000 && rate != 32000 && rate != 48000) || samples == null || samples.Length != rate / 100) { throw new ArgumentException("Trame PCM invalide."); } byte[] array = new byte[8 + samples.Length * 2]; array[0] = 87; array[1] = 80; array[2] = 67; array[3] = 51; BitConverter.GetBytes(rate).CopyTo(array, 4); Buffer.BlockCopy(samples, 0, array, 8, samples.Length * 2); return array; } public static bool Valid(byte[] b) { if (b == null || b.Length < 8 || b[0] != 87 || b[1] != 80 || b[2] != 67 || b[3] != 51) { return false; } int num = BitConverter.ToInt32(b, 4); return (num == 16000 || num == 32000 || num == 48000) && b.Length == 8 + num / 100 * 2; } public static short[] Decode(byte[] b, int outputRate) { if (!Valid(b) || outputRate < 8000 || outputRate > 192000) { throw new ArgumentException("Trame PCM invalide."); } int num = BitConverter.ToInt32(b, 4); short[] array = new short[num / 100]; Buffer.BlockCopy(b, 8, array, 0, array.Length * 2); int num2 = outputRate / 100; short[] array2 = new short[num2]; for (int i = 0; i < num2; i++) { double num3 = (double)i * (double)num / (double)outputRate; int num4 = (int)num3; int num5 = Math.Min(num4 + 1, array.Length - 1); array2[i] = (short)((double)array[num4] + (double)(array[num5] - array[num4]) * (num3 - (double)num4)); } return array2; } } public sealed class PhonePcmResampler { private readonly int outputRate; private PhoneVoiceFilter filter; private int inputRate; private long inputIndex; private long outputIndex; private double previous; public PhonePcmResampler(int rate) { outputRate = rate; } public void Push(short[] pcm, int rate, Action emit) { if (pcm == null || rate < outputRate || rate > 192000) { return; } if (inputRate != rate) { inputRate = rate; filter = new PhoneVoiceFilter(rate, (double)outputRate * 0.4); inputIndex = (outputIndex = 0L); previous = 0.0; } foreach (short num in pcm) { double num2 = filter.Process((float)num / 32768f); for (double num3 = (double)outputIndex * (double)inputRate / (double)outputRate; num3 <= (double)inputIndex; num3 = (double)outputIndex * (double)inputRate / (double)outputRate) { double num4 = ((inputIndex == 0L) ? 1.0 : (num3 - (double)(inputIndex - 1))); double num5 = previous + (num2 - previous) * num4; emit((short)Math.Clamp(num5 * 32767.0, -32767.0, 32767.0)); outputIndex++; } previous = num2; inputIndex++; } } } public sealed class PhoneStreamEncoder { private readonly List frame = new List(); private PhonePcmResampler resampler; private int sourceRate; private int wireRate; public void Push(short[] pcm, int inputRate, Action send) { if (pcm == null || inputRate < 16000) { return; } if (sourceRate != inputRate) { sourceRate = inputRate; wireRate = ((inputRate >= 48000) ? 48000 : ((inputRate >= 32000) ? 32000 : 16000)); resampler = new PhonePcmResampler(wireRate); frame.Clear(); } resampler.Push(pcm, inputRate, delegate(short sample) { frame.Add(sample); if (frame.Count == wireRate / 100) { send(PhoneStreamPcm.Pack(frame.ToArray(), wireRate)); frame.Clear(); } }); } } public sealed class PhoneSurface : IDisposable { public readonly GameObject Body; private readonly GameObject root; private readonly GameObject canvasObject; private GameObject content; private LaserCursor cursor; private bool disposed; private readonly Action warn; private Texture2D roundedTexture; private Sprite roundedSprite; private readonly float created = Time.realtimeSinceStartup; private bool cursorWarning; private readonly ScreenSettings settings; private bool wasHeld; private bool placed; private bool faceChosen; private bool firstPickup = true; private Vector3 fixedNormal; private Vector3 previousMin; private Vector3 previousMax; private Vector3 previousScale; private int stableSamples; private float nextPlacement; private float placementUntil; private Vector3 fitPosition; private Vector3 fitWorldScale; private Quaternion fitRotation; private readonly Dictionary icons = new Dictionary(); public bool HoldsLeft => !disposed && (Object)(object)Body != (Object)null && Held(Player.LeftHand); public bool HoldsRight => !disposed && (Object)(object)Body != (Object)null && Held(Player.RightHand); public bool IsHeld => !disposed && (Object)(object)Body != (Object)null && (Held(Player.LeftHand) || Held(Player.RightHand)); public bool PlacementReady => placed; public Transform CameraPose => root.transform; public string PlacementInfo { get; private set; } = ""; public Color TextColor { get; set; } = Color.white; public PhoneSurface(GameObject body, ScreenSettings settings, Action warn) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) Body = body; this.warn = warn; this.settings = settings; placementUntil = created + 3f; root = new GameObject("RP_PrivatePhoneScreen"); root.transform.SetParent(body.transform, false); try { canvasObject = new GameObject("CANVAS"); canvasObject.layer = 5; RectTransform val = canvasObject.AddComponent(); ((Transform)val).SetParent(root.transform, false); val.sizeDelta = new Vector2(600f, 1000f); Canvas val2 = canvasObject.AddComponent(); val2.renderMode = (RenderMode)2; canvasObject.AddComponent(); CreateRoundedSprite(); canvasObject.SetActive(false); root.SetActive(false); LaserCursorUtilities.CreateLaserCursor((Action)delegate(LaserCursor ready) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) if (disposed || (Object)(object)Body == (Object)null) { Object.Destroy((Object)(object)((Component)ready).gameObject); } else { cursor = ready; ((Component)cursor).transform.SetParent(root.transform, false); ((Component)cursor).transform.localPosition = Vector3.zero; ((Component)cursor).transform.localRotation = Quaternion.identity; UpdateCursorRegion(); ((Component)cursor).gameObject.SetActive(true); } }); } catch { Dispose(); throw; } } private bool Place(ScreenSettings s) { //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_02b9: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02cb: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_0310: Unknown result type (might be due to invalid IL or missing references) //IL_0304: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_034d: Unknown result type (might be due to invalid IL or missing references) //IL_0354: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_035d: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_0322: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_0346: Unknown result type (might be due to invalid IL or missing references) //IL_033f: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_0397: Unknown result type (might be due to invalid IL or missing references) //IL_0399: Unknown result type (might be due to invalid IL or missing references) //IL_039e: Unknown result type (might be due to invalid IL or missing references) //IL_03a2: Unknown result type (might be due to invalid IL or missing references) //IL_03b2: Unknown result type (might be due to invalid IL or missing references) //IL_03b4: Unknown result type (might be due to invalid IL or missing references) //IL_03b9: Unknown result type (might be due to invalid IL or missing references) //IL_03be: Unknown result type (might be due to invalid IL or missing references) //IL_03c0: Unknown result type (might be due to invalid IL or missing references) //IL_03c2: Unknown result type (might be due to invalid IL or missing references) //IL_0418: Unknown result type (might be due to invalid IL or missing references) //IL_041d: Unknown result type (might be due to invalid IL or missing references) //IL_03d6: Unknown result type (might be due to invalid IL or missing references) //IL_03d8: Unknown result type (might be due to invalid IL or missing references) //IL_03dd: Unknown result type (might be due to invalid IL or missing references) //IL_0406: Unknown result type (might be due to invalid IL or missing references) //IL_0408: Unknown result type (might be due to invalid IL or missing references) //IL_03fc: Unknown result type (might be due to invalid IL or missing references) //IL_03fe: Unknown result type (might be due to invalid IL or missing references) //IL_0403: Unknown result type (might be due to invalid IL or missing references) //IL_043a: Unknown result type (might be due to invalid IL or missing references) //IL_0431: Unknown result type (might be due to invalid IL or missing references) //IL_0428: Unknown result type (might be due to invalid IL or missing references) //IL_0476: Unknown result type (might be due to invalid IL or missing references) //IL_047d: Unknown result type (might be due to invalid IL or missing references) //IL_0461: Unknown result type (might be due to invalid IL or missing references) //IL_0468: Unknown result type (might be due to invalid IL or missing references) //IL_044c: Unknown result type (might be due to invalid IL or missing references) //IL_0453: Unknown result type (might be due to invalid IL or missing references) //IL_04be: Unknown result type (might be due to invalid IL or missing references) //IL_04c5: Unknown result type (might be due to invalid IL or missing references) //IL_050c: Unknown result type (might be due to invalid IL or missing references) //IL_0522: Unknown result type (might be due to invalid IL or missing references) //IL_0524: Unknown result type (might be due to invalid IL or missing references) //IL_0534: Unknown result type (might be due to invalid IL or missing references) //IL_0539: Unknown result type (might be due to invalid IL or missing references) //IL_0550: Unknown result type (might be due to invalid IL or missing references) //IL_0555: Unknown result type (might be due to invalid IL or missing references) //IL_056b: Unknown result type (might be due to invalid IL or missing references) //IL_056d: Unknown result type (might be due to invalid IL or missing references) //IL_0572: Unknown result type (might be due to invalid IL or missing references) //IL_0574: Unknown result type (might be due to invalid IL or missing references) //IL_058b: Unknown result type (might be due to invalid IL or missing references) //IL_0590: Unknown result type (might be due to invalid IL or missing references) //IL_05a6: Unknown result type (might be due to invalid IL or missing references) //IL_05bc: Unknown result type (might be due to invalid IL or missing references) //IL_05d2: Unknown result type (might be due to invalid IL or missing references) //IL_05df: Unknown result type (might be due to invalid IL or missing references) //IL_05f6: Unknown result type (might be due to invalid IL or missing references) //IL_05fb: Unknown result type (might be due to invalid IL or missing references) //IL_060c: Unknown result type (might be due to invalid IL or missing references) //IL_0611: Unknown result type (might be due to invalid IL or missing references) //IL_0622: Unknown result type (might be due to invalid IL or missing references) //IL_0627: Unknown result type (might be due to invalid IL or missing references) //IL_065c: Unknown result type (might be due to invalid IL or missing references) //IL_0673: Unknown result type (might be due to invalid IL or missing references) //IL_06a6: Unknown result type (might be due to invalid IL or missing references) //IL_04a9: Unknown result type (might be due to invalid IL or missing references) //IL_04b0: Unknown result type (might be due to invalid IL or missing references) //IL_0494: Unknown result type (might be due to invalid IL or missing references) //IL_049b: Unknown result type (might be due to invalid IL or missing references) Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(float.MaxValue, float.MaxValue, float.MaxValue); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(float.MinValue, float.MinValue, float.MinValue); bool flag = false; int num = 0; Vector3 val4; Vector3 val5 = default(Vector3); foreach (Renderer componentsInChild in Body.GetComponentsInChildren()) { if ((Object)(object)componentsInChild == (Object)null || ((Component)componentsInChild).transform.IsChildOf(root.transform)) { continue; } MeshFilter component = ((Component)componentsInChild).GetComponent(); Mesh val3 = ((component != null) ? component.sharedMesh : null); if ((Object)(object)val3 == (Object)null) { SkinnedMeshRenderer component2 = ((Component)componentsInChild).GetComponent(); val3 = ((component2 != null) ? component2.sharedMesh : null); } if ((Object)(object)val3 == (Object)null) { continue; } Bounds bounds = val3.bounds; val4 = ((Bounds)(ref bounds)).size; if (!(((Vector3)(ref val4)).sqrMagnitude < 1E-06f)) { num++; for (int i = 0; i < 8; i++) { ((Vector3)(ref val5))..ctor(((i & 1) == 0) ? ((Bounds)(ref bounds)).min.x : ((Bounds)(ref bounds)).max.x, ((i & 2) == 0) ? ((Bounds)(ref bounds)).min.y : ((Bounds)(ref bounds)).max.y, ((i & 4) == 0) ? ((Bounds)(ref bounds)).min.z : ((Bounds)(ref bounds)).max.z); Vector3 val6 = Body.transform.InverseTransformPoint(((Component)componentsInChild).transform.TransformPoint(val5)); val = Vector3.Min(val, val6); val2 = Vector3.Max(val2, val6); flag = true; } } } if (!flag) { stableSamples = 0; return false; } Vector3 lossyScale = Body.transform.lossyScale; val4 = val - previousMin; if (((Vector3)(ref val4)).sqrMagnitude < 1E-08f) { val4 = val2 - previousMax; if (((Vector3)(ref val4)).sqrMagnitude < 1E-08f) { val4 = lossyScale - previousScale; if (((Vector3)(ref val4)).sqrMagnitude < 1E-08f) { stableSamples++; goto IL_0272; } } } stableSamples = 0; goto IL_0272; IL_0272: previousMin = val; previousMax = val2; previousScale = lossyScale; if (stableSamples < 2) { return false; } Vector3 val7 = val2 - val; Vector3 val8 = (val + val2) * 0.5f; int num2 = ((!(val7.x <= val7.y) || !(val7.x <= val7.z)) ? ((val7.y <= val7.z) ? 1 : 2) : 0); Vector3 val9 = (Vector3)(num2 switch { 1 => Vector3.up, 0 => Vector3.right, _ => Vector3.forward, }); Vector3 val10 = ((num2 != 1) ? ((val7.y >= ((num2 == 0) ? val7.z : val7.x)) ? Vector3.up : ((num2 == 0) ? Vector3.forward : Vector3.right)) : ((val7.z >= val7.x) ? Vector3.forward : Vector3.right)); Transform head = Player.Head; if (!faceChosen && (Object)(object)head != (Object)null) { Vector3 val11 = Body.transform.TransformDirection(val9); Vector3 val12 = head.position - Body.transform.TransformPoint(val8); if (Vector3.Dot(val11, val12) < 0f) { val9 = -val9; } } if (!faceChosen) { if (s.ReverseFace) { val9 = -val9; } fixedNormal = val9; faceChosen = true; } else { val9 = fixedNormal; } float num3 = num2 switch { 1 => val7.y, 0 => val7.x, _ => val7.z, }; float num4 = Mathf.Max(1E-06f, Mathf.Min(num2 switch { 1 => Mathf.Min(val7.x, val7.z), 0 => Mathf.Min(val7.y, val7.z), _ => Mathf.Min(val7.x, val7.y), } * 0.78f, num2 switch { 1 => Mathf.Max(val7.x, val7.z), 0 => Mathf.Max(val7.y, val7.z), _ => Mathf.Max(val7.x, val7.y), } * 0.78f * 0.6f)) * s.SizeMultiplier; root.transform.localScale = Vector3.one; root.transform.localPosition = val8 + val9 * (num3 * 0.5f + 0.002f) + new Vector3(s.X, s.Y, s.Z); root.transform.localRotation = Quaternion.LookRotation(-val9, val10) * Quaternion.Euler(s.RotationX, s.RotationY, s.RotationZ); canvasObject.transform.localPosition = Vector3.zero; canvasObject.transform.localRotation = Quaternion.identity; canvasObject.transform.localScale = Vector3.one * (num4 / 600f); fitPosition = root.transform.localPosition; fitRotation = root.transform.localRotation; fitWorldScale = canvasObject.transform.lossyScale; PlacementInfo = $"maillages={num}, centre local={val8}, dimensions={val7}, largeur UI={num4:F4} m, face={val9}"; UpdateCursorRegion(); return true; } private void UpdateCursorRegion() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown if (!((Object)(object)cursor == (Object)null)) { cursor.regions = Il2CppReferenceArray.op_Implicit((CursorRegion[])(object)new CursorRegion[1] { new CursorRegion { bounds = new Bounds(Vector3.zero, Vector3.Scale(new Vector3(600f, 1000f, 80f), canvasObject.transform.lossyScale)), center = canvasObject.transform } }); } } private void CreateRoundedSprite() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) roundedTexture = new Texture2D(128, 128, (TextureFormat)4, false); ((Object)roundedTexture).name = "RP_PhoneRoundedCorners"; ((Texture)roundedTexture).filterMode = (FilterMode)1; ((Texture)roundedTexture).wrapMode = (TextureWrapMode)1; Color[] array = (Color[])(object)new Color[16384]; for (int i = 0; i < 128; i++) { for (int j = 0; j < 128; j++) { float num = MathF.Max(MathF.Abs((float)j + 0.5f - 64f) - 36f, 0f); float num2 = MathF.Max(MathF.Abs((float)i + 0.5f - 64f) - 36f, 0f); float num3 = Math.Clamp(0.5f - (MathF.Sqrt(num * num + num2 * num2) - 28f), 0f, 1f); array[i * 128 + j] = new Color(1f, 1f, 1f, num3); } } roundedTexture.SetPixels(Il2CppStructArray.op_Implicit(array)); roundedTexture.Apply(false, false); roundedSprite = Sprite.Create(roundedTexture, new Rect(0f, 0f, 128f, 128f), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, new Vector4(28f, 28f, 28f, 28f)); } public void Clear(Color? background = null) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)content != (Object)null) { content.SetActive(false); Object.Destroy((Object)(object)content); } content = new GameObject("PAGE"); content.layer = 5; ((Transform)content.AddComponent()).SetParent(canvasObject.transform, false); Image val = Element("Background", 0f, 0f, 600f, 1000f).AddComponent(); val.sprite = roundedSprite; val.type = (Type)1; ((Graphic)val).color = (Color)(((??)background) ?? new Color(0.025f, 0.04f, 0.065f, 1f)); ((Graphic)val).raycastTarget = false; } private GameObject Element(string name, float x, float y, float width, float height) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.layer = 5; RectTransform val2 = val.AddComponent(); ((Transform)val2).SetParent(content.transform, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0.5f, 0.5f); val2.pivot = val3; Vector2 anchorMin = (val2.anchorMax = val3); val2.anchorMin = anchorMin; val2.anchoredPosition = new Vector2(x, y); val2.sizeDelta = new Vector2(width, height); return val; } public void Text(string text, float y, float height = 60f, float size = 30f) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) TextMeshProUGUI val = Element("Text", 0f, y, 550f, height).AddComponent(); ((TMP_Text)val).font = TMP_Settings.defaultFontAsset; ((TMP_Text)val).text = text; ((TMP_Text)val).fontSize = size; ((Graphic)val).color = TextColor; ((TMP_Text)val).richText = false; ((TMP_Text)val).enableWordWrapping = true; ((TMP_Text)val).alignment = (TextAlignmentOptions)514; ((Graphic)val).raycastTarget = false; } public void Panel(float x, float y, float width, float height, Color color) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) Image val = Element("Card", x, y, width, height).AddComponent(); val.sprite = roundedSprite; val.type = (Type)1; ((Graphic)val).color = color; ((Graphic)val).raycastTarget = false; } public void Label(string text, float x, float y, float width, float height, float size, Color color, bool left = false) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) TextMeshProUGUI val = Element("Label", x, y, width, height).AddComponent(); ((TMP_Text)val).font = TMP_Settings.defaultFontAsset; ((TMP_Text)val).text = text; ((TMP_Text)val).fontSize = size; ((Graphic)val).color = color; ((TMP_Text)val).richText = false; ((TMP_Text)val).enableWordWrapping = true; ((TMP_Text)val).alignment = (TextAlignmentOptions)(left ? 513 : 514); ((Graphic)val).raycastTarget = false; } public void PhotoTile(Texture texture, float x, float y, float width, float height, Action click) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) Button("", x, y, width, height, click, (Color?)new Color(0.16f, 0.19f, 0.24f, 1f)); if ((Object)(object)texture != (Object)null) { Picture(texture, x, y, width - 4f, height - 4f); } else { Label("…", x, y, width, height, 28f, Color.white); } } public void Button(string text, float x, float y, float width, float height, Action click, Color? tint = null) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) GameObject val = Element("Button", x, y, width, height); Image val2 = val.AddComponent(); val2.sprite = roundedSprite; val2.type = (Type)1; ((Graphic)val2).color = (Color)(((??)tint) ?? new Color(0.12f, 0.17f, 0.28f, 1f)); ((Graphic)val2).raycastTarget = false; Button val3 = val.AddComponent