using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Splatform; using Steamworks; using UnityEngine; using WebMap.Patches; using WebSocketSharp; using WebSocketSharp.Net; using WebSocketSharp.Server; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: AssemblyCompany("WebMap")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("(c) 2025 Various Authors")] [assembly: AssemblyFileVersion("2.7.1.0")] [assembly: AssemblyInformationalVersion("2.7.1+12b6633cf0e9c75cec88d0a4fa8cb5b31a721fee")] [assembly: AssemblyProduct("WebMap")] [assembly: AssemblyTitle("Valheim WebMap")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.7.1.0")] [module: UnverifiableCode] namespace WebMap { internal static class WebMapConfig { public static int TEXTURE_SIZE = 2048; public static int PIXEL_SIZE = 12; public static float EXPLORE_RADIUS = 100f; public static float UPDATE_FOG_TEXTURE_INTERVAL = 2f; public static float SAVE_FOG_TEXTURE_INTERVAL = 30f; public static int MAX_PINS_PER_USER = 50; public static int MAX_MESSAGES = 100; public static bool ALWAYS_MAP = true; public static bool ALWAYS_VISIBLE = false; public static bool DEBUG = false; public static bool TEST = false; public static int SERVER_PORT = 3000; public static float PLAYER_UPDATE_INTERVAL = 1f; public static bool CACHE_SERVER_FILES = true; public static string WORLD_NAME = ""; public static Vector3 WORLD_START_POS = Vector3.zero; public static int DEFAULT_ZOOM = 100; public static string DISCORD_WEBHOOK = ""; public static string DISCORD_INVITE_URL = ""; public static string URL = ""; public static void ReadConfigFile(ConfigFile config) { TEXTURE_SIZE = config.Bind("Texture", "texture_size", TEXTURE_SIZE, "How large is the map texture? Probably dont change this.").Value; PIXEL_SIZE = config.Bind("Texture", "pixel_size", PIXEL_SIZE, "How many in game units does a map pixel represent? Probably dont change this.").Value; EXPLORE_RADIUS = config.Bind("Texture", "explore_radius", EXPLORE_RADIUS, "A larger explore_radius reveals the map more quickly.").Value; UPDATE_FOG_TEXTURE_INTERVAL = config.Bind("Interval", "update_fog_texture_interval", UPDATE_FOG_TEXTURE_INTERVAL, "How often do we update the fog texture on the server in seconds.").Value; SAVE_FOG_TEXTURE_INTERVAL = config.Bind("Interval", "save_fog_texture_interval", SAVE_FOG_TEXTURE_INTERVAL, "How often do we save the fog texture in seconds.").Value; MAX_PINS_PER_USER = config.Bind("User", "max_pins_per_user", MAX_PINS_PER_USER, "How many pins each client is allowed to make before old ones start being deleted.").Value; SERVER_PORT = config.Bind("Server", "server_port", SERVER_PORT, "HTTP port for the website. The map will be display on this site.").Value; PLAYER_UPDATE_INTERVAL = config.Bind("Interval", "player_update_interval", PLAYER_UPDATE_INTERVAL, "How often do we send position data to web browsers in seconds.").Value; CACHE_SERVER_FILES = config.Bind("Server", "cache_server_files", CACHE_SERVER_FILES, "Should the server cache web files to be more performant?").Value; DEFAULT_ZOOM = config.Bind("Texture", "default_zoom", DEFAULT_ZOOM, "How zoomed in should the web map start at? Higher is more zoomed in.").Value; MAX_MESSAGES = config.Bind("Server", "max_messages", MAX_MESSAGES, "How many messages to keep buffered and display to client.").Value; ALWAYS_MAP = config.Bind("User", "always_map", ALWAYS_MAP, "Update the map to show where hidden players have traveled.").Value; ALWAYS_VISIBLE = config.Bind("User", "always_visible", ALWAYS_VISIBLE, "Completely ignore the players preference to be hidden.").Value; DEBUG = config.Bind("Server", "debug", DEBUG, "Output debugging information.").Value; TEST = config.Bind("Server", "test", TEST, "Enable test features (bugs).").Value; DISCORD_WEBHOOK = config.Bind("Server", "discord_webhook", DISCORD_WEBHOOK, "Discord webhook URL").Value; DISCORD_INVITE_URL = config.Bind("Server", "discord_invite_url", DISCORD_INVITE_URL, "Optional Discord invite URL to be added to the webpage.").Value; URL = config.Bind("Server", "webmap_url", URL, "URL to view the web map.").Value; } public static string GetWorldName() { if ((Object)(object)ZNet.instance != (Object)null) { WORLD_NAME = ZNet.instance.GetWorldName(); } else { string[] commandLineArgs = Environment.GetCommandLineArgs(); string wORLD_NAME = ""; for (int i = 0; i < commandLineArgs.Length; i++) { if (commandLineArgs[i] == "-world") { wORLD_NAME = commandLineArgs[i + 1]; break; } } WORLD_NAME = wORLD_NAME; } return WORLD_NAME; } public static string MakeClientConfigJson() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) return DictionaryToJson(new Dictionary { ["world_name"] = GetWorldName(), ["world_start_pos"] = WORLD_START_POS, ["default_zoom"] = DEFAULT_ZOOM, ["texture_size"] = TEXTURE_SIZE, ["pixel_size"] = PIXEL_SIZE, ["update_interval"] = PLAYER_UPDATE_INTERVAL, ["explore_radius"] = EXPLORE_RADIUS, ["max_messages"] = MAX_MESSAGES, ["always_map"] = ALWAYS_MAP, ["always_visible"] = ALWAYS_VISIBLE }); } private static string DictionaryToJson(Dictionary dict) { IEnumerable values = dict.Select(delegate(KeyValuePair d) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) object value = d.Value; if (value is float num) { return "\"" + d.Key + "\": " + num.ToString("F2", CultureInfo.InvariantCulture); } if (value is double num2) { return "\"" + d.Key + "\": " + num2.ToString("F2", CultureInfo.InvariantCulture); } if (value is string text) { return "\"" + d.Key + "\": \"" + text + "\""; } if (value is bool flag) { return "\"" + d.Key + "\": " + flag.ToString().ToLower(); } return (value is Vector3 val) ? ("\"" + d.Key + "\": \"" + val.x.ToString("F2", CultureInfo.InvariantCulture) + "," + val.y.ToString("F2", CultureInfo.InvariantCulture) + "," + val.z.ToString("F2", CultureInfo.InvariantCulture) + "\"") : $"\"{d.Key}\": {d.Value}"; }); return "{\n " + string.Join(",\n ", values) + "\n}\n"; } } [HarmonyPatch(typeof(ZRoutedRpc), "RouteRPC")] internal class DeathWatch { private const double DUPLICATE_WINDOW = 5.0; private static int deathRpcHash; private static bool deathRpcHashReady; private static readonly Dictionary lastDeath = new Dictionary(); private static void Postfix(RoutedRPCData rpcData) { try { if (rpcData != null) { if (!deathRpcHashReady) { deathRpcHash = StringExtensionMethods.GetStableHashCode("OnDeath"); deathRpcHashReady = true; } if (rpcData.m_methodHash == deathRpcHash) { Announce(rpcData); } } } catch (Exception ex) { ZLog.LogWarning((object)("WebMap: death watch failed: " + ex.Message)); } } private static void Announce(RoutedRPCData rpcData) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) MapDataServer instance = MapDataServer.getInstance(); if (instance == null) { return; } ZDOID targetZDO = rpcData.m_targetZDO; long num = rpcData.m_senderPeerID; string text = null; List players = instance.players; if (players != null) { foreach (ZNetPeer item in players) { if (item != null && !item.m_server) { if (!((ZDOID)(ref targetZDO)).IsNone() && item.m_characterID == targetZDO) { text = item.m_playerName; num = item.m_uid; break; } if (text == null && item.m_uid == rpcData.m_senderPeerID) { text = item.m_playerName; } } } } if (string.IsNullOrEmpty(text) && !((ZDOID)(ref targetZDO)).IsNone()) { ZDO val = null; try { val = ZDOMan.instance.GetZDO(targetZDO); } catch { } if (val != null) { text = val.GetString(ZDOVars.s_playerName, ""); } } if (!string.IsNullOrEmpty(text) && ShouldAnnounce(num)) { ZLog.Log((object)("WebMap: player " + text + " died")); instance.AddMessage(num, 1, "Server", "player _" + text + "_ died"); } } private static bool ShouldAnnounce(long key) { DateTime utcNow = DateTime.UtcNow; if (lastDeath.TryGetValue(key, out var value) && (utcNow - value).TotalSeconds < 5.0) { return false; } if (lastDeath.Count > 64) { List list = new List(); foreach (KeyValuePair item in lastDeath) { if ((utcNow - item.Value).TotalSeconds >= 5.0) { list.Add(item.Key); } } list.ForEach(delegate(long k) { lastDeath.Remove(k); }); } lastDeath[key] = utcNow; return true; } } public class DiscordWebHook : IDisposable { private readonly WebClient webClient; private static readonly NameValueCollection values = new NameValueCollection(); private readonly string webHookUrl; public DiscordWebHook(string url) { webHookUrl = url; webClient = new WebClient(); } public void SendMessage(string msgSend) { values.Remove("content"); values.Add("content", msgSend); if (Ext.IsNullOrEmpty(webHookUrl)) { ZLog.Log((object)$"WebMap::DiscordWebHook::SendMessage: {values}"); } else { webClient.UploadValues(webHookUrl, values); } } public void Dispose() { webClient.Dispose(); } } internal static class ImageConv { private static readonly Type T = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule"); private static readonly MethodInfo _load = T?.GetMethod("LoadImage", new Type[2] { typeof(Texture2D), typeof(byte[]) }) ?? T?.GetMethod("LoadImage", new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) }); private static readonly MethodInfo _enc = T?.GetMethod("EncodeToPNG", new Type[1] { typeof(Texture2D) }); public static bool LoadImage(Texture2D tex, byte[] data) { object[] parameters = ((_load.GetParameters().Length != 3) ? new object[2] { tex, data } : new object[3] { tex, data, false }); return (bool)_load.Invoke(null, parameters); } public static byte[] EncodeToPNG(Texture2D tex) { return (byte[])_enc.Invoke(null, new object[1] { tex }); } } [Serializable] public struct MapMessage { public long id; public int type; public string name; public string message; public string ts; public MapMessage(long id, int type, string name, string message) { this.id = id; this.type = type; this.name = name; this.message = message; ts = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture); } public string ToJson() { return JsonUtility.ToJson((object)this); } } public class WebSocketHandler : WebSocketBehavior { protected override void OnOpen() { string text = ((WebSocketBehavior)this).Context.Headers.Get("X-Forwarded-For"); if (Ext.IsNullOrEmpty(text)) { text = ((WebSocketBehavior)this).Context.UserEndPoint.ToString(); } ZLog.Log((object)("WebMap: new visitor connected from " + text)); ((WebSocketBehavior)this).OnOpen(); } protected override void OnMessage(MessageEventArgs e) { if (e.Data.ToString() == "players") { ((WebSocketBehavior)this).Send(MapDataServer.getInstance().getPlayerResponse(sendLast: true)); } ((WebSocketBehavior)this).OnMessage(e); } } public class MapDataServer { private static readonly Dictionary contentTypes = new Dictionary { { "html", "text/html" }, { "js", "text/javascript" }, { "css", "text/css" }, { "png", "image/png" }, { "jpg", "image/jpeg" }, { "webp", "image/webp" } }; private readonly Timer broadcastTimer; private readonly Dictionary fileCache; public Texture2D fogTexture; private readonly HttpServer httpServer; public byte[] mapImageData; public List pins = new List(); public List sentMessages = new List(); public List newMessages = new List(); public List players = new List(); public string lastPlayerResponse = ""; private bool forceReload; private readonly string publicRoot; private readonly WebSocketServiceHost webSocketHandler; private static MapDataServer __instance; public MapDataServer() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown __instance = this; httpServer = new HttpServer(WebMapConfig.SERVER_PORT); httpServer.AddWebSocketService("/"); httpServer.KeepClean = true; webSocketHandler = httpServer.WebSocketServices["/"]; broadcastTimer = new Timer(delegate { string text = ""; if (forceReload) { webSocketHandler.Sessions.Broadcast("reload\n"); forceReload = false; } else { text = getPlayerResponse(sendLast: false); if (text != lastPlayerResponse) { webSocketHandler.Sessions.Broadcast(text); lastPlayerResponse = text; } if (newMessages.Count > 0) { List tosend = new List(); newMessages.ForEach(delegate(MapMessage message) { if (WebMapConfig.MAX_MESSAGES < sentMessages.Count) { sentMessages.RemoveAt(0); } tosend.Add(message.ToJson()); sentMessages.Add(message); }); if (tosend.Count > 0) { webSocketHandler.Sessions.Broadcast("messages\n[" + string.Join(",", tosend) + "]"); } newMessages.Clear(); newMessages.TrimExcess(); } } }, null, TimeSpan.Zero, TimeSpan.FromSeconds(WebMapConfig.PLAYER_UPDATE_INTERVAL)); publicRoot = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty, "web"); fileCache = new Dictionary(); httpServer.OnGet += delegate(object sender, HttpRequestEventArgs e) { _ = e.Request; if (!ProcessSpecialRoutes(e)) { ServeStaticFiles(e); } }; } public string getPlayerResponse(bool sendLast) { if (sendLast && lastPlayerResponse.Length > 0) { return lastPlayerResponse; } string dataString = "players\n"; players.ForEach(delegate(ZNetPeer player) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) ZDO val = null; try { val = ZDOMan.instance.GetZDO(player.m_characterID); } catch { } if (val != null) { Vector3 position = val.GetPosition(); int num = (int)Math.Ceiling(val.GetFloat("max_health", 25f)); int num2 = (int)Math.Ceiling(val.GetFloat("health", (float)num)); int num3 = (val.GetBool("dead", false) ? 1 : 0); int num4 = (val.GetBool("pvp", false) ? 1 : 0); int num5 = (val.GetBool("inBed", false) ? 1 : 0); num = Math.Max(num, num2); dataString += $"{player.m_uid}\n{player.m_playerName}\n{num2}\n{num}\n"; if (!player.m_publicRefPos) { dataString += "hidden\n"; } if (player.m_publicRefPos || WebMapConfig.ALWAYS_VISIBLE || WebMapConfig.ALWAYS_MAP) { dataString += FormattableString.Invariant($"{position.x:0.##},{position.z:0.##}\n"); } dataString += $"{num3}{num4}{num5}\n\n"; } }); return dataString.Trim(); } public string MakePlayersJson() { List entries = new List(); players.ForEach(delegate(ZNetPeer player) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) ZDO val = null; try { val = ZDOMan.instance.GetZDO(player.m_characterID); } catch { } if (val != null) { Vector3 position = val.GetPosition(); int num = (int)Math.Ceiling(val.GetFloat("max_health", 25f)); int num2 = (int)Math.Ceiling(val.GetFloat("health", (float)num)); num = Math.Max(num, num2); bool flag = !player.m_publicRefPos; bool num3 = player.m_publicRefPos || WebMapConfig.ALWAYS_VISIBLE; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\"name\":\"").Append(JsonEscape(player.m_playerName)).Append("\""); stringBuilder.Append(",\"health\":").Append(num2); stringBuilder.Append(",\"maxHealth\":").Append(num); stringBuilder.Append(",\"dead\":").Append(val.GetBool("dead", false) ? "true" : "false"); stringBuilder.Append(",\"inBed\":").Append(val.GetBool("inBed", false) ? "true" : "false"); stringBuilder.Append(",\"hidden\":").Append(flag ? "true" : "false"); if (num3) { stringBuilder.Append(FormattableString.Invariant($",\"x\":{position.x:0.##},\"z\":{position.z:0.##}")); } stringBuilder.Append("}"); entries.Add(stringBuilder.ToString()); } }); return "{\"count\":" + entries.Count + ",\"players\":[" + string.Join(",", entries) + "]}"; } private static string JsonEscape(string s) { if (string.IsNullOrEmpty(s)) { return ""; } return s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", " ") .Replace("\r", " ") .Replace("\t", " "); } public static MapDataServer getInstance() { return __instance; } public void Stop() { broadcastTimer.Dispose(); httpServer.Stop(); } private void ServeStaticFiles(HttpRequestEventArgs e) { HttpListenerRequest request = e.Request; HttpListenerResponse response = e.Response; string text = request.RawUrl; if (text == "/") { text = "/index.html"; } string text2 = text.Split(new char[1] { '/' })[^1]; string key = text2.Split(new char[1] { '.' })[^1]; if (contentTypes.ContainsKey(key)) { byte[] array = new byte[0]; if (fileCache.ContainsKey(text2)) { array = fileCache[text2]; } else { string path = Path.Combine(publicRoot, text2); try { array = File.ReadAllBytes(path); if (WebMapConfig.CACHE_SERVER_FILES) { fileCache.Add(text2, array); } } catch (Exception ex) { ZLog.LogError((object)("WebMap: FAILED TO READ FILE! " + ex.Message)); } } if (array.Length != 0) { response.Headers.Add((HttpResponseHeader)0, "public, max-age=604800, immutable"); response.ContentType = contentTypes[key]; response.StatusCode = 200; response.ContentLength64 = array.Length; response.Close(array, true); } else { response.StatusCode = 404; response.Close(); } } else { response.StatusCode = 404; response.Close(); } } private bool ProcessSpecialRoutes(HttpRequestEventArgs e) { HttpListenerRequest request = e.Request; HttpListenerResponse response = e.Response; string rawUrl = request.RawUrl; switch (rawUrl) { case "/config": { response.Headers.Add((HttpResponseHeader)0, "no-cache"); response.ContentType = "application/json"; response.StatusCode = 200; byte[] bytes = Encoding.UTF8.GetBytes(WebMapConfig.MakeClientConfigJson()); response.ContentLength64 = bytes.Length; response.Close(bytes, true); return true; } case "/map": response.Headers.Add((HttpResponseHeader)0, "public, max-age=604800, immutable"); response.ContentType = "application/octet-stream"; response.StatusCode = 200; response.ContentLength64 = mapImageData.Length; response.Close(mapImageData, true); return true; case "/fog": { response.Headers.Add((HttpResponseHeader)0, "no-cache"); response.ContentType = "image/png"; response.StatusCode = 200; byte[] array = ImageConv.EncodeToPNG(fogTexture); response.ContentLength64 = array.Length; response.Close(array, true); return true; } case "/messages": { response.Headers.Add((HttpResponseHeader)0, "no-cache"); response.ContentType = "applicaion/json"; response.StatusCode = 200; List tosend = new List(); sentMessages.ForEach(delegate(MapMessage message) { tosend.Add(message.ToJson()); }); byte[] bytes = Encoding.UTF8.GetBytes("[" + string.Join(", ", tosend) + "]"); response.ContentLength64 = bytes.Length; response.Close(bytes, true); return true; } case "/players": { response.Headers.Add((HttpResponseHeader)0, "no-cache"); response.ContentType = "application/json"; response.StatusCode = 200; byte[] bytes = Encoding.UTF8.GetBytes(MakePlayersJson()); response.ContentLength64 = bytes.Length; response.Close(bytes, true); return true; } case "/structures": { response.Headers.Add((HttpResponseHeader)0, "no-cache"); response.ContentType = "image/png"; response.StatusCode = 200; byte[] png = StructureMap.GetPng(); response.ContentLength64 = png.Length; response.Close(png, true); return true; } case "/structures/stats": { response.Headers.Add((HttpResponseHeader)0, "no-cache"); response.ContentType = "application/json"; response.StatusCode = 200; byte[] bytes = Encoding.UTF8.GetBytes(StructureMap.GetStats()); response.ContentLength64 = bytes.Length; response.Close(bytes, true); return true; } case "/structures/refresh": { StructureMap.RefreshRequested = true; response.ContentType = "application/json"; response.StatusCode = 202; byte[] bytes = Encoding.UTF8.GetBytes("{\"queued\":true}"); response.ContentLength64 = bytes.Length; response.Close(bytes, true); return true; } case "/pins": { response.Headers.Add((HttpResponseHeader)0, "no-cache"); response.ContentType = "text/csv"; response.StatusCode = 200; string s = string.Join("\n", pins); byte[] bytes = Encoding.UTF8.GetBytes(s); response.ContentLength64 = bytes.Length; response.Close(bytes, true); return true; } default: return false; } } public void Reload() { forceReload = true; } public void ListenAsync() { httpServer.Start(); if (httpServer.IsListening) { ZLog.Log((object)$"WebMap: HTTP Server Listening on port {WebMapConfig.SERVER_PORT}"); } else { ZLog.LogError((object)"WebMap: HTTP Server Failed To Start !!!"); } } public void BroadcastPing(long id, string name, Vector3 position) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) webSocketHandler.Sessions.Broadcast($"ping\n{id}\n{name}\n{FixedValue(position.x)},{FixedValue(position.z)}"); } public void BroadcastMessage(long id, int type, string name, string message) { webSocketHandler.Sessions.Broadcast($"message\n{id}\n{type}\n{name}\n{message}"); } public void AddPin(string id, string pinId, string type, string name, Vector3 position, string pinText) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: 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) pins.Add(id + "," + pinId + "," + type + "," + name + "," + FixedValue(position.x) + "," + FixedValue(position.z) + "," + pinText); webSocketHandler.Sessions.Broadcast("pin\n" + id + "\n" + pinId + "\n" + type + "\n" + name + "\n" + FixedValue(position.x) + "," + FixedValue(position.z) + "\n" + pinText); } public void RemovePin(int idx) { string[] array = pins[idx].Split(new char[1] { ',' }); pins.RemoveAt(idx); webSocketHandler.Sessions.Broadcast("rmpin\n" + array[1]); } public void AddMessage(long id, int type, string name, string message) { newMessages.Add(new MapMessage(id, type, name, message)); } private static string FixedValue(float f) { return f.ToString("F2", CultureInfo.InvariantCulture); } } public class ServerClient { public class RecognizeServerClient { private static bool Postfix(bool result, PlatformUserID platformUserID, ref PlayerInfo playerInfo) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (result) { return result; } PlayerInfo? client = Client; if (!client.HasValue || platformUserID != client.Value.m_userInfo.m_id) { return result; } playerInfo = client.Value; return true; } } public class AddExtraPlayer { private static IEnumerable Transpiler(IEnumerable instructions) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown return new CodeMatcher(instructions, (ILGenerator)null).End().MatchStartBackwards((CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Ldfld, (object)AccessTools.Field(typeof(ZNet), "m_players"), (string)null) }).Advance(-1) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Ldarg_0, (object)null) }) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Ldloc_0, (object)null) }) .InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(AddExtraPlayer), "AddServer", (Type[])null, (Type[])null)) }) .InstructionEnumeration(); } private static void AddServer(ZNet net, ZPackage pkg) { if (!Client.HasValue) { return; } int pos = pkg.GetPos(); try { pkg.SetPos(0); if (IsExtraPlayerAdded(net, pkg.ReadInt())) { pkg.SetPos(pos); return; } pkg.SetPos(0); pkg.Write(net.m_players.Count + 1); Write(pkg); } catch (Exception ex) { try { pkg.SetPos(pos); } catch { } clientFailed = true; client = null; ZLog.LogWarning((object)("WebMap: disabling server chat client after error: " + ex.Message)); } } private static bool IsExtraPlayerAdded(ZNet net, int count) { return count >= net.m_players.Count + 1; } } private static PlayerInfo? client; private static bool clientFailed; public static PlayerInfo? Client { get { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (client.HasValue || clientFailed) { return client; } try { client = CreatePlayerInfo(); } catch (Exception ex) { clientFailed = true; ZLog.LogWarning((object)("WebMap: server chat client unavailable, !pin disabled: " + ex.Message)); } return client; } } private static PlayerInfo CreatePlayerInfo() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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_0057: 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_0067: 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) //IL_0071: Unknown result type (might be due to invalid IL or missing references) return new PlayerInfo { m_name = "Server", m_characterID = new ZDOID(ZDOMan.GetSessionID(), uint.MaxValue), m_userInfo = new CrossNetworkUserInfo { m_id = new PlatformUserID(ZNet.instance.m_steamPlatform, GetId()), m_displayName = "Server" }, m_publicPosition = false, m_position = Vector3.zero }; } private static string GetId() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) try { return ((object)SteamGameServer.GetSteamID()/*cast due to .constrained prefix*/).ToString(); } catch (Exception) { return "0"; } } public unsafe static void Write(ZPackage pkg) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_004e: 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) PlayerInfo? val = Client; if (val.HasValue) { PlayerInfo value = val.Value; pkg.Write(value.m_name); pkg.Write(value.m_characterID); pkg.Write(((object)(*(PlatformUserID*)(&value.m_userInfo.m_id))/*cast due to .constrained prefix*/).ToString()); pkg.Write(value.m_userInfo.m_displayName); pkg.Write(false); } } } internal static class StructureMap { private struct Cell { public int n; public int r; public int g; public int b; } private const float SweepInterval = 120f; private const int ZdosPerFrame = 3000; private static Texture2D texture; private static readonly Dictionary cells = new Dictionary(); private static readonly Dictionary paletteCache = new Dictionary(); private static Color32[] buf; private static string statsJson = "{\"total\":0,\"prefabs\":[]}"; private static byte[] png; private static bool pngStale = true; private static bool sweeping; public static volatile bool RefreshRequested; public static int LastCount { get; private set; } public static int LastScanned { get; private set; } private static Color32 MaterialOf(int prefabHash) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) if (paletteCache.TryGetValue(prefabHash, out var value)) { return value; } string text = null; try { GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefabHash) : null); if ((Object)(object)val != (Object)null) { text = ((Object)val).name.ToLowerInvariant(); } } catch { } Color32 val2 = default(Color32); if (text == null) { ((Color32)(ref val2))..ctor((byte)150, (byte)120, (byte)90, byte.MaxValue); } else if (text.Contains("portal")) { ((Color32)(ref val2))..ctor((byte)90, (byte)200, (byte)210, byte.MaxValue); } else if (text.Contains("blackmarble")) { ((Color32)(ref val2))..ctor((byte)70, (byte)70, (byte)85, byte.MaxValue); } else if (text.Contains("stone") || text.Contains("grausten")) { ((Color32)(ref val2))..ctor((byte)150, (byte)150, (byte)145, byte.MaxValue); } else if (text.Contains("iron") || text.Contains("metal")) { ((Color32)(ref val2))..ctor((byte)120, (byte)130, (byte)145, byte.MaxValue); } else if (text.Contains("darkwood")) { ((Color32)(ref val2))..ctor((byte)90, (byte)66, (byte)46, byte.MaxValue); } else if (text.Contains("roof") || text.Contains("straw") || text.Contains("thatch")) { ((Color32)(ref val2))..ctor((byte)196, (byte)160, (byte)86, byte.MaxValue); } else if (text.Contains("fire") || text.Contains("hearth") || text.Contains("forge")) { ((Color32)(ref val2))..ctor((byte)214, (byte)122, (byte)58, byte.MaxValue); } else { ((Color32)(ref val2))..ctor((byte)150, (byte)108, (byte)66, byte.MaxValue); } paletteCache[prefabHash] = val2; return val2; } private static void Init() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown if (!((Object)(object)texture != (Object)null)) { int tEXTURE_SIZE = WebMapConfig.TEXTURE_SIZE; texture = new Texture2D(tEXTURE_SIZE, tEXTURE_SIZE, (TextureFormat)4, false); buf = (Color32[])(object)new Color32[tEXTURE_SIZE * tEXTURE_SIZE]; texture.SetPixels32(buf); texture.Apply(); } } public static IEnumerator Loop() { Init(); yield return (object)new WaitForSeconds(30f); while (true) { yield return Sweep(); for (float waited = 0f; waited < 120f; waited += 1f) { if (RefreshRequested) { break; } yield return (object)new WaitForSeconds(1f); } RefreshRequested = false; } } private static IEnumerator Sweep() { if (sweeping) { yield break; } sweeping = true; Init(); int size = WebMapConfig.TEXTURE_SIZE; int half = size / 2; cells.Clear(); Array.Clear(buf, 0, buf.Length); Dictionary byPrefab = new Dictionary(); List list = null; try { list = new List(ZDOMan.instance.m_objectsByID.Values); } catch { } if (list == null) { sweeping = false; yield break; } int found = 0; int seen = 0; foreach (ZDO item in list) { seen++; if (item != null) { long num = 0L; try { num = item.GetLong(ZDOVars.s_creator, 0L); } catch { } if (num != 0L) { Vector3 position = item.GetPosition(); int num2 = Mathf.RoundToInt(position.x / (float)WebMapConfig.PIXEL_SIZE + (float)half); int num3 = Mathf.RoundToInt(position.z / (float)WebMapConfig.PIXEL_SIZE + (float)half); if (num2 >= 0 && num3 >= 0 && num2 < size && num3 < size) { int key = num3 * size + num2; int num4 = 0; try { num4 = item.GetPrefab(); } catch { } Color32 val = MaterialOf(num4); cells.TryGetValue(key, out var value); value.n++; value.r += val.r; value.g += val.g; value.b += val.b; cells[key] = value; found++; byPrefab.TryGetValue(num4, out var value2); byPrefab[num4] = value2 + 1; } } } if (seen % 3000 == 0) { yield return null; } } yield return Render(size); LastCount = found; LastScanned = seen; statsJson = BuildStats(byPrefab, found, seen); pngStale = true; sweeping = false; ZLog.Log((object)$"WebMap: structures sweep -> {found} placed pieces from {seen} zdos"); } private static IEnumerator Render(int size) { int done = 0; foreach (KeyValuePair cell in cells) { Cell value = cell.Value; if (value.n > 0) { byte b = (byte)Mathf.Clamp(120 + value.n * 20, 120, 255); buf[cell.Key] = new Color32((byte)(value.r / value.n), (byte)(value.g / value.n), (byte)(value.b / value.n), b); int num = done + 1; done = num; if ((num & 0x3FF) == 0) { yield return null; } } } List> list = new List>(); Color32 value3 = default(Color32); foreach (KeyValuePair cell2 in cells) { Cell value2 = cell2.Value; ((Color32)(ref value3))..ctor((byte)(value2.r / value2.n), (byte)(value2.g / value2.n), (byte)(value2.b / value2.n), (byte)Mathf.Clamp(55 + value2.n * 10, 55, 140)); int key = cell2.Key; list.Add(new KeyValuePair(key - 1, value3)); list.Add(new KeyValuePair(key + 1, value3)); list.Add(new KeyValuePair(key - size, value3)); list.Add(new KeyValuePair(key + size, value3)); } foreach (KeyValuePair item in list) { int key2 = item.Key; if (key2 >= 0 && key2 < buf.Length && !cells.ContainsKey(key2) && buf[key2].a < item.Value.a) { buf[key2] = item.Value; } } yield return null; texture.SetPixels32(buf); texture.Apply(); } private static string BuildStats(Dictionary byPrefab, int found, int seen) { List> list = new List>(byPrefab); list.Sort((KeyValuePair a, KeyValuePair b) => b.Value.CompareTo(a.Value)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\"total\":").Append(found).Append(",\"scanned\":") .Append(seen) .Append(",\"distinct\":") .Append(byPrefab.Count) .Append(",\"prefabs\":["); int num = 0; foreach (KeyValuePair item in list) { if (num >= 25) { break; } string text = item.Key.ToString(); try { GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(item.Key) : null); if ((Object)(object)val != (Object)null) { text = ((Object)val).name; } } catch { } if (num > 0) { stringBuilder.Append(","); } stringBuilder.Append("{\"name\":\"").Append(text.Replace("\"", "")).Append("\",\"count\":") .Append(item.Value) .Append("}"); num++; } stringBuilder.Append("]}"); return stringBuilder.ToString(); } public static string GetStats() { return statsJson; } public static byte[] GetPng() { if ((Object)(object)texture == (Object)null) { return new byte[0]; } if (pngStale || png == null) { png = ImageConv.EncodeToPNG(texture); pngStale = false; } return png; } } [BepInPlugin("com.github.h0tw1r3.valheim.webmap", "WebMap", "2.7.1")] public class WebMap : BaseUnityPlugin { [HarmonyPatch(typeof(ZoneSystem), "Start")] private class ZoneSystemPatch { private static readonly Color DeepWaterColor = new Color(0.36105883f, 0.36105883f, 22f / 51f); private static readonly Color ShallowWaterColor = new Color(0.574f, 0.50709206f, 0.47892025f); private static readonly Color ShoreColor = new Color(21f / 106f, 0.12241901f, 0.1503943f); private static Color GetMaskColor(float wx, float wy, float height, Biome biome) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Invalid comparison between Unknown and I4 //IL_0043: 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_0063: Invalid comparison between Unknown and I4 //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Invalid comparison between Unknown and I4 //IL_006c: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Invalid comparison between Unknown and I4 //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) Color result = default(Color); ((Color)(ref result))..ctor(0f, 0f, 0f, 0f); Color result2 = default(Color); ((Color)(ref result2))..ctor(1f, 0f, 0f, 0f); if (height < ZoneSystem.instance.m_waterLevel) { return result; } if ((int)biome == 1) { if (!WorldGenerator.InForest(new Vector3(wx, 0f, wy))) { return result; } return result2; } if ((int)biome == 16) { if (WorldGenerator.GetForestFactor(new Vector3(wx, 0f, wy)) >= 0.8f) { return result; } return result2; } if ((int)biome == 8 || (int)biome == 512) { return result2; } return result; } private static Color GetPixelColor(Biome biome) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Invalid comparison between Unknown and I4 //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Invalid comparison between Unknown and I4 //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected I4, but got Unknown //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Invalid comparison between Unknown and I4 //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Invalid comparison between Unknown and I4 //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: 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_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Invalid comparison between Unknown and I4 //IL_010c: 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_00f7: Invalid comparison between Unknown and I4 //IL_0106: 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_00e5: Invalid comparison between Unknown and I4 //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Invalid comparison between Unknown and I4 //IL_0112: 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_0103: Unknown result type (might be due to invalid IL or missing references) Color result = default(Color); ((Color)(ref result))..ctor(0.573f, 0.655f, 0.361f); Color result2 = default(Color); ((Color)(ref result2))..ctor(0.639f, 0.447f, 0.345f); Color result3 = default(Color); ((Color)(ref result3))..ctor(1f, 1f, 1f); Color result4 = default(Color); ((Color)(ref result4))..ctor(0.42f, 0.455f, 0.247f); Color result5 = default(Color); ((Color)(ref result5))..ctor(0.906f, 0.671f, 0.47f); Color result6 = default(Color); ((Color)(ref result6))..ctor(0.69f, 0.192f, 0.192f); Color result7 = default(Color); ((Color)(ref result7))..ctor(1f, 1f, 1f); Color result8 = default(Color); ((Color)(ref result8))..ctor(0.36f, 0.22f, 0.4f); if ((int)biome <= 16) { switch (biome - 1) { default: if ((int)biome != 8) { if ((int)biome != 16) { break; } return result5; } return result4; case 0: return result; case 1: return result2; case 3: return result3; case 2: break; } } else if ((int)biome <= 64) { if ((int)biome == 32) { return result6; } if ((int)biome == 64) { return result7; } } else { if ((int)biome == 256) { return Color.white; } if ((int)biome == 512) { return result8; } } return Color.white; } private static void Postfix(ZoneSystem __instance) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: 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_00dd: 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_00e4: 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_0101: 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_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0329: Unknown result type (might be due to invalid IL or missing references) //IL_032e: Unknown result type (might be due to invalid IL or missing references) //IL_033b: Expected O, but got Unknown //IL_01f8: 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_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0215: 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) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0223: 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_0227: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_029e: 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_02a5: 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_02ac: 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) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_02c8: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: 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_02de: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_02fc: 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_0308: Unknown result type (might be due to invalid IL or missing references) instance.NewWorld(); if (mapDataServer.mapImageData != null) { ZLog.Log((object)"WebMap: MAP ALREADY BUILT!"); return; } ZLog.Log((object)"WebMap: BUILD MAP!"); int num = WebMapConfig.TEXTURE_SIZE / 2; float num2 = (float)WebMapConfig.PIXEL_SIZE / 2f; Color32[] array = (Color32[])(object)new Color32[WebMapConfig.TEXTURE_SIZE * WebMapConfig.TEXTURE_SIZE]; Color32[] array2 = (Color32[])(object)new Color32[WebMapConfig.TEXTURE_SIZE * WebMapConfig.TEXTURE_SIZE]; float[] array3 = new float[WebMapConfig.TEXTURE_SIZE * WebMapConfig.TEXTURE_SIZE]; Color val = default(Color); for (int i = 0; i < WebMapConfig.TEXTURE_SIZE; i++) { for (int j = 0; j < WebMapConfig.TEXTURE_SIZE; j++) { float num3 = (float)(j - num) * (float)WebMapConfig.PIXEL_SIZE + num2; float num4 = (float)(i - num) * (float)WebMapConfig.PIXEL_SIZE + num2; Biome biome = WorldGenerator.instance.GetBiome(num3, num4, 0.02f, false); float biomeHeight = WorldGenerator.instance.GetBiomeHeight(biome, num3, num4, ref val, false, true); array[i * WebMapConfig.TEXTURE_SIZE + j] = Color32.op_Implicit(GetPixelColor(biome)); array2[i * WebMapConfig.TEXTURE_SIZE + j] = Color32.op_Implicit(GetMaskColor(num3, num4, biomeHeight, biome)); array3[i * WebMapConfig.TEXTURE_SIZE + j] = biomeHeight; } } float waterLevel = ZoneSystem.instance.m_waterLevel; Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(-0.57735f, 0.57735f, 0.57735f); Color[] array4 = (Color[])(object)new Color[array.Length]; for (int k = 0; k < array.Length; k++) { float num5 = array3[k]; int num6 = k - WebMapConfig.TEXTURE_SIZE; if (num6 < 0) { num6 = k; } int num7 = k + WebMapConfig.TEXTURE_SIZE; if (num7 > array.Length - 1) { num7 = k; } int num8 = k + 1; if (num8 > array.Length - 1) { num8 = k; } int num9 = k - 1; if (num9 < 0) { num9 = k; } float num10 = array3[num6]; float num11 = array3[num8]; float num12 = array3[num9]; float num13 = array3[num7]; Vector3 val3 = new Vector3(2f, 0f, num11 - num12); Vector3 normalized = ((Vector3)(ref val3)).normalized; val3 = new Vector3(0f, 2f, num10 - num13); Vector3 normalized2 = ((Vector3)(ref val3)).normalized; float num14 = Vector3.Dot(Vector3.Cross(normalized, normalized2), val2) * 0.25f + 0.75f; float num15 = Mathf.Clamp(num5 - waterLevel, 0f, 1f); float num16 = Mathf.Clamp((num5 - waterLevel + 2.5f) * 0.5f, 0f, 1f); float num17 = Mathf.Clamp((num5 - waterLevel + 12.5f) * 0.1f, 0f, 1f); Color32 val4 = array[k]; Color val5 = Color.Lerp(ShoreColor, Color32.op_Implicit(val4), num15); val5 = Color.Lerp(ShallowWaterColor, val5, num16); val5 = Color.Lerp(DeepWaterColor, val5, num17); array4[k] = new Color(val5.r * num14, val5.g * num14, val5.b * num14, val5.a); } Texture2D val6 = new Texture2D(WebMapConfig.TEXTURE_SIZE, WebMapConfig.TEXTURE_SIZE, (TextureFormat)4, false); val6.SetPixels(array4); byte[] array5 = ImageConv.EncodeToPNG(val6); mapDataServer.mapImageData = array5; try { File.WriteAllBytes(Path.Combine(worldDataPath, "map.png"), array5); ZLog.Log((object)"WebMap: BUILDING MAP DONE!"); } catch (Exception ex) { ZLog.LogError((object)("WebMap: FAILED TO WRITE MAP FILE! " + ex.Message)); } } } [HarmonyPatch(typeof(ZoneSystem), "Load")] private class ZoneSystemLoadPatch { private static void Postfix() { //IL_000a: 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_0019: 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) LocationInstance val = default(LocationInstance); if (ZoneSystem.instance.FindClosestLocation("StartTemple", Vector3.zero, ref val)) { WebMapConfig.WORLD_START_POS = val.m_position; ZLog.Log((object)("WebMap: starting point " + ((object)Unsafe.As(ref WebMapConfig.WORLD_START_POS)/*cast due to .constrained prefix*/).ToString())); } else { ZLog.LogError((object)"WebMap: failed to find starting point"); } instance.Online(); mapDataServer.ListenAsync(); } } [HarmonyPatch(typeof(ZNet), "Start")] private class ZNetPatchStart { private static void Postfix(List ___m_peers) { mapDataServer.players = ___m_peers; } } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ZNetPatchShutdown { private static void Postfix() { mapDataServer.Stop(); instance.NotifyOffline(); } } [HarmonyPatch(typeof(ZNet), "SetServer")] private class ZNetPatchSetServer { private static void Postfix(bool server, bool openServer, bool publicServer, string serverName, string password, World world) { instance.SetServerInfo(openServer, publicServer, serverName, password, world.m_name, world.m_seedName); } } [HarmonyPatch(typeof(ZNet), "Disconnect")] private class ZNetPatchDisconnect { private static void Prefix(ref ZNetPeer peer) { if (!peer.m_server && !string.IsNullOrEmpty(peer.m_playerName)) { instance.NotifyLeave(peer); } } } [HarmonyPatch(typeof(ZRoutedRpc), "AddPeer")] private class ZRoutedRpcAddPeerPatch { private static void Postfix(ZNetPeer peer) { if (!peer.m_server && !string.IsNullOrEmpty(peer.m_playerName)) { instance.NotifyJoin(peer); } } } [HarmonyPatch(typeof(ZRoutedRpc), "HandleRoutedRPC")] private class ZRoutedRpcPatch { private static string[] ignoreRpc = new string[4] { "DestroyZDO", "SetEvent", "OnTargeted", "Step" }; private static void Postfix(ref ZRoutedRpc __instance, ref RoutedRPCData data) { Observe(ref __instance, ref data); } internal static void Observe(ref ZRoutedRpc __instance, ref RoutedRPCData data) { //IL_012f: 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) //IL_013e: 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_0153: Expected O, but got Unknown //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Expected O, but got Unknown //IL_044d: 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_052f: Unknown result type (might be due to invalid IL or missing references) //IL_0536: Expected O, but got Unknown //IL_0538: Unknown result type (might be due to invalid IL or missing references) //IL_053d: Unknown result type (might be due to invalid IL or missing references) //IL_0548: Unknown result type (might be due to invalid IL or missing references) //IL_054f: Expected O, but got Unknown //IL_0570: Unknown result type (might be due to invalid IL or missing references) //IL_057c: Unknown result type (might be due to invalid IL or missing references) //IL_05e1: Unknown result type (might be due to invalid IL or missing references) string methodName = StringExtensionMethods_Patch.GetStableHashName(data?.m_methodHash ?? 0); if (Array.Exists(ignoreRpc, (string x) => x == methodName)) { return; } if (WebMapConfig.DEBUG) { ZLog.Log((object)("HandleRoutedRPC: " + methodName)); } ZNetPeer peer = ZNet.instance.GetPeer(data.m_senderPeerID); string steamid = ""; try { steamid = peer.m_rpc.GetSocket().GetHostName(); } catch { } if (data?.m_methodHash == sayMethodHash || data?.m_methodHash == StringExtensionMethods.GetStableHashCode("Say")) { sayMethodHash = data.m_methodHash; ZLog.Log((object)$"WebMap: chat RPC observed from peer {data.m_senderPeerID}"); try { Vector3 position = ZDOMan.instance.GetZDO(peer.m_characterID).GetPosition(); ZPackage val = new ZPackage(data.m_parameters.GetArray()); int num = val.ReadInt(); UserInfo val2 = new UserInfo(); val2.Deserialize(ref val); string text = val.ReadString() ?? ""; text = text.Trim(); if (text.ToUpper().StartsWith("!PIN")) { string[] messageParts = text.Split(new char[1] { ' ' }); string type = "dot"; int num2 = 1; if (messageParts.Length > 1 && Array.Exists(ALLOWED_PINS, (string e) => e == messageParts[1].ToLower())) { type = messageParts[1].ToLower(); num2 = 2; } string text2 = ""; if (num2 < messageParts.Length) { text2 = string.Join(" ", messageParts, num2, messageParts.Length - num2); } if (text2.Length > 20) { text2 = text2.Substring(0, 20); } string pinText = Regex.Replace(text2, "[^a-zA-Z0-9 ]", ""); long num3 = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds(); string pinId = $"{num3}-{Random.Range(1000, 9999)}"; mapDataServer.AddPin(steamid, pinId, type, val2.Name, position, pinText); for (int num4 = mapDataServer.pins.FindAll((string pin) => pin.StartsWith(steamid)).Count - WebMapConfig.MAX_PINS_PER_USER; num4 > 0; num4--) { int idx = mapDataServer.pins.FindIndex((string pin) => pin.StartsWith(steamid)); mapDataServer.RemovePin(idx); } SavePins(); } else if (text.ToUpper().StartsWith("!UNDOPIN")) { int num5 = mapDataServer.pins.FindLastIndex((string pin) => pin.StartsWith(steamid)); if (num5 > -1) { mapDataServer.RemovePin(num5); SavePins(); } } else if (text.ToUpper().StartsWith("!DELETEPIN")) { string[] array = text.Split(new char[1] { ' ' }); string pinText2 = ""; if (array.Length > 1) { pinText2 = string.Join(" ", array, 1, array.Length - 1); } int num6 = mapDataServer.pins.FindLastIndex(delegate(string pin) { string[] array2 = pin.Split(new char[1] { ',' }); return array2[0] == steamid && array2[^1] == pinText2; }); if (num6 > -1) { mapDataServer.RemovePin(num6); SavePins(); } } else { if (num != 0) { mapDataServer.AddMessage(data.m_senderPeerID, num, val2.Name, text); } ZLog.Log((object)$"WebMap: (say) {position} | {num} | {val2.Name} | {text}"); } return; } catch (Exception ex) { ZLog.LogWarning((object)("WebMap: failed handling a chat message: " + ex)); return; } } if (data?.m_methodHash != chatMessageMethodHash && data?.m_methodHash != StringExtensionMethods.GetStableHashCode("ChatMessage")) { return; } chatMessageMethodHash = data.m_methodHash; try { ZPackage val3 = new ZPackage(data.m_parameters.GetArray()); Vector3 val4 = val3.ReadVector3(); int num7 = val3.ReadInt(); UserInfo val5 = new UserInfo(); val5.Deserialize(ref val3); if (num7 == 3) { mapDataServer.BroadcastPing(data.m_senderPeerID, val5.Name, val4); ZLog.Log((object)$"WebMap: (ping) {val4} | {num7} | {val5.Name}"); return; } string text3 = val3.ReadString() ?? ""; text3 = text3.Trim(); mapDataServer.AddMessage(data.m_senderPeerID, num7, val5.Name, text3); ZLog.Log((object)$"WebMap: (chat) {val4} | {num7} | {val5.Name} | {text3}"); } catch (Exception ex2) { if (WebMapConfig.DEBUG) { ZLog.LogError((object)ex2.ToString()); } } } } public const string GUID = "com.github.h0tw1r3.valheim.webmap"; public const string NAME = "WebMap"; public const string VERSION = "2.7.1"; private static readonly string[] ALLOWED_PINS = new string[5] { "dot", "fire", "mine", "house", "cave" }; public DiscordWebHook discordWebHook; public static MapDataServer mapDataServer; public static string worldDataPath; public static string mapDataPath; public static string pluginPath; public static int sayMethodHash = 0; public static int chatMessageMethodHash = 0; public static bool fogTextureNeedsSaving; public static string currentWorldName; public static Dictionary serverInfo; private static Harmony harmony; public static WebMap instance; public void Awake() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown instance = this; harmony = new Harmony("com.github.h0tw1r3.valheim.webmap"); harmony.PatchAll(Assembly.GetExecutingAssembly()); pluginPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); mapDataPath = Path.Combine(pluginPath ?? string.Empty, "map_data"); Directory.CreateDirectory(mapDataPath); WebMapConfig.ReadConfigFile(((BaseUnityPlugin)this).Config); discordWebHook = new DiscordWebHook(WebMapConfig.DISCORD_WEBHOOK); } public void OnDestroy() { ((BaseUnityPlugin)this).Config.Save(); } public void Online() { StaticCoroutine.Start(SaveFogTextureLoop()); StaticCoroutine.Start(UpdateFogTextureLoop()); StaticCoroutine.Start(StructureMap.Loop()); NotifyOnline(); } public void SetServerInfo(bool openServer, bool publicServer, string serverName, string password, string worldName, string worldSeed) { serverInfo = new Dictionary(); serverInfo.Add("openServer", openServer); serverInfo.Add("publicServer", publicServer); serverInfo.Add("serverName", serverName); serverInfo.Add("password", password); serverInfo.Add("worldName", worldName); serverInfo.Add("worldSeed", worldSeed); } public void NotifyOnline() { discordWebHook.SendMessage(string.Format("\ud83c\udfae **{0}** is *online* \ud83d\udfe2\n\ud83d\udcbb {1}:{2}\n\ud83d\udd11 {3}\n\ud83d\uddfa {4}", serverInfo["serverName"], AccessTools.Method(typeof(ZNet), "GetServerIP", (Type[])null, (Type[])null).Invoke(ZNet.instance, new object[0]), ZNet.instance.GetHostPort(), serverInfo["password"], WebMapConfig.URL)); } public void NotifyOffline() { discordWebHook.SendMessage(string.Format("\ud83c\udfae **{0}** is *offline* \ud83d\udd34", serverInfo["serverName"])); } public void NotifyJoin(ZNetPeer peer) { string text = "player _" + peer.m_playerName + "_ joined"; discordWebHook.SendMessage(string.Format("\ud83c\udfae **{0}** {1}", serverInfo["serverName"], text)); mapDataServer.AddMessage(peer.m_uid, 1, "Server", text); } public void NotifyLeave(ZNetPeer peer) { string text = "player _" + peer.m_playerName + "_ left"; discordWebHook.SendMessage(string.Format("\ud83c\udfae **{0}** {1}", serverInfo["serverName"], text)); MessageHud.instance.MessageAll((MessageType)2, text); mapDataServer.AddMessage(peer.m_uid, 1, "Server", text); } public void NewWorld() { //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) string worldName = WebMapConfig.GetWorldName(); bool flag = currentWorldName != worldName; worldDataPath = Path.Combine(mapDataPath, WebMapConfig.GetWorldName()); Directory.CreateDirectory(worldDataPath); if (mapDataServer == null) { ZLog.Log((object)("WebMap: loading existing world: #" + worldName)); mapDataServer = new MapDataServer(); } else if (flag) { ZLog.Log((object)("WebMap: loading a new world! old: #" + currentWorldName + " new: #" + worldName)); } currentWorldName = worldName; string path = Path.Combine(worldDataPath, "map.png"); try { mapDataServer.mapImageData = File.ReadAllBytes(path); } catch (Exception ex) { ZLog.LogError((object)("WebMap: Failed to read map image data from disk. " + ex.Message)); } string path2 = Path.Combine(worldDataPath, "fog.png"); try { Texture2D val = new Texture2D(WebMapConfig.TEXTURE_SIZE, WebMapConfig.TEXTURE_SIZE); byte[] data = File.ReadAllBytes(path2); ImageConv.LoadImage(val, data); mapDataServer.fogTexture = val; } catch (Exception ex2) { ZLog.LogWarning((object)("WebMap: Failed to read fog image data from disk... Making new fog image..." + ex2.Message)); Texture2D val2 = new Texture2D(WebMapConfig.TEXTURE_SIZE, WebMapConfig.TEXTURE_SIZE, (TextureFormat)63, false); Color32[] array = (Color32[])(object)new Color32[WebMapConfig.TEXTURE_SIZE * WebMapConfig.TEXTURE_SIZE]; for (int i = 0; i < array.Length; i++) { array[i] = Color32.op_Implicit(Color.black); } val2.SetPixels32(array); byte[] bytes = ImageConv.EncodeToPNG(val2); mapDataServer.fogTexture = val2; try { File.WriteAllBytes(path2, bytes); } catch (Exception ex3) { ZLog.LogError((object)("WebMap: FAILED TO WRITE FOG FILE! " + ex3.Message)); } } string path3 = Path.Combine(worldDataPath, "pins.csv"); try { string[] collection = File.ReadAllLines(path3); mapDataServer.pins = new List(collection); } catch (Exception ex4) { ZLog.LogError((object)("WebMap: Failed to read pins.csv from disk. " + ex4.Message)); } if (flag) { mapDataServer.Reload(); } } public IEnumerator UpdateFogTextureLoop() { while (true) { yield return (object)new WaitForSeconds(WebMapConfig.UPDATE_FOG_TEXTURE_INTERVAL); UpdateFogTexture(); } } public void UpdateFogTexture() { int pixelExploreRadius = (int)Mathf.Ceil(WebMapConfig.EXPLORE_RADIUS / (float)WebMapConfig.PIXEL_SIZE); int pixelExploreRadiusSquared = pixelExploreRadius * pixelExploreRadius; int halfTextureSize = WebMapConfig.TEXTURE_SIZE / 2; mapDataServer.players.ForEach(delegate(ZNetPeer player) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) if (player.m_publicRefPos || WebMapConfig.ALWAYS_MAP || WebMapConfig.ALWAYS_VISIBLE) { ZDO val = null; try { val = ZDOMan.instance.GetZDO(player.m_characterID); } catch { } if (val != null) { Vector3 position = val.GetPosition(); int num = Mathf.RoundToInt(position.x / (float)WebMapConfig.PIXEL_SIZE + (float)halfTextureSize); int num2 = Mathf.RoundToInt(position.z / (float)WebMapConfig.PIXEL_SIZE + (float)halfTextureSize); for (int i = num2 - pixelExploreRadius; i <= num2 + pixelExploreRadius; i++) { for (int j = num - pixelExploreRadius; j <= num + pixelExploreRadius; j++) { if (i >= 0 && j >= 0 && i < WebMapConfig.TEXTURE_SIZE && j < WebMapConfig.TEXTURE_SIZE) { int num3 = num - j; int num4 = num2 - i; if (num3 * num3 + num4 * num4 < pixelExploreRadiusSquared && mapDataServer.fogTexture.GetPixel(j, i) != Color.white) { if (WebMapConfig.DEBUG && !fogTextureNeedsSaving) { ZLog.Log((object)"Fog needs saving"); } fogTextureNeedsSaving = true; mapDataServer.fogTexture.SetPixel(j, i, Color.white); } } } } } } }); } public IEnumerator SaveFogTextureLoop() { while (true) { yield return (object)new WaitForSeconds(WebMapConfig.SAVE_FOG_TEXTURE_INTERVAL); SaveFogTexture(); } } public void SaveFogTexture() { if (mapDataServer.players.Count > 0 && fogTextureNeedsSaving) { byte[] bytes = ImageConv.EncodeToPNG(mapDataServer.fogTexture); if (WebMapConfig.DEBUG) { ZLog.Log((object)"Saving Fog"); } try { File.WriteAllBytes(Path.Combine(worldDataPath, "fog.png"), bytes); fogTextureNeedsSaving = false; } catch (Exception ex) { ZLog.LogError((object)("WebMap: FAILED TO WRITE FOG FILE! " + ex.Message)); } } } public static void SavePins() { string path = Path.Combine(worldDataPath, "pins.csv"); try { File.WriteAllLines(path, mapDataServer.pins); } catch (Exception ex) { ZLog.Log((object)("WebMap: FAILED TO WRITE PINS FILE! " + ex.Message)); } } } public class StaticCoroutine { private class StaticCoroutineRunner : MonoBehaviour { } private static StaticCoroutineRunner runner; public static Coroutine Start(IEnumerator coroutine) { EnsureRunner(); return ((MonoBehaviour)runner).StartCoroutine(coroutine); } private static void EnsureRunner() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)runner == (Object)null) { runner = new GameObject("[Static Coroutine Runner]").AddComponent(); Object.DontDestroyOnLoad((Object)(object)((Component)runner).gameObject); } } } } namespace WebMap.Patches { [HarmonyPatch] internal class StringExtensionMethods_Patch { internal static Dictionary stablehashNames = new Dictionary(); internal static Dictionary stablehashNamesAnim = new Dictionary(); internal static Dictionary stablehashLookup = new Dictionary(); internal static Dictionary stablehashLookupAnim = new Dictionary(); [HarmonyPatch(typeof(StringExtensionMethods), "GetStableHashCode")] [HarmonyPostfix] public static void GetStableHashCode(string str, int __result) { if (str != null) { if (!stablehashNames.ContainsKey(__result)) { stablehashNames[__result] = str; } stablehashLookup[str] = __result; } } [HarmonyPatch(typeof(ZSyncAnimation), "GetHash")] [HarmonyPrefix] public static void GetAnimHash(string name, ref int __result, ref bool __runOriginal) { if (stablehashLookupAnim.TryGetValue(name, out __result)) { __runOriginal = false; } else { __runOriginal = true; } } [HarmonyPatch(typeof(ZSyncAnimation), "GetHash")] [HarmonyPostfix] public static void AddAnimHash(string name, ref int __result, ref bool __runOriginal) { if (__runOriginal) { stablehashNamesAnim[__result] = name; stablehashLookupAnim[name] = __result; if (WebMapConfig.DEBUG) { ZLog.Log((object)$"First GetAnimHash: {name} -> {__result}"); } } } public static string GetStableHashName(int code) { if (stablehashNames.TryGetValue(code, out var value)) { return value; } if (stablehashNamesAnim.TryGetValue(code - 438569, out value)) { return value + " (A)"; } return code.ToString(); } } [HarmonyPatch] internal class ZRoutedRpc_Patch { private static string[] ignoreRpc = new string[4] { "DestroyZDO", "SetEvent", "OnTargeted", "Step" }; [HarmonyPatch(typeof(ZRoutedRpc), "InvokeRoutedRPC", new Type[] { typeof(long), typeof(ZDOID), typeof(string), typeof(object[]) })] [HarmonyPrefix] private static void InvokeRoutedRPC(ref ZRoutedRpc __instance, ref long targetPeerID, ZDOID targetZDO, string methodName, params object[] parameters) { if (WebMapConfig.DEBUG && !Array.Exists(ignoreRpc, (string x) => x == methodName)) { ZLog.Log((object)("RoutedRPC Invoking: " + methodName + " " + StringExtensionMethods.GetStableHashCode(methodName))); } if (WebMapConfig.TEST && methodName == "DiscoverLocationRespons") { ZLog.Log((object)("TEST: Sending discovered location to everyone: " + methodName + " " + parameters[0]?.ToString() + " " + parameters[1]?.ToString() + " " + parameters[2]?.ToString() + " " + parameters[3])); targetPeerID = 0L; } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { internal IgnoresAccessChecksToAttribute(string assemblyName) { } } }