Files
ZHGD_Web/Assets/UniversalMediaPlayer/Scripts/Sources/Services/Youtube/YoutubeVideo.Decrypt.cs
2025-07-13 23:16:20 +08:00

203 lines
7.2 KiB
C#

using Services.Helpers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Networking;
namespace UMP.Services.Youtube
{
public partial class YoutubeVideo
{
private static readonly Regex DefaultDecryptionFunctionRegex = new Regex(@"\bc\s*&&\s*d\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*([a-zA-Z0-9$]+)\(");
private static readonly Regex FunctionRegex = new Regex(@"\w+(?:.|\[)(\""?\w+(?:\"")?)\]?\(");
public IEnumerator Decrypt(string decryptFunction = null, Action<string> errorCallback = null)
{
if (_encrypted)
{
var query = new Query(_uri);
var signature = string.Empty;
if (!query.TryGetValue("signature", out signature))
yield break;
var requestText = string.Empty;
#if UNITY_2017_2_OR_NEWER
var request = UnityWebRequest.Get(_jsPlayer);
yield return request.SendWebRequest();
#else
var request = new WWW(_jsPlayer);
yield return request;
#endif
try
{
if (!string.IsNullOrEmpty(request.error))
throw new Exception(string.Format("[YouTubeVideo.Decrypt] jsPlayer request is failed: {0}", request.error));
#if UNITY_2017_2_OR_NEWER
requestText = request.downloadHandler.text;
#else
requestText = request.text;
#endif
query["signature"] = DecryptSignature(requestText, signature, decryptFunction);
_uri = query.ToString();
_encrypted = false;
}
catch (Exception error)
{
if (errorCallback != null)
errorCallback(error.ToString());
}
}
}
private string DecryptSignature(string js, string signature, string decryptFunction)
{
var functionLines = GetDecryptionFunctionLines(js, decryptFunction);
var decryptor = new Decryptor();
foreach (var functionLine in functionLines)
{
if (decryptor.IsComplete)
break;
var match = FunctionRegex.Match(functionLine);
if (match.Success)
decryptor.AddFunction(js, match.Groups[1].Value);
}
foreach (var functionLine in functionLines)
{
var match = FunctionRegex.Match(functionLine);
if (match.Success)
signature = decryptor.ExecuteFunction(signature, functionLine, match.Groups[1].Value);
}
return signature;
}
private string[] GetDecryptionFunctionLines(string js, string decryptFunction)
{
var decryptionFunction = GetDecryptionFunction(js, decryptFunction);
var match = Regex.Match(js, string.Format(@"(?!h\.){0}=function\(\w+\)\{{(.*?)\}}", Regex.Escape(decryptionFunction)), RegexOptions.Singleline);
if (!match.Success)
throw new Exception("[YouTubeVideo.Decrypt] GetDecryptionFunctionLines failed");
return match.Groups[1].Value.Split(';');
}
private string GetDecryptionFunction(string js, string decryptFunction)
{
var decryptionFunctionRegex = string.IsNullOrEmpty(decryptFunction) ? DefaultDecryptionFunctionRegex :
new Regex(decryptFunction);
var match = decryptionFunctionRegex.Match(js);
if (!match.Success)
throw new Exception("[YouTubeVideo.Decrypt] GetDecryptionFunction failed");
return match.Groups[1].Value;
}
private class Decryptor
{
private static readonly Regex ParametersRegex = new Regex(@"\(\w+,(\d+)\)");
private readonly Dictionary<string, FunctionType> _functionTypes = new Dictionary<string, FunctionType>();
private readonly StringBuilder _stringBuilder = new StringBuilder();
public bool IsComplete
{
get { return _functionTypes.Count == Enum.GetValues(typeof(FunctionType)).Length; }
}
public void AddFunction(string js, string function)
{
var escapedFunction = Regex.Escape(function);
FunctionType? type = null;
if (Regex.IsMatch(js, string.Format(@"{0}:\bfunction\b\(\w+\)", escapedFunction)))
{
type = FunctionType.Reverse;
}
else if (Regex.IsMatch(js, string.Format(@"{0}:\bfunction\b\([a],b\).(\breturn\b)?.?\w+\.", escapedFunction)))
{
type = FunctionType.Slice;
}
else if (Regex.IsMatch(js, string.Format(@"{0}:\bfunction\b\(\w+\,\w\).\bvar\b.\bc=a\b", escapedFunction)))
{
type = FunctionType.Swap;
}
if (type.HasValue)
{
_functionTypes[function] = type.Value;
}
}
public string ExecuteFunction(string signature, string line, string function)
{
var type = FunctionType.Reverse;
if (!_functionTypes.TryGetValue(function, out type))
return signature;
switch (type)
{
case FunctionType.Reverse:
return Reverse(signature);
case FunctionType.Slice:
case FunctionType.Swap:
var index = int.Parse(ParametersRegex.Match(line).Groups[1].Value, NumberStyles.AllowThousands, NumberFormatInfo.InvariantInfo);
return type == FunctionType.Slice ? Slice(signature, index) : Swap(signature, index);
default:
throw new ArgumentOutOfRangeException(string.Format("[YouTubeVideo.Decryptor] {0}", type));
}
}
private string Reverse(string signature)
{
//_stringBuilder.Clear();
_stringBuilder.Remove(0, _stringBuilder.Length);
for (var index = signature.Length - 1; index >= 0; index--)
_stringBuilder.Append(signature[index]);
return _stringBuilder.ToString();
}
private string Slice(string signature, int index)
{
return signature.Substring(index);
}
private string Swap(string signature, int index)
{
//_stringBuilder.Clear();
_stringBuilder.Remove(0, _stringBuilder.Length);
_stringBuilder.Append(signature);
_stringBuilder[0] = signature[index];
_stringBuilder[index] = signature[0];
return _stringBuilder.ToString();
}
private enum FunctionType
{
Reverse,
Slice,
Swap
}
}
}
}