using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Photon.Pun;
using TMPro;
using ThunderstoreAPI;
using UnityEngine;
using UnityEngine.Localization.PropertyVariants;
using UnityEngine.UI;
using UnityEngine.UIElements.Collections;
using Zorro.Core;
using spook.tube;
using spook.tube.Uploaders;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("spook.tube")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("spook.tube")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("spook.tube")]
[assembly: AssemblyTitle("spook.tube")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.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;
}
}
}
[HarmonyPatch(typeof(UploadCompleteUI), "PlayVideo")]
internal class VideoEndedUIInjection
{
[HarmonyPostfix]
private static void Postfix(UploadCompleteUI __instance)
{
Plugin.CreateUploadButton();
}
}
namespace spook.tube
{
internal class HTTPServer
{
private Thread thread;
private HttpListener listener;
private int port;
public HTTPServer(int port)
{
this.port = port;
}
public void Start()
{
thread = new Thread((ThreadStart)delegate
{
WorkerTask();
});
thread.Start();
}
private void WorkerTask()
{
listener = new HttpListener();
listener.Prefixes.Add($"http://localhost:{port}/");
listener.Start();
Plugin.Logger.LogInfo((object)$"Listening on port :{port}");
while (true)
{
HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
using HttpListenerResponse httpListenerResponse = context.Response;
Plugin.Logger.LogInfo((object)("[Webserver] Handling request for " + request.HttpMethod + " : " + request.Url?.ToString() + " : " + request.UserAgent));
httpListenerResponse.Headers.Set("Access-Control-Allow-Origin", "*");
httpListenerResponse.Headers.Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
httpListenerResponse.StatusCode = 200;
httpListenerResponse.StatusDescription = "status ok";
string httpMethod = request.HttpMethod;
string text = httpMethod;
if (text == "POST")
{
string token;
using (StreamReader streamReader = new StreamReader(request.InputStream, request.ContentEncoding))
{
token = streamReader.ReadToEnd();
}
Plugin.Logger.LogInfo((object)"Received token from HTTP server, forwarding to uploader...");
Plugin.uploader.HandleLogin(token);
}
else
{
httpListenerResponse.Headers.Set("Content-Type", "text/plain");
string s = "spook.tube Content Warning Mod server";
byte[] bytes = Encoding.UTF8.GetBytes(s);
httpListenerResponse.ContentLength64 = bytes.Length;
using Stream stream = httpListenerResponse.OutputStream;
stream.Write(bytes, 0, bytes.Length);
}
httpListenerResponse.Close();
}
}
}
[BepInPlugin("spook.tube", "spook.tube", "1.0.0")]
[ContentWarningPlugin("spook.tube", "spook.tube", true)]
public class Plugin : BaseUnityPlugin
{
private Harmony harmony;
public static ConfigEntry<string> URL;
public static Uploader uploader;
public static string modpackID;
public static Action onFinishAuth;
internal static ManualLogSource Logger;
private void Awake()
{
//IL_0073: Unknown result type (might be due to invalid IL or missing references)
//IL_007d: Expected O, but got Unknown
Logger = ((BaseUnityPlugin)this).Logger;
URL = ((BaseUnityPlugin)this).Config.Bind<string>("Networking", "spook.tube Host", "https://spook.tube", "A PeerTube-compatible instance");
LoadModpackID();
Logger.LogInfo((object)"spook.tube has loaded.");
if (modpackID != null)
{
Logger.LogWarning((object)("Using modpack ID: " + modpackID));
}
harmony = new Harmony("tube.spook.mod");
harmony.PatchAll();
uploader = new PTUploader(URL.Value);
HTTPServer hTTPServer = new HTTPServer(48923);
hTTPServer.Start();
}
private void OnDestroy()
{
harmony.UnpatchSelf();
Logger.LogInfo((object)"spook.tube has unloaded.");
}
public static string getVideoMime(string path)
{
Dictionary<string, string> dictionary = new Dictionary<string, string>();
dictionary.Add(".webm", "video/webm");
dictionary.Add(".mp4", "video/mp4");
string extension = Path.GetExtension(path);
if (dictionary.ContainsKey(extension))
{
return DictionaryExtensions.Get<string, string>((IDictionary<string, string>)dictionary, extension, (string)null);
}
return null;
}
public void LoadModpackID()
{
List<string> list = Directory.GetDirectories(Paths.PluginPath, "SpookTube", SearchOption.AllDirectories).ToList();
foreach (string item in list)
{
string path = Path.Combine(item, "modpackid.txt");
if (File.Exists(path))
{
modpackID = File.ReadAllText(path);
}
}
}
public static List<LoadedPlugin> GetLoadedPlugins()
{
List<LoadedPlugin> list = new List<LoadedPlugin>();
foreach (ThunderstoreMod loadedMod in Library.GetLoadedMods())
{
list.Add(new LoadedPlugin
{
ThunderstoreName = loadedMod.name,
Version = $"{loadedMod.versionNumber.major}.{loadedMod.versionNumber.minor}.{loadedMod.versionNumber.patch}"
});
}
return list;
}
public static List<ContentPlayer> GetPlayers()
{
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
List<ContentPlayer> list = new List<ContentPlayer>();
PlayerVisor[] array = Object.FindObjectsOfType<PlayerVisor>();
PlayerVisor[] array2 = array;
foreach (PlayerVisor val in array2)
{
PhotonView component = ((Component)val).gameObject.GetComponent<PhotonView>();
ContentPlayer item = new ContentPlayer
{
FaceText = ((TMP_Text)val.visorFaceText).text,
FaceColor = ColorUtility.ToHtmlStringRGB(val.visorColor.Value),
FaceRotation = (int)val.FaceRotation
};
list.Add(item);
}
return list;
}
private static void CreateUploadButton(GameObject root, string namePrefix)
{
//IL_00ba: 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_00f1: Unknown result type (might be due to invalid IL or missing references)
//IL_0127: 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_015e: Unknown result type (might be due to invalid IL or missing references)
//IL_01c5: 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_01fe: Unknown result type (might be due to invalid IL or missing references)
//IL_02a2: 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_02db: Unknown result type (might be due to invalid IL or missing references)
if (!((Component)root.transform.Find("Content/ShowVideoState/VIDEO/VideoDone")).gameObject.DoesObjectExist(namePrefix + "STUpload"))
{
Logger.LogInfo((object)("Creating spook.tube upload button for " + namePrefix));
GameObject gameObject = ((Component)root.transform.Find("Content/ShowVideoState/VIDEO/VideoDone")).gameObject;
GameObject gameObject2 = ((Component)gameObject.transform.Find("Replay")).gameObject;
GameObject val = Object.Instantiate<GameObject>(gameObject2);
((Object)val).name = namePrefix + "STUpload";
val.transform.SetParent(gameObject2.transform.parent, true);
val.transform.localPosition = new Vector3(0f, -125f, 0f);
val.transform.rotation = gameObject2.transform.rotation;
val.transform.localScale = new Vector3(1f, 1f, 1f);
GameObject gameObject3 = ((Component)val.transform.Find("Image")).gameObject;
gameObject3.transform.localPosition = new Vector3(-100f, 0f, 0f);
gameObject3.transform.rotation = gameObject2.transform.rotation;
gameObject3.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
Sprite sprite = ((Component)gameObject.transform.Find("SaveVideo/Image")).gameObject.GetComponent<Image>().sprite;
gameObject3.GetComponent<Image>().sprite = sprite;
GameObject gameObject4 = ((Component)val.transform.Find("Text (TMP)")).gameObject;
gameObject4.transform.localPosition = new Vector3(0f, 0f, 0f);
gameObject4.transform.rotation = gameObject2.transform.rotation;
gameObject4.transform.localScale = new Vector3(1f, 1f, 1f);
((Behaviour)gameObject4.GetComponent<GameObjectLocalizer>()).enabled = false;
TextMeshProUGUI component = gameObject4.GetComponent<TextMeshProUGUI>();
((TMP_Text)component).SetText("UPLOAD TO SPOOK.TUBE", true);
GameObject val2 = ((Component)root.transform.parent).gameObject.FindObject("ReplayInt");
GameObject val3 = Object.Instantiate<GameObject>(val2);
((Object)val3).name = namePrefix + "STUploadInt";
Object.Destroy((Object)(object)val3.GetComponent<ReplayVideoInteractable>());
val3.AddComponent<UploadButton>();
val3.transform.SetParent(val2.transform.parent, true);
val3.transform.position = val.transform.position;
val3.transform.rotation = val.transform.rotation;
val3.transform.localScale = new Vector3(1f, 1f, 1f);
}
}
public static void CreateUploadButton()
{
CreateUploadButton(GameObject.Find("UploadMachine2/McScreen"), "tv");
CreateUploadButton(GameObject.Find("CinemaScreen(Clone)/McScreen"), "cinema");
}
private static bool TryStartUpload(GameObject root)
{
//IL_0087: Unknown result type (might be due to invalid IL or missing references)
GameObject val = ((Component)root.transform.Find("McScreen/Content")).gameObject.FindObject("ShowVideoState");
if ((Object)(object)val == (Object)null)
{
Logger.LogError((object)"Failed to get the ShowVideoState object");
return false;
}
UploadCompleteUI component = val.GetComponent<UploadCompleteUI>();
if ((Object)(object)component == (Object)null)
{
Logger.LogError((object)"Failed to get UploadCompleteUI");
return false;
}
object value = new Traverse((object)component).Field("m_replayVideo").GetValue();
IPlayableVideo val2 = (IPlayableVideo)((value is IPlayableVideo) ? value : null);
Logger.LogWarning((object)(((object)root)?.ToString() + " m_replayVideo " + (object)val2));
if (val2 == null)
{
return false;
}
GameObject parent = ((Component)root.transform.Find("McScreen")).gameObject.FindObject("Content");
GameObject val3 = parent.FindObject("ShowVideoState");
GameObject UploadingState = parent.FindObject("UploadingState");
UploadingState.SetActive(true);
val3.SetActive(false);
VideoMeta meta = new VideoMeta
{
Plugins = GetLoadedPlugins(),
Players = GetPlayers()
};
uploader.Upload(val2, meta).ContinueWith(delegate
{
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
UploadingState.SetActive(false);
object value2 = new Traverse((object)root.GetComponent<UploadVideoStation>()).Field("m_stateMachine").GetValue();
UploadVideoStationStateMachine val4 = (UploadVideoStationStateMachine)((value2 is UploadVideoStationStateMachine) ? value2 : null);
((StateMachine<UploadVideoStationState>)(object)val4).SwitchState<UploadVideoState>();
});
return true;
}
public static void StartUpload()
{
if (!TryStartUpload(GameObject.Find("CinemaScreen(Clone)")))
{
TryStartUpload(GameObject.Find("UploadMachine2"));
}
}
public static void WaitForLogin(Action onFinish)
{
onFinishAuth = onFinish;
uploader.OpenAuth();
}
}
public class LoadedPlugin
{
public string ThunderstoreName;
public string Version;
}
public class ContentPlayer
{
public string SteamID;
public string FaceText;
public string FaceColor;
public int FaceRotation;
}
internal class UploadButton : Interactable
{
public UploadVideoStation UploadVideoStation;
private void Start()
{
base.hoverText = "Upload to spook.tube";
}
public override void Interact(Player player)
{
Plugin.Logger.LogWarning((object)"Upload to spook.tube button has been pressed");
if (Plugin.uploader.IsAuthenticated())
{
Plugin.StartUpload();
}
else
{
Plugin.uploader.OpenAuth();
}
}
public override void OnStartHover(Player player)
{
}
public override void OnEndHover(Player player)
{
}
}
internal static class Utils
{
public static GameObject FindObject(this GameObject parent, string name)
{
Transform[] componentsInChildren = parent.GetComponentsInChildren<Transform>(true);
Transform[] array = componentsInChildren;
foreach (Transform val in array)
{
if (((Object)val).name.StartsWith(name))
{
return ((Component)val).gameObject;
}
}
return null;
}
public static bool DoesObjectExist(this GameObject parent, string name)
{
return (Object)(object)parent.FindObject(name) != (Object)null;
}
}
public static class PluginInfo
{
public const string PLUGIN_GUID = "spook.tube";
public const string PLUGIN_NAME = "spook.tube";
public const string PLUGIN_VERSION = "1.0.0";
}
}
namespace spook.tube.Uploaders
{
public class PTUploader : Uploader
{
private class UploadResult_Success
{
public class Video
{
public int id;
public string shortUUID;
public string uuid;
}
public Video video;
}
private class PTAccount
{
public string username;
public PTChannel[] videoChannels;
}
private class ChannelResponse
{
public int total;
public PTChannel[] data;
}
private class PTChannel
{
public int id;
public string displayName;
}
private string hostname;
private static string token;
private HttpClient client;
private static int channelId;
public PTUploader(string hostname)
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Expected O, but got Unknown
this.hostname = hostname;
client = new HttpClient();
}
public override bool IsAuthenticated()
{
return token != null;
}
private HttpRequestMessage buildRequest(string endpoint, HttpMethod method)
{
//IL_0001: 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_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Expected O, but got Unknown
HttpRequestMessage val = new HttpRequestMessage
{
RequestUri = new Uri(hostname + endpoint),
Method = method
};
((HttpHeaders)val.Headers).Add("Authorization", "Bearer " + token);
return val;
}
private string getVideoURL(string shortUUID)
{
return hostname + "/videos/update/" + shortUUID + "?ref=mod-upload";
}
public override void OpenAuth()
{
Application.OpenURL(hostname + "/p/mod/token");
}
public override void HandleLogin(string token)
{
PTUploader.token = token;
HttpResponseMessage result = client.SendAsync(buildRequest("/api/v1/users/me", HttpMethod.Get)).Result;
string result2 = result.Content.ReadAsStringAsync().Result;
Plugin.Logger.LogInfo((object)("Token check result: " + result2));
if (result.StatusCode == HttpStatusCode.OK)
{
PTAccount pTAccount = JsonConvert.DeserializeObject<PTAccount>(result2);
Plugin.Logger.LogInfo((object)("Logged in as " + pTAccount.username));
if (pTAccount.videoChannels.Length == 0)
{
Plugin.Logger.LogError((object)"User has no channels? Cannot load.");
return;
}
PTChannel pTChannel = pTAccount.videoChannels[0];
channelId = pTChannel.id;
Plugin.Logger.LogInfo((object)$"Using channel {pTChannel.displayName} ({pTChannel.id})");
Plugin.onFinishAuth?.Invoke();
}
else
{
Plugin.Logger.LogWarning((object)"Failed to login");
}
}
public override Task Upload(IPlayableVideo recording, VideoMeta meta)
{
string path = default(string);
recording.TryGetVideoPath(ref path);
return Task.Factory.StartNew(delegate
{
UploadThread(path, meta);
});
}
private void UploadThread(string path, VideoMeta meta)
{
//IL_0085: Unknown result type (might be due to invalid IL or missing references)
//IL_008b: Expected O, but got Unknown
//IL_009f: Unknown result type (might be due to invalid IL or missing references)
//IL_00ae: Expected O, but got Unknown
//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
//IL_00c0: Expected O, but got Unknown
//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
//IL_00d6: Expected O, but got Unknown
//IL_0123: Unknown result type (might be due to invalid IL or missing references)
//IL_0132: Expected O, but got Unknown
//IL_013f: Unknown result type (might be due to invalid IL or missing references)
//IL_014e: Expected O, but got Unknown
//IL_0155: Unknown result type (might be due to invalid IL or missing references)
//IL_015c: Expected O, but got Unknown
//IL_0106: Unknown result type (might be due to invalid IL or missing references)
//IL_0115: Expected O, but got Unknown
Plugin.Logger.LogInfo((object)("Starting upload of " + path));
Plugin.Logger.LogInfo((object)("File name: " + Path.GetFileName(path)));
string videoMime = Plugin.getVideoMime(path);
Plugin.Logger.LogInfo((object)("File mime type: " + videoMime));
string text = "In-Game Upload";
if (videoMime == null)
{
Plugin.Logger.LogWarning((object)("I don't know what mime type the video is (" + Path.GetExtension(path) + ")"));
return;
}
try
{
MultipartFormDataContent val = new MultipartFormDataContent();
val.Add((HttpContent)new StringContent(channelId.ToString() ?? ""), "channelId");
val.Add((HttpContent)new StringContent(text), "name");
val.Add((HttpContent)new StringContent("sc07-spooktube"), "usingMod");
if (Plugin.modpackID != null)
{
Plugin.Logger.LogWarning((object)("Uploading video using modpackId " + Plugin.modpackID));
val.Add((HttpContent)new StringContent(Plugin.modpackID), "modpackId");
}
val.Add((HttpContent)new StringContent(JsonConvert.SerializeObject((object)meta.Plugins)), "mods");
val.Add((HttpContent)new StringContent(JsonConvert.SerializeObject((object)meta.Players)), "players");
ByteArrayContent val2 = new ByteArrayContent(File.ReadAllBytes(path));
((HttpContent)val2).Headers.ContentType = MediaTypeHeaderValue.Parse(videoMime);
val.Add((HttpContent)(object)val2, "videofile", Path.GetFileName(path));
HttpRequestMessage val3 = buildRequest("/api/v1/videos/upload", HttpMethod.Post);
val3.Content = (HttpContent)(object)val;
HttpResponseMessage result = client.SendAsync(val3).Result;
string result2 = result.Content.ReadAsStringAsync().Result;
Plugin.Logger.LogInfo((object)("Upload response: " + result2));
HttpStatusCode statusCode = result.StatusCode;
HttpStatusCode httpStatusCode = statusCode;
if (httpStatusCode == HttpStatusCode.OK)
{
UploadResult_Success uploadResult_Success = JsonConvert.DeserializeObject<UploadResult_Success>(result2);
Plugin.Logger.LogInfo((object)("Successfully uploaded " + uploadResult_Success.video.shortUUID));
Application.OpenURL(getVideoURL(uploadResult_Success.video.shortUUID));
}
else
{
Plugin.Logger.LogError((object)("Error while uploading " + result.StatusCode.ToString() + " : " + result2));
}
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)ex);
}
}
}
public abstract class Uploader
{
public abstract bool IsAuthenticated();
public abstract void OpenAuth();
public abstract void HandleLogin(string token);
public abstract Task Upload(IPlayableVideo video, VideoMeta meta);
}
public class VideoMeta
{
public List<LoadedPlugin> Plugins;
public List<ContentPlayer> Players;
}
}