From cd4ceea38ad7fbbb4404b31bb031899e1cdfa40d Mon Sep 17 00:00:00 2001 From: Elwador <75888166+Elwador@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:09:18 +0200 Subject: [PATCH] Fix - Localization bug in widevine licence request Fix - Subsciption check for third party subscriptions --- CRD/Downloader/CRAuth.cs | 12 +++++- CRD/Downloader/Crunchyroll.cs | 53 +++++++++++++------------- CRD/Utils/DRM/Session.cs | 4 +- CRD/Utils/DRM/Widevine.cs | 41 +++++++++++--------- CRD/Utils/Structs/CrProfile.cs | 27 +++++++++++++ CRD/ViewModels/AccountPageViewModel.cs | 6 ++- 6 files changed, 94 insertions(+), 49 deletions(-) diff --git a/CRD/Downloader/CRAuth.cs b/CRD/Downloader/CRAuth.cs index 97888e5..dd798dd 100644 --- a/CRD/Downloader/CRAuth.cs +++ b/CRD/Downloader/CRAuth.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; @@ -112,7 +113,16 @@ public class CrAuth{ if (responseSubs.IsOk){ var subsc = Helpers.Deserialize(responseSubs.ResponseContent, crunInstance.SettingsJsonSerializerSettings); crunInstance.Profile.Subscription = subsc; - crunInstance.Profile.HasPremium = subsc.IsActive; + if ( subsc.SubscriptionProducts is{ Count: 0 } && subsc.ThirdPartySubscriptionProducts is{ Count: > 0 }){ + var thirdPartySub = subsc.ThirdPartySubscriptionProducts.First(); + var remaining = thirdPartySub.ExpirationDate - DateTime.UtcNow; + crunInstance.Profile.HasPremium = remaining > TimeSpan.Zero; + crunInstance.Profile.Subscription.IsActive = remaining > TimeSpan.Zero; + crunInstance.Profile.Subscription.NextRenewalDate = thirdPartySub.ExpirationDate; + } else{ + crunInstance.Profile.HasPremium = subsc.IsActive; + } + } else{ crunInstance.Profile.HasPremium = false; Console.Error.WriteLine("Failed to check premium subscription status"); diff --git a/CRD/Downloader/Crunchyroll.cs b/CRD/Downloader/Crunchyroll.cs index 58e9665..df1956a 100644 --- a/CRD/Downloader/Crunchyroll.cs +++ b/CRD/Downloader/Crunchyroll.cs @@ -1029,9 +1029,8 @@ public class Crunchyroll{ var assetIdRegexMatch = Regex.Match(chosenVideoSegments.segments[0].uri, @"/assets/(?:p/)?([^_,]+)"); var assetId = assetIdRegexMatch.Success ? assetIdRegexMatch.Groups[1].Value : null; var sessionId = Helpers.GenerateSessionId(); - - //TODO change back from error - Console.Error.WriteLine("Decryption Needed, attempting to decrypt"); + + Console.WriteLine("Decryption Needed, attempting to decrypt"); if (!_widevine.canDecrypt){ dlFailed = true; @@ -1070,18 +1069,12 @@ public class Crunchyroll{ ErrorText = "DRM Authentication failed" }; } - - //TODO change back from error - Console.Error.WriteLine("Request to DRM Authentication successful"); + DrmAuthData authData = Helpers.Deserialize(decRequestResponse.ResponseContent, SettingsJsonSerializerSettings) ?? new DrmAuthData(); - - //TODO change back from error - Console.Error.WriteLine("Deserialized Authentication successful"); + Dictionary authDataDict = new Dictionary { { "dt-custom-data", authData.CustomData ?? string.Empty },{ "x-dt-auth-token", authData.Token ?? string.Empty } }; - - //TODO change back from error - Console.Error.WriteLine("Requesting encryption keys"); + var encryptionKeys = await _widevine.getKeys(chosenVideoSegments.pssh, "https://lic.drmtoday.com/license-proxy-widevine/cenc/", authDataDict); if (encryptionKeys.Count == 0){ @@ -1094,23 +1087,24 @@ public class Crunchyroll{ ErrorText = "Couldn't get DRM encryption keys" }; } - - //TODO change back from error - Console.Error.WriteLine("Requested encryption keys successful"); + if (Path.Exists(CfgManager.PathMP4Decrypt)){ - //TODO change back from error - Console.Error.WriteLine("Building command"); var keyId = BitConverter.ToString(encryptionKeys[0].KeyID).Replace("-", "").ToLower(); var key = BitConverter.ToString(encryptionKeys[0].Bytes).Replace("-", "").ToLower(); var commandBase = $"--show-progress --key {keyId}:{key}"; var commandVideo = commandBase + $" \"{tempTsFile}.video.enc.m4s\" \"{tempTsFile}.video.m4s\""; var commandAudio = commandBase + $" \"{tempTsFile}.audio.enc.m4s\" \"{tempTsFile}.audio.m4s\""; - //TODO change back from error - Console.Error.WriteLine("Built command"); if (videoDownloaded){ - //TODO change back from error - Console.Error.WriteLine("Started decrypting video"); + Console.WriteLine("Started decrypting video"); + data.DownloadProgress = new DownloadProgress(){ + IsDownloading = true, + Percent = 100, + Time = 0, + DownloadSpeed = 0, + Doing = "Decrypting video" + }; + Queue.Refresh(); var decryptVideo = await Helpers.ExecuteCommandAsync("mp4decrypt", CfgManager.PathMP4Decrypt, commandVideo); if (!decryptVideo.IsOk){ @@ -1156,13 +1150,19 @@ public class Crunchyroll{ }); } } else{ - //TODO change back from error - Console.Error.WriteLine("No Video downloaded"); + Console.WriteLine("No Video downloaded"); } if (audioDownloaded){ - //TODO change back from error - Console.Error.WriteLine("Started decrypting audio"); + Console.WriteLine("Started decrypting audio"); + data.DownloadProgress = new DownloadProgress(){ + IsDownloading = true, + Percent = 100, + Time = 0, + DownloadSpeed = 0, + Doing = "Decrypting audio" + }; + Queue.Refresh(); var decryptAudio = await Helpers.ExecuteCommandAsync("mp4decrypt", CfgManager.PathMP4Decrypt, commandAudio); if (!decryptAudio.IsOk){ @@ -1207,8 +1207,7 @@ public class Crunchyroll{ }); } } else{ - //TODO change back from error - Console.Error.WriteLine("No Audio downloaded"); + Console.WriteLine("No Audio downloaded"); } } else{ Console.Error.WriteLine("mp4decrypt not found, files need decryption. Decryption Keys: "); diff --git a/CRD/Utils/DRM/Session.cs b/CRD/Utils/DRM/Session.cs index 38e2ec4..fcb97eb 100644 --- a/CRD/Utils/DRM/Session.cs +++ b/CRD/Utils/DRM/Session.cs @@ -87,7 +87,7 @@ public class Session{ Type = LicenseRequest.RequestType.New, KeyControlNonce = 1093602366, ProtocolVersion = ProtocolVersion.Current, - RequestTime = uint.Parse((DateTime.Now - DateTime.UnixEpoch).TotalSeconds.ToString().Split(",")[0]), + RequestTime = uint.Parse((DateTime.Now - DateTime.UnixEpoch).TotalSeconds.ToString(System.Globalization.CultureInfo.InvariantCulture).Split('.')[0]), ContentId = new LicenseRequest.ContentIdentification{ CencId = new LicenseRequest.ContentIdentification.Cenc{ LicenseType = LicenseType.Default, @@ -104,7 +104,7 @@ public class Session{ Type = LicenseRequestRaw.RequestType.New, KeyControlNonce = 1093602366, ProtocolVersion = ProtocolVersion.Current, - RequestTime = uint.Parse((DateTime.Now - DateTime.UnixEpoch).TotalSeconds.ToString().Split(",")[0]), + RequestTime = uint.Parse((DateTime.Now - DateTime.UnixEpoch).TotalSeconds.ToString(System.Globalization.CultureInfo.InvariantCulture).Split('.')[0]), ContentId = new LicenseRequestRaw.ContentIdentification{ CencId = new LicenseRequestRaw.ContentIdentification.Cenc{ LicenseType = LicenseType.Default, diff --git a/CRD/Utils/DRM/Widevine.cs b/CRD/Utils/DRM/Widevine.cs index 44d7811..198a7f6 100644 --- a/CRD/Utils/DRM/Widevine.cs +++ b/CRD/Utils/DRM/Widevine.cs @@ -71,34 +71,39 @@ public class Widevine{ canDecrypt = false; } } - + public async Task> getKeys(string? pssh, string licenseServer, Dictionary authData){ if (pssh == null || !canDecrypt) return new List(); - byte[] psshBuffer = Convert.FromBase64String(pssh); + try{ + byte[] psshBuffer = Convert.FromBase64String(pssh); - Session ses = new Session(new ContentDecryptionModule{ identifierBlob = identifierBlob, privateKey = privateKey }, psshBuffer); + Session ses = new Session(new ContentDecryptionModule{ identifierBlob = identifierBlob, privateKey = privateKey }, psshBuffer); - var playbackRequest2 = new HttpRequestMessage(HttpMethod.Post, licenseServer); - foreach (var keyValuePair in authData){ - playbackRequest2.Headers.Add(keyValuePair.Key, keyValuePair.Value); - } + var playbackRequest2 = new HttpRequestMessage(HttpMethod.Post, licenseServer); + foreach (var keyValuePair in authData){ + playbackRequest2.Headers.Add(keyValuePair.Key, keyValuePair.Value); + } - var licenceReq = ses.GetLicenseRequest(); - playbackRequest2.Content = new ByteArrayContent(licenceReq); + var licenceReq = ses.GetLicenseRequest(); + playbackRequest2.Content = new ByteArrayContent(licenceReq); - var response = await HttpClientReq.Instance.SendHttpRequest(playbackRequest2); + var response = await HttpClientReq.Instance.SendHttpRequest(playbackRequest2); - if (!response.IsOk){ - Console.Error.WriteLine("Failed to get Keys!"); + if (!response.IsOk){ + Console.Error.WriteLine("Failed to get Keys!"); + return new List(); + } + + LicenceReqResp resp = Helpers.Deserialize(response.ResponseContent, null) ?? new LicenceReqResp(); + + ses.ProvideLicense(Convert.FromBase64String(resp.license)); + + return ses.ContentKeys; + } catch (Exception e){ + Console.Error.WriteLine(e); return new List(); } - - LicenceReqResp resp = Helpers.Deserialize(response.ResponseContent,null) ?? new LicenceReqResp(); - - ses.ProvideLicense(Convert.FromBase64String(resp.license)); - - return ses.ContentKeys; } } diff --git a/CRD/Utils/Structs/CrProfile.cs b/CRD/Utils/Structs/CrProfile.cs index dbc221e..e0a4e87 100644 --- a/CRD/Utils/Structs/CrProfile.cs +++ b/CRD/Utils/Structs/CrProfile.cs @@ -40,6 +40,12 @@ public class Subscription{ public bool TaxIncluded{ get; set; } [JsonProperty("subscription_products")] public List? SubscriptionProducts{ get; set; } + + [JsonProperty("third_party_subscription_products")] + public List? ThirdPartySubscriptionProducts{ get; set; } + + [JsonProperty("nonrecurring_subscription_products")] + public List? NonrecurringSubscriptionProducts{ get; set; } } public class SubscriptionProduct{ @@ -54,4 +60,25 @@ public class SubscriptionProduct{ public string? Tier{ get; set; } [JsonProperty("active_free_trial")] public bool ActiveFreeTrial{ get; set; } +} + +public class ThirdPartySubscriptionProduct{ + [JsonProperty("effective_date")] + public DateTime EffectiveDate{ get; set; } + public string? Source{ get; set; } + [JsonProperty("source_reference")] + public string? SourceReference{ get; set; } + public string? Sku{ get; set; } + public string? Tier{ get; set; } + [JsonProperty("active_free_trial")] + public bool ActiveFreeTrial{ get; set; } + [JsonProperty("in_grace")] + public bool InGrace{ get; set; } + [JsonProperty("on_hold")] + public bool OnHold{ get; set; } + [JsonProperty("auto_renew")] + public bool AutoRenew{ get; set; } + [JsonProperty("expiration_date")] + public DateTime ExpirationDate{ get; set; } + } \ No newline at end of file diff --git a/CRD/ViewModels/AccountPageViewModel.cs b/CRD/ViewModels/AccountPageViewModel.cs index d486d74..0ab7b86 100644 --- a/CRD/ViewModels/AccountPageViewModel.cs +++ b/CRD/ViewModels/AccountPageViewModel.cs @@ -53,10 +53,14 @@ public partial class AccountPageViewModel : ViewModelBase{ if (Crunchyroll.Instance.Profile.Subscription != null && Crunchyroll.Instance.Profile.Subscription?.SubscriptionProducts != null){ if (Crunchyroll.Instance.Profile.Subscription?.SubscriptionProducts.Count >= 1){ var sub = Crunchyroll.Instance.Profile.Subscription?.SubscriptionProducts.First(); - if (sub != null){ IsCancelled = sub.IsCancelled; } + }else if (Crunchyroll.Instance.Profile.Subscription?.ThirdPartySubscriptionProducts.Count >= 1){ + var sub = Crunchyroll.Instance.Profile.Subscription?.ThirdPartySubscriptionProducts.First(); + if (sub != null){ + IsCancelled = !sub.AutoRenew; + } } if (Crunchyroll.Instance.Profile.Subscription?.NextRenewalDate != null){