Skip to content

Issue with connection #13

Description

@dcutts77

Ok.... this is what I got when fixing it with cursor Appreciate the hard work always!

Fix for older PotPlayer: HostDecodeSigUrl is missing

Diffed against the original PotPlayer-SponsorBlock script (MediaPlayParse - YouTube with SponsorBlock.as).

That is the only functional change — 2,840 lines → 3,112 lines, five hunks, nothing else. SponsorBlock chapters, Innertube, quality list, etc. are untouched.


Problem

On older PotPlayer builds this script never actually runs. The AngelScript console shows:

ERR : No matching symbol 'HostDecodeSigUrl'

HostDecodeSigUrl is a host API that exists only in newer PotPlayer. Without it the parser does not compile, so YouTube URLs fail / flicker through qualities and never play.

The stock bundled MediaPlayParse - YouTube.as already has a local JS signature decoder (SignatureDecode + youtubeFuncType / Delete / Swap / Reverse / GetFunction). I copied that into this SponsorBlock fork and wrapped it as DecodeSigUrl(...) so the three existing call sites could stay the same shape.


Changes

1. Inserted local decoder (before ReplaceCodecName)

Added: youtubeFuncType, Delete, Swap, Reverse, GetFunction, SignatureDecode, and DecodeSigUrl.

DecodeSigUrl is a drop-in for HostDecodeSigUrl(url, cipher, jsData): it parses signatureCipher / cipher (url / s / sp), applies the player-JS transform, and also handles /s/ in dash/HLS URLs.

The full decoder block is in the working file at lines 1021–1289, copied from stock MediaPlayParse - YouTube.as.

2. In PlayitemParse, declare the decoder state next to the other locals

array<youtubeFuncType> JSFuncs;
array<int> JSFuncArgs;

3. Replace the three HostDecodeSigUrl calls

Live dash/HLS:

// was:
liveUrl = HostDecodeSigUrl(liveUrl, "", JSData);
// now:
liveUrl = DecodeSigUrl(liveUrl, "", WebData, JSData, JSFuncs, JSFuncArgs);

Adaptive formats (url or signatureCipher / cipher):

// was:
resolvedUrl = HostDecodeSigUrl(resolvedUrl, cipherText, JSData);
// now:
resolvedUrl = DecodeSigUrl(resolvedUrl, cipherText, WebData, JSData, JSFuncs, JSFuncArgs);

Non-live dash/HLS fallback:

// was:
url = HostDecodeSigUrl(url, "", JSData);
// now:
url = DecodeSigUrl(url, "", WebData, JSData, JSFuncs, JSFuncArgs);

How to patch your own copy

Open MediaPlayParse - YouTube with SponsorBlock.as in a text editor. There are four edits: one insert, one local-variable add, and three one-line replacements. Search for the FIND text; it should match once each time.

When you are done, there must be zero remaining HostDecodeSigUrl in the file. Copy the patched .as into:

PotPlayer\Extension\Media\PlayParse\

Restart PotPlayer (or reload extensions).


Edit 1 — insert the local decoder

FIND this (it is the start of ReplaceCodecName, around line 1021 in the original):

string ReplaceCodecName(string name, string id)

INSERT the entire block below immediately before that line. Do not delete ReplaceCodecName.

// Local signature decoder - HostDecodeSigUrl is not available in all PotPlayer builds
enum youtubeFuncType
{
	funcNONE = -1,
	funcDELETE,
	funcREVERSE,
	funcSWAP
};

void Delete(string &a, int b)
{
	a.erase(0, b);
}

void Swap(string &a, int b)
{
	uint8 c = a[0];

	b %= a.size();
	a[0] = a[b];
	a[b] = c;
};

void Reverse(string &a)
{
	int len = a.size();

	for (int i = 0; i < len / 2; ++i)
	{
		uint8 c = a[i];

		a[i] = a[len - i - 1];
		a[len - i - 1] = c;
	}
}

string GetFunction(string str)
{
	array<string> signatureRegExps =
	{
		"(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)",
		"(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\);[a-zA-Z0-9$]{2}\\.[a-zA-Z0-9$]{2}\\(a,\\d+\\)",
		"\\b[cs]\\s*&&\\s*[adf]\\.set\\([^,]+\\s*,\\s*encodeURIComponent\\s*\\(\\s*([a-zA-Z0-9$]+)\\(",
		"\\b[a-zA-Z0-9]+\\s*&&\\s*[a-zA-Z0-9]+\\.set\\([^,]+\\s*,\\s*encodeURIComponent\\s*\\(\\s*([a-zA-Z0-9$]+)\\(",
		"([a-zA-Z0-9$]+)\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)",
		"([\"\\'])signature\\1\\s*,\\s*([a-zA-Z0-9$]+)\\(",
		"\\.sig\\|\\|([a-zA-Z0-9$]+)\\(",
		"yt\\.akamaized\\.net/\\)\\s*\\|\\|\\s*.*?\\s*[cs]\\s*&&\\s*[adf]\\.set\\([^,]+\\s*,\\s*(?:encodeURIComponent\\s*\\()?\\s*([a-zA-Z0-9$]+)\\(",
		"\\b[cs]\\s*&&\\s*[adf]\\.set\\([^,]+\\s*,\\s*([a-zA-Z0-9$]+)\\(",
		"\\b[a-zA-Z0-9]+\\s*&&\\s*[a-zA-Z0-9]+\\.set\\([^,]+\\s*,\\s*([a-zA-Z0-9$]+)\\(",
		"\\bc\\s*&&\\s*a\\.set\\([^,]+\\s*,\\s*\\([^)]*\\)\\s*\\(\\s*([a-zA-Z0-9$]+)\\(",
		"\\bc\\s*&&\\s*[a-zA-Z0-9]+\\.set\\([^,]+\\s*,\\s*\\([^)]*\\)\\s*\\(\\s*([a-zA-Z0-9$]+)\\(",
		"\\bc\\s*&&\\s*[a-zA-Z0-9]+\\.set\\([^,]+\\s*,\\s*\\([^)]*\\)\\s*\\(\\s*([a-zA-Z0-9$]+)\\("
	};
	for (int i = 0, len = signatureRegExps.size(); i < len; i++)
	{
		string ret = HostRegExpParse(str, signatureRegExps[i]);
		if (!ret.empty()) return ret;
	}

	string r, sig = "\"signature\"";
	int p = 0;
	while (true)
	{
		int e = str.find(sig, p);

		if (e < 0) break;
		int s1 = str.find("(", e);
		int s2 = str.find(")", e);
		if (s1 > s2)
		{
			p = e + 10;
			continue;
		}
		p = e + sig.size() + 1;
		r = str.substr(p, s1 - p);
		break;
	}
	r.Trim(",");
	r.Trim();
	r.Trim(",");
	r.Trim();
	return r;
}

string SignatureDecode(string url, string signature, string append, string data, string js_data, array<youtubeFuncType> &JSFuncs, array<int> &JSFuncArgs)
{
	if (JSFuncs.size() == 0 && !js_data.empty())
	{
		string funcName = GetFunction(js_data);

		if (!funcName.empty())
		{
			string funcRegExp = funcName + "=function\\(a\\)\\{([^\\n]+)\\};";
			string funcBody = HostRegExpParse(data, funcRegExp);

			if (funcBody.empty())
			{
				string varfunc = funcName + "=function(a){";

				funcBody = GetEntry(js_data, varfunc, "};");
			}
			if (!funcBody.empty())
			{
				string funcGroup;
				array<string> funcList;
				array<string> funcCodeList;

				array<string> code = funcBody.split(";");
				for (int i = 0, len = code.size(); i < len; i++)
				{
					string line = code[i];

					if (!line.empty())
					{
						if (line.find("split") >= 0 || line.find("return") >= 0) continue;
						funcList.insertLast(line);
						if (funcGroup.empty())
						{
							int k = line.find(".");

							if (k > 0) funcGroup = line.Left(k);
						}
					}
				}

				if (!funcGroup.empty())
				{
					string tmp = GetEntry(js_data, "var " + funcGroup + "={", "};");

					if (!tmp.empty())
					{
						tmp.replace("\n", "");
						funcCodeList = tmp.split("},");
					}
				}

				if (!funcList.empty() && !funcCodeList.empty())
				{
					for (int j = 0, len = funcList.size(); j < len; j++)
					{
						string func = funcList[j];

						if (!func.empty())
						{
							int funcArg = 0;
							string funcArgs = GetEntry(func, "(", ")");
							array<string> args = funcArgs.split(",");

							if (args.size() >= 1)
							{
								string arg = args[args.size() - 1];

								funcArg = parseInt(arg);
							}

							string funcName = GetEntry(func, funcGroup + '.', "(");
							if (funcName.empty())
							{
								funcName = GetEntry(func, funcGroup, "(");
								if (funcName.empty()) continue;
							}
							if (funcName.find("[") >= 0)
							{
								funcName.replace("[", "");
								funcName.replace("]", "");
							}
							funcName += ":function";

							youtubeFuncType funcType = youtubeFuncType::funcNONE;
							for (int k = 0, len = funcCodeList.size(); k < len; k++)
							{
								string funcCode = funcCodeList[k];

								if (funcCode.find(funcName) >= 0)
								{
									if (funcCode.find("splice") > 0) funcType = youtubeFuncType::funcDELETE;
									else if (funcCode.find("reverse") > 0) funcType = youtubeFuncType::funcREVERSE;
									else if (funcCode.find(".length]") > 0) funcType = youtubeFuncType::funcSWAP;
									break;
								}
							}
							if (funcType != youtubeFuncType::funcNONE)
							{
								JSFuncs.insertLast(funcType);
								JSFuncArgs.insertLast(funcArg);
							}
						}
					}
				}
			}
		}
	}

	if (!JSFuncs.empty() && JSFuncs.size() == JSFuncArgs.size())
	{
		for (int i = 0, len = JSFuncs.size(); i < len; i++)
		{
 mar youtubeFuncType func = JSFuncs[i];
			int arg = JSFuncArgs[i];

			switch (func)
			{
			case youtubeFuncType::funcDELETE:
				Delete(signature, arg);
				break;
			case youtubeFuncType::funcSWAP:
				Swap(signature, arg);
				break;
			case youtubeFuncType::funcREVERSE:
				Reverse(signature);
				break;
			}
		}
		url = url + append + signature;
	}

	return url;
}

// Drop-in replacement for HostDecodeSigUrl (missing on older PotPlayer hosts)
string DecodeSigUrl(string url, string cipher, string webData, string jsData, array<youtubeFuncType> &JSFuncs, array<int> &JSFuncArgs)
{
	if (!cipher.empty())
	{
		string u, signature, sigName = "signature";
		string str = cipher;

		str.replace("\\u0026", "&");
		array<string> params = str.split("&");
		for (int i = 0, len = params.size(); i < len; i++)
		{
			string param = params[i];
			int k = param.find("=");

			if (k > 0)
			{
				string paramHeader = param.Left(k);
				string paramValue = param.substr(k + 1);

				if (paramHeader == "url") u = HostUrlDecode(paramValue);
				else if (paramHeader == "s") signature = HostUrlDecode(paramValue);
				else if (paramHeader == "sp") sigName = paramValue;
				else if (!u.empty()) u = u + "&" + paramHeader + "=" + HostUrlDecode(paramValue);
			}
			else if (!u.empty()) u = u + "&" + param;
		}
		if (!u.empty() && !signature.empty() && !jsData.empty())
		{
			string param = "&" + sigName + "=";

			u = SignatureDecode(u, signature, param, webData, jsData, JSFuncs, JSFuncArgs);
		}
		u.replace("\\u0026", "&");
		return u;
	}

	if (url.empty()) return "";

	url.replace("\\u0026", "&");
	if (url.find("/s/") > 0)
	{
		string tmp = url;
		string signature = HostRegExpParse(tmp, "/s/([0-9A-Z]+.[0-9A-Z]+)");

		if (!signature.empty()) url = SignatureDecode(tmp, signature, "/signature/", webData, jsData, JSFuncs, JSFuncArgs);
	}
	return url;
}

After this insert, string ReplaceCodecName(string name, string id) should still be the next function.


Edit 2 — add decoder state in PlayitemParse

FIND this (inside PlayitemParse, right after string tmp_fn = fn;):

		string fn = path;
		string tmp_fn = fn;

		tmp_fn.MakeLower();

REPLACE with:

		string fn = path;
		string tmp_fn = fn;
		array<youtubeFuncType> JSFuncs;
		array<int> JSFuncArgs;

		tmp_fn.MakeLower();

Edit 3 — replace the three HostDecodeSigUrl calls

Search for HostDecodeSigUrl. There are exactly three.

3a. Live dash/HLS (around original line 1561)

FIND:

			liveUrl = HostDecodeSigUrl(liveUrl, "", JSData);

REPLACE with:

			liveUrl = DecodeSigUrl(liveUrl, "", WebData, JSData, JSFuncs, JSFuncArgs);

3b. Adaptive formats (url or signatureCipher / cipher, around original line 1679)

FIND:

									resolvedUrl = HostDecodeSigUrl(resolvedUrl, cipherText, JSData);

REPLACE with:

									resolvedUrl = DecodeSigUrl(resolvedUrl, cipherText, WebData, JSData, JSFuncs, JSFuncArgs);

3c. Non-live dash/HLS fallback (around original line 1721)

FIND:

							url = HostDecodeSigUrl(url, "", JSData);

REPLACE with:

							url = DecodeSigUrl(url, "", WebData, JSData, JSFuncs, JSFuncArgs);

Check

  1. Search the file: HostDecodeSigUrl should match 0 times.
  2. Search: DecodeSigUrl should match 4 times (the function definition plus the three call sites).
  3. Restart PotPlayer and open a YouTube URL. The AngelScript console should no longer report No matching symbol 'HostDecodeSigUrl'.

Result

After that the script compiles on hosts that do not export HostDecodeSigUrl, and signed stream URLs decode the same way the stock YouTube parser does.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions