diff --git a/scripts/pyplayready/LICENSE b/scripts/pyplayready/LICENSE
new file mode 100644
index 0000000..cfe676c
--- /dev/null
+++ b/scripts/pyplayready/LICENSE
@@ -0,0 +1,403 @@
+Attribution-NonCommercial-NoDerivatives 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-NonCommercial-NoDerivatives 4.0
+International Public License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-NonCommercial-NoDerivatives 4.0 International Public
+License ("Public License"). To the extent this Public License may be
+interpreted as a contract, You are granted the Licensed Rights in
+consideration of Your acceptance of these terms and conditions, and the
+Licensor grants You such rights in consideration of benefits the
+Licensor receives from making the Licensed Material available under
+these terms and conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ c. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ d. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ e. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ f. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ g. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ h. NonCommercial means not primarily intended for or directed towards
+ commercial advantage or monetary compensation. For purposes of
+ this Public License, the exchange of the Licensed Material for
+ other material subject to Copyright and Similar Rights by digital
+ file-sharing or similar means is NonCommercial provided there is
+ no payment of monetary compensation in connection with the
+ exchange.
+
+ i. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ j. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ k. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part, for NonCommercial purposes only; and
+
+ b. produce and reproduce, but not Share, Adapted Material
+ for NonCommercial purposes only.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties, including when
+ the Licensed Material is used other than for NonCommercial
+ purposes.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material, You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ For the avoidance of doubt, You do not have permission under
+ this Public License to Share Adapted Material.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database for NonCommercial purposes
+ only and provided You do not Share Adapted Material;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material; and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
+
diff --git a/scripts/pyplayready/README.md b/scripts/pyplayready/README.md
index 963f8ef..b39f685 100644
--- a/scripts/pyplayready/README.md
+++ b/scripts/pyplayready/README.md
@@ -1,5 +1,5 @@
# pyplayready
-All of this is already public. 100% of this code has been derived from the mspr_toolkit.
+All of this is already public. Almost 100% of this code has been derived from the mspr_toolkit.
## Installation
```shell
@@ -19,9 +19,6 @@ Test a playready device:
pyplayready test DEVICE.prd
```
-> [!IMPORTANT]
-> There currently isn't a proper method of extracting Group Certificates/Keys. They can be found inside older Samsung phones/Smart TVs, Windows DLLs and set-top-boxes in encrypted form.
-
Export a provisioned device to its raw .dat files
```shell
pyplayready export-device DEVICE.prd
@@ -55,8 +52,7 @@ pssh = PSSH(
"AFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAPgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA=="
)
-wrm_headers = pssh.get_wrm_headers()
-request = cdm.get_license_challenge(session_id, wrm_headers[0])
+request = cdm.get_license_challenge(session_id, pssh.wrm_headers[0])
response = requests.post(
url="https://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(persist:false,sl:2000)",
diff --git a/scripts/pyplayready/pyplayready/__init__.py b/scripts/pyplayready/pyplayready/__init__.py
index 0ff5604..21ba9cf 100644
--- a/scripts/pyplayready/pyplayready/__init__.py
+++ b/scripts/pyplayready/pyplayready/__init__.py
@@ -11,4 +11,4 @@ from pyplayready.system.pssh import *
from pyplayready.system.session import *
-__version__ = "0.5.0"
+__version__ = "0.6.0"
diff --git a/scripts/pyplayready/pyplayready/cdm.py b/scripts/pyplayready/pyplayready/cdm.py
index d56f890..7b7d7a8 100644
--- a/scripts/pyplayready/pyplayready/cdm.py
+++ b/scripts/pyplayready/pyplayready/cdm.py
@@ -1,46 +1,49 @@
from __future__ import annotations
import base64
-import math
import time
-from typing import List, Union
+from typing import List, Union, Optional
from uuid import UUID
import xml.etree.ElementTree as ET
+import xmltodict
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad
+from Crypto.Util.strxor import strxor
from ecpy.curves import Point, Curve
from pyplayready.crypto import Crypto
+from pyplayready.drmresults import DRMResult
from pyplayready.system.bcert import CertificateChain
from pyplayready.crypto.ecc_key import ECCKey
from pyplayready.license.key import Key
-from pyplayready.license.xmrlicense import XMRLicense
-from pyplayready.exceptions import (InvalidSession, TooManySessions, InvalidLicense)
+from pyplayready.license.xmrlicense import XMRLicense, XMRObjectTypes
+from pyplayready.exceptions import (InvalidSession, TooManySessions, InvalidLicense, ServerException)
from pyplayready.system.session import Session
+from pyplayready.system.wrmheader import WRMHeader
class Cdm:
MAX_NUM_OF_SESSIONS = 16
+ rgbMagicConstantZero = bytes.fromhex("7ee9ed4af773224f00b8ea7efb027cbb")
+
def __init__(
self,
security_level: int,
- certificate_chain: Union[CertificateChain, None],
- encryption_key: Union[ECCKey, None],
- signing_key: Union[ECCKey, None],
+ certificate_chain: Optional[CertificateChain],
+ encryption_key: Optional[ECCKey],
+ signing_key: Optional[ECCKey],
client_version: str = "10.0.16384.10011",
- protocol_version: int = 1
):
self.security_level = security_level
self.certificate_chain = certificate_chain
self.encryption_key = encryption_key
self.signing_key = signing_key
self.client_version = client_version
- self.protocol_version = protocol_version
self.__crypto = Crypto()
self._wmrm_key = Point(
@@ -48,7 +51,6 @@ class Cdm:
y=0x982b27b5cb2341326e56aa857dbfd5c634ce2cf9ea74fca8f2af5957efeea562,
curve=Curve.get_curve("secp256r1")
)
- self._rgbMagicConstantZero = bytes([0x7e, 0xe9, 0xed, 0x4a, 0xf7, 0x73, 0x22, 0x4f, 0x00, 0xb8, 0xea, 0x7e, 0xfb, 0x02, 0x7c, 0xbb])
self.__sessions: dict[bytes, Session] = {}
@@ -63,12 +65,7 @@ class Cdm:
)
def open(self) -> bytes:
- """
- Open a Playready Content Decryption Module (CDM) session.
-
- Raises:
- TooManySessions: If the session cannot be opened as limit has been reached.
- """
+ """Open a Playready Content Decryption Module (CDM) session"""
if len(self.__sessions) > self.MAX_NUM_OF_SESSIONS:
raise TooManySessions(f"Too many Sessions open ({self.MAX_NUM_OF_SESSIONS}).")
@@ -78,18 +75,10 @@ class Cdm:
return session.id
def close(self, session_id: bytes) -> None:
- """
- Close a Playready Content Decryption Module (CDM) session.
-
- Parameters:
- session_id: Session identifier.
-
- Raises:
- InvalidSession: If the Session identifier is invalid.
- """
+ """Close a Playready Content Decryption Module (CDM) session """
session = self.__sessions.get(session_id)
if not session:
- raise InvalidSession(f"Session identifier {session_id!r} is invalid.")
+ raise InvalidSession(f"Session identifier {session_id.hex()} is invalid.")
del self.__sessions[session_id]
def _get_key_data(self, session: Session) -> bytes:
@@ -100,17 +89,22 @@ class Cdm:
def _get_cipher_data(self, session: Session) -> bytes:
b64_chain = base64.b64encode(self.certificate_chain.dumps()).decode()
- body = (
- ""
- f"{b64_chain}"
- ""
- '""'
- ""
- ""
- ""
- ""
- ""
- )
+ body = xmltodict.unparse({
+ 'Data': {
+ 'CertificateChains': {
+ 'CertificateChain': b64_chain
+ },
+ 'Features': {
+ 'Feature': {
+ '@Name': 'AESCBC',
+ '#text': '""'
+ },
+ 'REE': {
+ 'AESCBCS': None
+ }
+ }
+ }
+ }, full_document=False)
cipher = AES.new(
key=session.xml_key.aes_key,
@@ -125,109 +119,157 @@ class Cdm:
return session.xml_key.aes_iv + ciphertext
+ @staticmethod
+ def _build_main_body(la_content: dict, signed_info: dict, signature: str, public_signing_key: str) -> dict:
+ return {
+ 'soap:Envelope': {
+ '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
+ '@xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
+ '@xmlns:soap': 'http://schemas.xmlsoap.org/soap/envelope/',
+ 'soap:Body': {
+ 'AcquireLicense': {
+ '@xmlns': 'http://schemas.microsoft.com/DRM/2007/03/protocols',
+ 'challenge': {
+ 'Challenge': {
+ '@xmlns': 'http://schemas.microsoft.com/DRM/2007/03/protocols/messages',
+ 'LA': la_content["LA"],
+ 'Signature': {
+ 'SignedInfo': signed_info["SignedInfo"],
+ '@xmlns': 'http://www.w3.org/2000/09/xmldsig#',
+ 'SignatureValue': signature,
+ 'KeyInfo': {
+ '@xmlns': 'http://www.w3.org/2000/09/xmldsig#',
+ 'KeyValue': {
+ 'ECCKeyValue': {
+ 'PublicKey': public_signing_key
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
def _build_digest_content(
self,
wrm_header: str,
nonce: str,
wmrm_cipher: str,
- cert_cipher: str
- ) -> str:
- return (
- ''
- f'{self.protocol_version}'
- f'{wrm_header}'
- ''
- f'{self.client_version}'
- ''
- f'{nonce}'
- f'{math.floor(time.time())}'
- ''
- ''
- ''
- ''
- ''
- ''
- 'WMRMServer'
- ''
- ''
- f'{wmrm_cipher}'
- ''
- ''
- ''
- ''
- f'{cert_cipher}'
- ''
- ''
- ''
- )
+ cert_cipher: str,
+ protocol_version: int
+ ) -> dict:
+ return {
+ 'LA': {
+ '@xmlns': 'http://schemas.microsoft.com/DRM/2007/03/protocols',
+ '@Id': 'SignedData',
+ '@xml:space': 'preserve',
+ 'Version': protocol_version,
+ 'ContentHeader': xmltodict.parse(wrm_header),
+ 'CLIENTINFO': {
+ 'CLIENTVERSION': self.client_version
+ },
+ 'LicenseNonce': nonce,
+ 'ClientTime': int(time.time()),
+ 'EncryptedData': {
+ '@xmlns': 'http://www.w3.org/2001/04/xmlenc#',
+ '@Type': 'http://www.w3.org/2001/04/xmlenc#Element',
+ 'EncryptionMethod': {
+ '@Algorithm': 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
+ },
+ 'KeyInfo': {
+ '@xmlns': 'http://www.w3.org/2000/09/xmldsig#',
+ 'EncryptedKey': {
+ '@xmlns': 'http://www.w3.org/2001/04/xmlenc#',
+ 'EncryptionMethod': {
+ '@Algorithm': 'http://schemas.microsoft.com/DRM/2007/03/protocols#ecc256'
+ },
+ 'KeyInfo': {
+ '@xmlns': 'http://www.w3.org/2000/09/xmldsig#',
+ 'KeyName': 'WMRMServer'
+ },
+ 'CipherData': {
+ 'CipherValue': wmrm_cipher
+ }
+ }
+ },
+ 'CipherData': {
+ 'CipherValue': cert_cipher
+ }
+ }
+ }
+ }
@staticmethod
- def _build_signed_info(digest_value: str) -> str:
- return (
- ''
- ''
- ''
- ''
- ''
- f'{digest_value}'
- ''
- ''
- )
+ def _build_signed_info(digest_value: str) -> dict:
+ return {
+ 'SignedInfo': {
+ '@xmlns': 'http://www.w3.org/2000/09/xmldsig#',
+ 'CanonicalizationMethod': {
+ '@Algorithm': 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
+ },
+ 'SignatureMethod': {
+ '@Algorithm': 'http://schemas.microsoft.com/DRM/2007/03/protocols#ecdsa-sha256'
+ },
+ 'Reference': {
+ '@URI': '#SignedData',
+ 'DigestMethod': {
+ '@Algorithm': 'http://schemas.microsoft.com/DRM/2007/03/protocols#sha256'
+ },
+ 'DigestValue': digest_value
+ }
+ }
+ }
- def get_license_challenge(self, session_id: bytes, wrm_header: str) -> str:
+ def get_license_challenge(self, session_id: bytes, wrm_header: Union[WRMHeader, str]) -> str:
session = self.__sessions.get(session_id)
if not session:
- raise InvalidSession(f"Session identifier {session_id!r} is invalid.")
+ raise InvalidSession(f"Session identifier {session_id.hex()} is invalid.")
+
+ if isinstance(wrm_header, str):
+ wrm_header = WRMHeader(wrm_header)
+ if not isinstance(wrm_header, WRMHeader):
+ raise ValueError(f"Expected WRMHeader to be a {str} or {WRMHeader} not {wrm_header!r}")
+
+ match wrm_header.version:
+ case WRMHeader.Version.VERSION_4_3_0_0:
+ protocol_version = 5
+ case WRMHeader.Version.VERSION_4_2_0_0:
+ protocol_version = 4
+ case _:
+ protocol_version = 1
session.signing_key = self.signing_key
session.encryption_key = self.encryption_key
la_content = self._build_digest_content(
- wrm_header=wrm_header,
+ wrm_header=wrm_header.dumps(),
nonce=base64.b64encode(get_random_bytes(16)).decode(),
wmrm_cipher=base64.b64encode(self._get_key_data(session)).decode(),
- cert_cipher=base64.b64encode(self._get_cipher_data(session)).decode()
+ cert_cipher=base64.b64encode(self._get_cipher_data(session)).decode(),
+ protocol_version=protocol_version
)
+ la_content_xml = xmltodict.unparse(la_content, full_document=False)
la_hash_obj = SHA256.new()
- la_hash_obj.update(la_content.encode())
+ la_hash_obj.update(la_content_xml.encode())
la_hash = la_hash_obj.digest()
signed_info = self._build_signed_info(base64.b64encode(la_hash).decode())
- signature = self.__crypto.ecc256_sign(session.signing_key, signed_info.encode())
+ signed_info_xml = xmltodict.unparse(signed_info, full_document=False)
- # haven't found a better way to do this. xmltodict.unparse doesn't work
- main_body = (
- ''
- ''
- ''
- ''
- ''
- ''
- + la_content +
- ''
- + signed_info +
- f'{base64.b64encode(signature).decode()}'
- ''
- ''
- ''
- f'{base64.b64encode(session.signing_key.public_bytes()).decode()}'
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- )
+ signature = self.__crypto.ecc256_sign(session.signing_key, signed_info_xml.encode())
+ b64_signature = base64.b64encode(signature).decode()
- return main_body
+ b64_public_singing_key = base64.b64encode(session.signing_key.public_bytes()).decode()
+
+ return xmltodict.unparse(self._build_main_body(la_content, signed_info, b64_signature, b64_public_singing_key)).replace('\n', '')
@staticmethod
def _verify_encryption_key(session: Session, licence: XMRLicense) -> bool:
- ecc_keys = list(licence.get_object(42))
+ ecc_keys = list(licence.get_object(XMRObjectTypes.ECC_DEVICE_KEY_OBJECT))
if not ecc_keys:
raise InvalidLicense("No ECC public key in license")
@@ -236,13 +278,25 @@ class Cdm:
def parse_license(self, session_id: bytes, licence: str) -> None:
session = self.__sessions.get(session_id)
if not session:
- raise InvalidSession(f"Session identifier {session_id!r} is invalid.")
+ raise InvalidSession(f"Session identifier {session_id.hex()} is invalid.")
+ if not licence:
+ raise InvalidLicense("Cannot parse an empty licence message")
+ if not isinstance(licence, str):
+ raise InvalidLicense(f"Expected licence message to be a {str}, not {licence!r}")
if not session.encryption_key or not session.signing_key:
raise InvalidSession("Cannot parse a license message without first making a license request")
try:
root = ET.fromstring(licence)
+ faults = root.findall(".//{http://schemas.xmlsoap.org/soap/envelope/}Fault")
+
+ for fault in faults:
+ status_codes = fault.findall(".//StatusCode")
+ for status_code in status_codes:
+ code = DRMResult.from_code(status_code.text)
+ raise ServerException(f"[{status_code.text}] ({code.name}) {code.message}")
+
license_elements = root.findall(".//{http://schemas.microsoft.com/DRM/2007/03/protocols}License")
for license_element in license_elements:
@@ -251,12 +305,12 @@ class Cdm:
if not self._verify_encryption_key(session, parsed_licence):
raise InvalidLicense("Public encryption key does not match")
- is_scalable = bool(next(parsed_licence.get_object(81), None))
+ is_scalable = bool(next(parsed_licence.get_object(XMRObjectTypes.AUX_KEY_OBJECT), None))
for content_key in parsed_licence.get_content_keys():
cipher_type = Key.CipherType(content_key.cipher_type)
- if not cipher_type in (Key.CipherType.ECC_256, Key.CipherType.ECC_256_WITH_KZ, Key.CipherType.ECC_256_VIA_SYMMETRIC):
+ if cipher_type not in (Key.CipherType.ECC_256, Key.CipherType.ECC_256_WITH_KZ, Key.CipherType.ECC_256_VIA_SYMMETRIC):
raise InvalidLicense(f"Invalid cipher type {cipher_type}")
via_symmetric = Key.CipherType(content_key.cipher_type) == Key.CipherType.ECC_256_VIA_SYMMETRIC
@@ -265,7 +319,7 @@ class Cdm:
private_key=session.encryption_key,
ciphertext=content_key.encrypted_key
)
- ci, ck = decrypted[:16], decrypted[16:32]
+ ci, ck = decrypted[:16], decrypted[16:]
if is_scalable:
ci, ck = decrypted[::2][:16], decrypted[1::2][:16]
@@ -274,13 +328,12 @@ class Cdm:
embedded_root_license = content_key.encrypted_key[:144]
embedded_leaf_license = content_key.encrypted_key[144:]
- rgb_key = bytes(ck[i] ^ self._rgbMagicConstantZero[i] for i in range(16))
+ rgb_key = strxor(ck, self.rgbMagicConstantZero)
content_key_prime = AES.new(ck, AES.MODE_ECB).encrypt(rgb_key)
- aux_key = next(parsed_licence.get_object(81))["auxiliary_keys"][0]["key"]
- derived_aux_key = AES.new(content_key_prime, AES.MODE_ECB).encrypt(aux_key)
+ aux_key = next(parsed_licence.get_object(XMRObjectTypes.AUX_KEY_OBJECT))["auxiliary_keys"][0]["key"]
- uplink_x_key = bytes(bytearray(16)[i] ^ derived_aux_key[i] for i in range(16))
+ uplink_x_key = AES.new(content_key_prime, AES.MODE_ECB).encrypt(aux_key)
secondary_key = AES.new(ck, AES.MODE_ECB).encrypt(embedded_root_license[128:])
embedded_leaf_license = AES.new(uplink_x_key, AES.MODE_ECB).encrypt(embedded_leaf_license)
@@ -298,14 +351,12 @@ class Cdm:
key_length=content_key.key_length,
key=ck
))
- except InvalidLicense as e:
- raise InvalidLicense(e)
except Exception as e:
- raise Exception(f"Unable to parse license, {e}")
+ raise InvalidLicense(f"Unable to parse license: {e}")
def get_keys(self, session_id: bytes) -> List[Key]:
session = self.__sessions.get(session_id)
if not session:
- raise InvalidSession(f"Session identifier {session_id!r} is invalid.")
+ raise InvalidSession(f"Session identifier {session_id.hex()} is invalid.")
return session.keys
diff --git a/scripts/pyplayready/pyplayready/device/structs.py b/scripts/pyplayready/pyplayready/device/structs.py
index 7a7bda2..9ef07de 100644
--- a/scripts/pyplayready/pyplayready/device/structs.py
+++ b/scripts/pyplayready/pyplayready/device/structs.py
@@ -16,7 +16,7 @@ class DeviceStructs:
"group_certificate_length" / Int32ub,
"group_certificate" / Bytes(this.group_certificate_length),
"encryption_key" / Bytes(96),
- "signing_key" / Bytes(96),
+ "signing_key" / Bytes(96)
)
v3 = Struct(
@@ -24,7 +24,7 @@ class DeviceStructs:
"encryption_key" / Bytes(96),
"signing_key" / Bytes(96),
"group_certificate_length" / Int32ub,
- "group_certificate" / Bytes(this.group_certificate_length),
+ "group_certificate" / Bytes(this.group_certificate_length)
)
prd = Struct(
diff --git a/scripts/pyplayready/pyplayready/drmresults.py b/scripts/pyplayready/pyplayready/drmresults.py
new file mode 100644
index 0000000..2a4feca
--- /dev/null
+++ b/scripts/pyplayready/pyplayready/drmresults.py
@@ -0,0 +1,908 @@
+from enum import Enum
+
+
+class DRMResult(Enum):
+ """Holds Playready DRM results (error codes and their messages)"""
+
+ DRM_SUCCESS = (0x00000000, "Operation was successful.")
+ DRM_S_FALSE = (0x00000001, "Operation was successful, but returned a FALSE test condition.")
+ DRM_S_MORE_DATA = (0x00000002, "Operation was successful, but more data is available.")
+ DRM_S_TEST_SKIP_FILE = (0x0004C300, "Skip processing this file, not an eror.")
+ DRM_S_TEST_CONVERTED_FILE = (0x0004C301, "The file was converted to a PlayReady file during the action.")
+ DRM_E_OEM_CONSTRAINT_1_FAIL = (0x8004DFF, "Failed while OEM CONSTRAINT-1 check")
+ DRM_E_OEM_CONSTRAINT_2_FAIL = (0x8004DFF, "Failed while OEM CONSTRAINT-2 check")
+ DRM_E_OEM_CONSTRAINT_3_FAIL = (0x8004DFF, "Failed while OEM CONSTRAINT-3 check")
+ DRM_E_HDCP_NON_SECURE = (0x8004DFF, "Attempt to play back through insecure HDMI output path")
+ DRM_E_OUTOFMEMORY = (0x80000002, "Insufficient resources exist to complete the request.")
+ DRM_E_NOTIMPL = (0x80004001, "The requested operation is not implemented.")
+ DRM_E_POINTER = (0x80004003, "Invalid pointer.")
+ DRM_E_FAIL = (0x80004005, "The requested operation failed.")
+ DRM_E_HLOS_TAMPERED = (0x80004006, "HLOS tampered.")
+ DRM_E_OPL_BLOCKED = (0x80004007, "OPL blocked.")
+ DRM_E_LOAD_IMG = (0x80004008, "Failed on loading playready image.")
+ DRM_E_VER_MISMATCH = (0x80004009, "Version mismatch between HLOS and TZ.")
+ DRM_E_SET_BANDWIDTH = (0x8000400A, "Failed on setting bandwidth.")
+ DRM_E_OUT_OF_BOUND = (0x8000400B, "Out of bound.")
+ DRM_E_PLAY_ENABLER_BLOCKED = (0x8000400C, "WFD is blocked as play enabler object for HDCP doesn't exist in license")
+ DRM_E_HDMI_READ_FAIL = (0x8000400E, "Failed to read HDMI Status")
+ DRM_E_FILENOTFOUND = (0x80030002, "A requested file could not be found.")
+ DRM_E_FILEOPEN = (0x8003006E, "A request failed due to a file being open.")
+ DRM_E_VERIFICATION_FAILURE = (0x80040E80, "Validation of a Longhorn certificate failed.")
+ DRM_E_RSA_SIGNATURE_ERROR = (0x80040E82, "Error in RSA(PSS) signature.")
+ DRM_E_BAD_RSA_EXPONENT = (0x80040E86, "An incorrect RSA exponent was supplied for a public key.")
+ DRM_E_P256_CONVERSION_FAILURE = (0x80040E87, "An error occurred while converting between P256 types.")
+ DRM_E_P256_PKCRYPTO_FAILURE = (0x80040E88, "An error occurred in an asymmetric P256 cryptographic operation.")
+ DRM_E_P256_PLAINTEXT_MAPPING_FAILURE = (0x80040E89, "An error occurred while attempting to map a plaintext array to a EC Point: There is no conversion for this byte array to a EC Point.")
+ DRM_E_P256_INVALID_SIGNATURE = (0x80040E8A, "The ECDSA signature to be verified was not a valid signature format.")
+ DRM_E_P256_ECDSA_VERIFICATION_ERROR = (0x80040E8B, "The ECDSA verification algorithm encountered an unknown error.")
+ DRM_E_P256_ECDSA_SIGNING_ERROR = (0x80040E8C, "The ECDSA signature algorithm encountered an unknown error.")
+ DRM_E_P256_HMAC_KEYGEN_FAILURE = (0x80040E8D, "Could not generate a valid HMAC key under constraint where CK || HMACK is a valid x coord on the EC (P256).")
+ DRM_E_CH_VERSION_MISSING = (0x80041103, "Missing content header version.")
+ DRM_E_CH_KID_MISSING = (0x80041104, "Missing KID attribute in content header.")
+ DRM_E_CH_LAINFO_MISSING = (0x80041105, "Missing LAINFO attribute in content header.")
+ DRM_E_CH_CHECKSUM_MISSING = (0x80041106, "Missing content header checksum.")
+ DRM_E_CH_ATTR_MISSING = (0x80041107, "Missing content header attribute.")
+ DRM_E_CH_INVALID_HEADER = (0x80041108, "Invalid content header.")
+ DRM_E_CH_INVALID_CHECKSUM = (0x80041109, "Invalid checksum in the header.")
+ DRM_E_CH_UNABLE_TO_VERIFY = (0x8004110A, "Unable to verify signature of content header.")
+ DRM_E_CH_UNSUPPORTED_VERSION = (0x8004110B, "Unsupported content header version.")
+ DRM_E_CH_UNSUPPORTED_HASH_ALGORITHM = (0x8004110C, "Unsupported hash algorithm.")
+ DRM_E_CH_UNSUPPORTED_SIGN_ALGORITHM = (0x8004110D, "Unsupported signature algorithm.")
+ DRM_E_CH_BAD_KEY = (0x8004110E, "Invalid key.")
+ DRM_E_CH_INCOMPATIBLE_HEADER_TYPE = (0x8004110F, "Incompatible content header type.")
+ DRM_E_HEADER_ALREADY_SET = (0x80041110, "Content header type is already set. Reinitialize is required.")
+ DRM_E_CH_MULTIPLE_KIDS = (0x80041111, "Content header includes multiple KIDs. The operation requested is unsupported.")
+ DRM_E_CH_NOT_SIGNED = (0x80041113, "The header was not signed.")
+ DRM_E_CH_UNKNOWN_ERROR = (0x80041116, "Unknown Error.")
+ DRM_E_CDMIGRATIONTOOL_INVALID_FILE = (0x80041180, "File cannot be migrated because it is invalid.")
+ DRM_E_CDMIGRATIONTOOL_FILE_IS_NOT_CD_RIPPED = (0x80041181, "File cannot be migrated because it was not ripped from CD.")
+ DRM_E_CDMIGRATIONTOOL_FILE_IS_NOT_PROTECTED = (0x80041182, "File cannot be migrated because it is not protected.")
+ DRM_E_CDMIGRATIONTOOL_LICENSE_KID_INVALID = (0x80041183, "File cannot be migrated because the server returned a license with an invalid KID.")
+ DRM_E_CDMIGRATIONTOOL_LICENSE_KID_MISMATCH = (0x80041184, "File cannot be migrated because the server returned a license with a KID that did not match the content.")
+ DRM_E_CDMIGRATIONTOOL_LICENSE_CONTENT_KEY_INVALID = (0x80041185, "File cannot be migrated because the server returned a license with an invalid content key.")
+ DRM_E_CDMIGRATIONTOOL_INVALID_ASF_FORMAT = (0x80041186, "File cannot be migrated because the ASF is corrupt.")
+ DRM_E_CDMIGRATIONTOOL_INVALID_ASF_PACKETS = (0x80041187, "File cannot be migrated because the ASF packets are corrupt.")
+ DRM_E_CDMIGRATIONTOOL_CONTENT_KEY_CACHE_CORRUPT = (0x80041188, "File cannot be migrated because the content key obtained from the local cache is invalid.")
+ DRM_E_CDMIGRATIONTOOL_FILE_WRITE_ERROR = (0x80041189, "File cannot be migrated because the file could not be written.")
+ DRM_E_CDMIGRATIONTOOL_CANCELLED = (0x8004118A, "File migration was cancelled.")
+ LIC_INIT_FAILURE = (0x80041201, "LIC_INIT_FAILURE")
+ LIC_LICENSE_NOTSET = (0x80041202, "LIC_LICENSE_NOTSET")
+ LIC_PARAM_NOT_OPTIONAL = (0x80041203, "LIC_PARAM_NOT_OPTIONAL")
+ LIC_MEMORY_ALLOCATION_ERROR = (0x80041204, "LIC_MEMORY_ALLOCATION_ERROR")
+ LIC_INVALID_LICENSE = (0x80041205, "LIC_INVALID_LICENSE")
+ LIC_FIELD_MISSING = (0x80041206, "LIC_FIELD_MISSING")
+ DRM_E_LIC_UNSUPPORTED_VALUE = (0x80041207, " DRM_E_LIC_UNSUPPORTED_VALUE")
+ LIC_UNKNOWN_ERROR = (0x80041208, "LIC_UNKNOWN_ERROR")
+ LIC_INVALID_REVLIST = (0x80041209, "LIC_INVALID_REVLIST")
+ LIC_EXPIRED_CERT = (0x8004120A, "LIC_EXPIRED_CERT")
+ DRM_E_CDMI_INVALID_INITIALIZATION_DATA = (0x80041301, "Invalid initialization data.")
+ DRM_E_CDMI_PERSISTENT_LICENSE_FOR_NON_PERSISTENT_LICENSE_SESSION = (0x80041302, "A persistent license was provided for a session that was not persistent-license.")
+ DRM_E_CDMI_SECURE_STOP_LICENSE_FOR_NON_PERSISTENT_USAGE_RECORD_SESSION = (0x80041303, "A secure stop license was provided for a session that was not persistent-usage-record.")
+ DRM_E_CDMI_TEMPORARY_LICENSE_FOR_NON_TEMPORARY_SESSION = (0x80041304, "An in-memory-only license without secure-stop was provided for a session that was not temporary.")
+ DRM_E_CDMI_UNSUPPORTED_KEY_SYSTEM = (0x80041305, "The requested key system is not supported by PlayReady.")
+ DRM_E_CDMI_UNSUPPORTED_INITIALIZATION_DATA_TYPES = (0x80041306, "None of the requested initialization data types are supported by PlayReady.")
+ DRM_E_CDMI_UNSUPPORTED_DISTINCTIVE_IDENTIFIER = (0x80041307, "The requested distinctive identifier setting is not supported by PlayReady.")
+ DRM_E_CDMI_UNSUPPORTED_SESSION_TYPE = (0x80041308, "The requested session type is not supported by PlayReady.")
+ DRM_E_CDMI_UNSUPPORTED_INITIALIZATION_DATA = (0x80041309, "The provided initialization data is not supported by PlayReady.")
+ DRM_E_CDMI_SESSION_ALREADY_USED = (0x8004130A, "The session has already been used.")
+ DRM_E_CDMI_SESSION_UNINITIALIZED = (0x8004130B, "The session is not yet initialized.")
+ DRM_E_CDMI_SESSION_CLOSED = (0x8004130C, "The session is closed.")
+ DRM_E_CDMI_SESSION_ID_NOT_FOUND = (0x8004130D, "The given session ID could not be found.")
+ DRM_E_CDMI_SESSION_TYPE_MISMATCH = (0x8004130E, "The given session was initialized with a different session type than the session being loaded or Load/Remove was called on a temporary session.")
+ DRM_E_CDMI_SECURE_STOP_LICENSE_FOR_NON_PERSISTENT_USAGE_RECORD_SESSION_2 = (0x8004130F, "A secure stop license was provided for a session that was not persistent-usage-record.")
+ DRM_E_CPRMEXP_NOERROR = (0x80041400, "DRM_E_CPRMEXP_NOERROR") # actually the code for DRM_E_CPRMEXP_BASECODE
+ CPRMEXP_PARAM_NOT_OPTIONAL = (0x80041401, "CPRMEXP_PARAM_NOT_OPTIONAL")
+ CPRMEXP_MEMORY_ALLOCATION_ERROR = (0x80041402, "CPRMEXP_MEMORY_ALLOCATION_ERROR")
+ CPRMEXP_NO_OPERANDS_IN_EXPRESSION = (0x80041403, "CPRMEXP_NO_OPERANDS_IN_EXPRESSION")
+ CPRMEXP_INVALID_TOKEN = (0x80041404, "CPRMEXP_INVALID_TOKEN")
+ CPRMEXP_INVALID_CONSTANT = (0x80041405, "CPRMEXP_INVALID_CONSTANT")
+ CPRMEXP_INVALID_VARIABLE = (0x80041406, "CPRMEXP_INVALID_VARIABLE")
+ CPRMEXP_INVALID_FUNCTION = (0x80041407, "CPRMEXP_INVALID_FUNCTION")
+ CPRMEXP_INVALID_ARGUMENT = (0x80041408, "CPRMEXP_INVALID_ARGUMENT")
+ CPRMEXP_INVALID_CONTEXT = (0x80041409, "CPRMEXP_INVALID_CONTEXT")
+ CPRMEXP_ENDOFBUFFER = (0x8004140A, "CPRMEXP_ENDOFBUFFER")
+ CPRMEXP_MISSING_OPERAND = (0x8004140B, "CPRMEXP_MISSING_OPERAND")
+ CPRMEXP_OVERFLOW = (0x8004140C, "CPRMEXP_OVERFLOW")
+ CPRMEXP_UNDERFLOW = (0x8004140D, "CPRMEXP_UNDERFLOW")
+ CPRMEXP_INCORRECT_NUM_ARGS = (0x8004140E, "CPRMEXP_INCORRECT_NUM_ARGS")
+ CPRMEXP_VARIABLE_EXPECTED = (0x8004140F, "CPRMEXP_VARIABLE_EXPECTED")
+ CPRMEXP_RETRIEVAL_FAILURE = (0x80041410, "CPRMEXP_RETRIEVAL_FAILURE")
+ CPRMEXP_UPDATE_FAILURE = (0x80041411, "CPRMEXP_UPDATE_FAILURE")
+ CPRMEXP_STRING_UNTERMINATED = (0x80041412, "CPRMEXP_STRING_UNTERMINATED")
+ CPRMEXP_UPDATE_UNSUPPORTED = (0x80041413, "CPRMEXP_UPDATE_UNSUPPORTED")
+ CPRMEXP_ISOLATED_OPERAND_OR_OPERATOR = (0x80041414, "CPRMEXP_ISOLATED_OPERAND_OR_OPERATOR")
+ CPRMEXP_UNMATCHED = (0x80041415, "CPRMEXP_UNMATCHED")
+ CPRMEXP_WRONG_TYPE_OPERAND = (0x80041416, "CPRMEXP_WRONG_TYPE_OPERAND")
+ CPRMEXP_TOO_MANY_OPERANDS = (0x80041417, "CPRMEXP_TOO_MANY_OPERANDS")
+ CPRMEXP_UNKNOWN_PARSE_ERROR = (0x80041418, "CPRMEXP_UNKNOWN_PARSE_ERROR")
+ CPRMEXP_UNSUPPORTED_FUNCTION = (0x80041419, "CPRMEXP_UNSUPPORTED_FUNCTION")
+ CPRMEXP_CLOCK_REQUIRED = (0x8004141A, "CPRMEXP_CLOCK_REQUIRED")
+ DRM_E_LIC_KEY_DECODE_FAILURE = (0x80048007, "Key decode failure.")
+ DRM_E_LIC_SIGNATURE_FAILURE = (0x80048008, "License signature failure.")
+ DRM_E_LIC_KEY_AND_CERT_MISMATCH = (0x80048013, "Key and cert mismatch.")
+ DRM_E_KEY_MISMATCH = (0x80048014, "A public/private keypair is mismatched.")
+ DRM_E_INVALID_SIGNATURE = (0x800480CF, "License signature failure.")
+ DRM_E_SYNC_ENTRYNOTFOUND = (0x800480D0, "An entry was not found in the sync store.")
+ DRM_E_STACKTOOSMALL = (0x800480D1, "The stack supplied to the DRM API was too small.")
+ DRM_E_CIPHER_NOT_INITIALIZED = (0x800480D2, "The DRM Cipher routines were not correctly initialized before calling encryption/decryption routines.")
+ DRM_E_DECRYPT_NOT_INITIALIZED = (0x800480D3, "The DRM decrypt routines were not correctly initialized before trying to decrypt data.")
+ DRM_E_SECURESTORE_LOCK_NOT_OBTAINED = (0x800480D4, "Before reading or writing data to securestore in raw mode, first the lock must be obtained using DRM_SST_OpenData.")
+ DRM_E_PKCRYPTO_FAILURE = (0x800480D5, "An error occurred in an asymmetric cryptographic operation.")
+ DRM_E_INVALID_DST_SLOT_SIZE = (0x800480D6, "Invalid DST slot size is specified.")
+ DRM_E_UNSUPPORTED_VERSION = (0x80049005, " DRM_E_UNSUPPORTED_VERSION")
+ DRMUTIL_EXPIRED_CERT = (0x80049006, "DRMUTIL_EXPIRED_CERT")
+ DRMUTIL_INVALID_CERT = (0x80049007, "DRMUTIL_INVALID_CERT")
+ DRM_E_DEVICE_NOT_REGISTERED = (0x8004A000, "The DEVICEID does not exist in the device store")
+ DRM_E_TOO_MANY_INCLUSION_GUIDS = (0x8004A001, "The license contained more than DRM_MAX_INCLUSION_GUIDS entries in its inclusion list")
+ DRM_E_REVOCATION_GUID_NOT_RECOGNIZED = (0x8004A002, "The revocation list type GUID was not recognized")
+ DRM_E_LIC_CHAIN_TOO_DEEP = (0x8004A003, "The license chained deeper than this implementation can handle")
+ DRM_E_DEVICE_SECURITY_LEVEL_TOO_LOW = (0x8004A004, "The security level of the remote device is too low to receive the license")
+ DRM_E_DST_BLOCK_CACHE_CORRUPT = (0x8004A005, "The block header cache returned invalid data")
+ DRM_E_CONTRACT_FAILED = (0x8004A006, "The error code returned by the API is not present in the contract")
+ DRM_E_DST_BLOCK_CACHE_MISS = (0x8004A007, "The block header cache didn't contain the requested block header")
+ DRM_E_INVALID_METERRESPONSE_SIGNATURE = (0x8004A013, "Invalid signature in meter response")
+ DRM_E_INVALID_LICENSE_REVOCATION_LIST_SIGNATURE = (0x8004A014, "Invalid signature in license revocation list.")
+ DRM_E_INVALID_METERCERT_SIGNATURE = (0x8004A015, "Invalid signature in metering certificate")
+ DRM_E_METERSTORE_DATA_NOT_FOUND = (0x8004A016, "Metering data slot not found due to bad data in response file")
+ DRM_E_NO_LICENSES_TO_SYNC = (0x8004A017, "No more licenses to sync")
+ DRM_E_INVALID_REVOCATION_LIST = (0x8004A018, "The revocation list version does not match the current revocation version")
+ DRM_E_ENVELOPE_CORRUPT = (0x8004A019, "The envelope archive or file is corrupt")
+ DRM_E_ENVELOPE_FILE_NOT_COMPATIBLE = (0x8004A01A, "The envelope file is not compatible with this version of the porting kit")
+ DRM_E_EXTENDED_RESTRICTION_NOT_UNDERSTOOD = (0x8004A01B, "An extensible restriction was not understood by the app, and is mark as being required")
+ DRM_E_INVALID_SLK = (0x8004A01C, "An ILA SLK (symmetric session key) was found, but did not contain valid data")
+ DRM_E_DEVCERT_MODEL_MISMATCH = (0x8004A01D, "The model string in the certificate does not match the model of the device and so the cert must be re-generated.")
+ DRM_E_OUTDATED_REVOCATION_LIST = (0x8004A01E, "The revocation list is outdated. It is required for the revocation list to be refreshed at least every 90 days.")
+ DRM_E_DSTR_NOT_FOUND = (0x8004A01F, "The substring search inside a DRM string failed.")
+ DRM_E_DEVICE_NOT_INITIALIZED = (0x8004C001, "This device has not been initialized against a DRM init service")
+ DRM_E_DRM_NOT_INITIALIZED = (0x8004C002, "The app has not call DRM_Init properly")
+ DRM_E_INVALIDRIGHT = (0x8004C003, "A right in the license in invalid")
+ DRM_E_INCOMPATABLELICENSESIZE = (0x8004C004, "The size of the license is incompatable. DRM doesn't understand this license")
+ DRM_E_INVALIDLICENSEFLAGS = (0x8004C005, "The flags in the license are invalid. DRM either doesn't understand them or they are conflicting")
+ DRM_E_INVALID_LICENSE = (0x8004C006, "The license is invalid")
+ DRM_E_CONDITIONFAIL = (0x8004C007, "A condition in the license failed to pass")
+ DRM_E_CONDITIONNOTSUPPORTED = (0x8004C008, "A condition in the license is not supported by this verison of DRM")
+ DRM_E_LICENSE_EXPIRED = (0x8004C009, "The license has expired either by depleting a play count or via an end time.")
+ DRM_E_LICENSENOTYETVALID = (0x8004C00A, "The license start time had not come to pass yet.")
+ DRM_E_RIGHTS_NOT_AVAILABLE = (0x8004C00B, "The rights the app has requested are not available in the license")
+ DRM_E_LICENSEMISMATCH = (0x8004C00C, "The license content id/ sku id doesn't match that requested by the app")
+ DRM_E_WRONG_PARAMETER_TYPE = (0x8004C00D, "The token parameter was of an incompatible type.") # renamed from DRM_E_WRONG_TOKEN_TYPE so it doesn't conflict with a duplicate that has a different code
+ DRM_E_NORIGHTSREQUESTED = (0x8004C00E, "The app has not requested any rights before trying to bind")
+ DRM_E_LICENSE_NOT_BOUND = (0x8004C00F, "A license has not been bound to. Decrypt can not happen without a successful bind call")
+ DRM_E_HASH_MISMATCH = (0x8004C010, "A Keyed Hash check failed.")
+ DRM_E_INVALIDTIME = (0x8004C011, "A time structure is invalid.")
+ DRM_E_LICENSESTORENOTFOUND = (0x8004C012, "The external license store was not found.")
+ DRM_E_LICENSE_NOT_FOUND = (0x8004C013, "A license was not found in the license store.")
+ DRM_E_LICENSE_VERSION_NOT_SUPPORTED = (0x8004C014, "The DRM license version is not supported by the DRM version on the device.")
+ DRM_E_INVALIDBINDID = (0x8004C015, "The bind id is invalid.")
+ DRM_E_UNSUPPORTED_ALGORITHM = (0x8004C016, "The encryption algorithm required for this operation is not supported.")
+ DRM_E_ALGORITHMNOTSET = (0x8004C017, "The encryption algorithm required for this operation is not supported.")
+ DRM_E_LICENSESERVERNEEDSKEY = (0x8004C018, "The license server needs a version of the device bind key from the init service.")
+ DRM_E_INVALID_LICENSE_STORE = (0x8004C019, "The license store version number is incorrect, or the store is invalid in some other way.")
+ DRM_E_FILE_READ_ERROR = (0x8004C01A, "There was an error reading a file.")
+ DRM_E_FILE_WRITE_ERROR = (0x8004C01B, "There was an error writing a file.")
+ DRM_E_CLIENTTIMEINVALID = (0x8004C01C, "The time/clock on the device is not in sync with the license server within tolerance.")
+ DRM_E_DST_STORE_FULL = (0x8004C01D, "The data store is full.")
+ DRM_E_NO_XML_OPEN_TAG = (0x8004C01E, "XML open tag not found")
+ DRM_E_NO_XML_CLOSE_TAG = (0x8004C01F, "XML close tag not found")
+ DRM_E_INVALID_XML_TAG = (0x8004C020, "Invalid XML tag")
+ DRM_E_NO_XML_CDATA = (0x8004C021, "No XML CDATA found")
+ DRM_E_DSTNAMESPACEFULL = (0x8004C022, "No more room for DST Namespace")
+ DRM_E_DST_NAMESPACE_NOT_FOUND = (0x8004C023, "No DST Namespace found")
+ DRM_E_DST_SLOT_NOT_FOUND = (0x8004C024, "DST Dataslot not found")
+ DRM_E_DST_SLOT_EXISTS = (0x8004C025, "DST Dataslot already exists")
+ DRM_E_DST_CORRUPTED = (0x8004C026, "The data store is corrupted")
+ DRM_E_DST_SEEK_ERROR = (0x8004C027, "There was an error attempting to seek in the Data Store")
+ DRM_E_DSTNAMESPACEINUSE = (0x8004C028, "No DST Namespace in use")
+ DRM_E_INVALID_SECURESTORE_PASSWORD = (0x8004C029, "The password used to open the secure store key was not able to validate the secure store hash.")
+ DRM_E_SECURESTORE_CORRUPT = (0x8004C02A, "The secure store is corrupt")
+ DRM_E_SECURESTORE_FULL = (0x8004C02B, "The current secure store key is full. No more data can be added.")
+ DRM_E_NOACTIONINLICENSEREQUEST = (0x8004C02C, "No action(s) added for license request")
+ DRM_E_DUPLICATED_HEADER_ATTRIBUTE = (0x8004C02D, "Duplicated attribute in Header")
+ DRM_E_NO_KID_IN_HEADER = (0x8004C02E, "No KID attribute in Header")
+ DRM_E_NO_LAINFO_IN_HEADER = (0x8004C02F, "No LAINFO attribute in Header")
+ DRM_E_NO_CHECKSUM_IN_HEADER = (0x8004C030, "No Checksum attribute in Header")
+ DRM_E_DST_BLOCK_MISMATCH = (0x8004C031, "DST block mismatch")
+ DRM_E_BACKUP_EXISTS = (0x8004C032, "Backup file already exist.")
+ DRM_E_LICENSE_TOOLONG = (0x8004C033, "License size is too long")
+ DRM_E_DST_EXISTS = (0x8004C034, "A DST already exists in the specified location")
+ DRM_E_INVALID_DEVICE_CERTIFICATE = (0x8004C035, "The device certificate is invalid.")
+ DRM_E_DST_LOCK_FAILED = (0x8004C036, "Locking a segment of the DST failed.")
+ DRM_E_FILE_SEEK_ERROR = (0x8004C037, "File Seek Error")
+ DRM_E_DST_NOT_LOCKED_EXCLUSIVE = (0x8004C038, "Existing lock is not exclusive")
+ DRM_E_DST_EXCLUSIVE_LOCK_ONLY = (0x8004C039, "Only exclusive lock is accepted")
+ DRM_E_DSTRESERVEDKEYDETECTED = (0x8004C03A, "DST reserved key value detected in UniqueKey")
+ DRM_E_V1_NOT_SUPPORTED = (0x8004C03B, "V1 Lic Acquisition is not supported")
+ DRM_E_HEADER_NOT_SET = (0x8004C03C, "Content header is not set")
+ DRM_E_NEEDDEVCERTINDIV = (0x8004C03D, "The device certificate is template. It need Devcert Indiv")
+ DRM_E_MACHINE_ID_MISMATCH = (0x8004C03E, "The device has Machine Id different from that in devcert.")
+ DRM_E_CLK_INVALID_RESPONSE = (0x8004C03F, "The secure clock response is invalid.")
+ DRM_E_CLK_INVALID_DATE = (0x8004C040, "The secure clock response is invalid.")
+ DRM_E_CLK_UNSUPPORTED_VALUE = (0x8004C041, "The secure clock response has unsupported value.")
+ DRM_E_INVALIDDEVICECERTIFICATETEMPLATE = (0x8004C042, "The device certificate is invalid.")
+ DRM_E_DEVCERT_EXCEEDS_SIZE_LIMIT = (0x8004C043, "The device certificate exceeds max size")
+ DRM_E_DEVCERTTEMPLATEEXCEEDSSIZELIMIT = (0x8004C044, "The device certificate template exceeds max size")
+ DRM_E_DEVCERTREADERROR = (0x8004C045, "Can't get the device certificate")
+ DRM_E_DEVCERTWRITEERROR = (0x8004C046, "Can't store the device certificate")
+ DRM_E_PRIVKEY_READ_ERROR = (0x8004C047, "Can't get device private key")
+ DRM_E_PRIVKEYWRITEERROR = (0x8004C048, "Can't store device private key")
+ DRM_E_DEVCERT_TEMPLATE_READ_ERROR = (0x8004C049, "Can't get the device certificate template")
+ DRM_E_CLK_NOT_SUPPORTED = (0x8004C04A, "The secure clock is not supported.")
+ DRM_E_DEVCERTINDIV_NOT_SUPPORTED = (0x8004C04B, "The Devcert Indiv is not supported.")
+ DRM_E_METERING_NOT_SUPPORTED = (0x8004C04C, "The Metering is not supported.")
+ DRM_E_CLK_RESETSTATEREADERROR = (0x8004C04D, "Can not read Secure clock Reset State.")
+ DRM_E_CLK_RESETSTATEWRITEERROR = (0x8004C04E, "Can not write Secure clock Reset State.")
+ DRM_E_XMLNOTFOUND = (0x8004C04F, "a required XML tag was not found")
+ DRM_E_METERING_WRONG_TID = (0x8004C050, "wrong TID sent on metering response")
+ DRM_E_METERING_INVALID_COMMAND = (0x8004C051, "wrong command sent on metering response")
+ DRM_E_METERING_STORE_CORRUPT = (0x8004C052, "The metering store is corrupt")
+ DRM_E_CERTIFICATE_REVOKED = (0x8004C053, "A certificate given to DRM was revoked.")
+ DRM_E_CRYPTO_FAILED = (0x8004C054, "A cryptographic operation failed.")
+ DRM_E_STACK_CORRUPT = (0x8004C055, "The stack allocator context is corrupt. Likely a buffer overrun problem.")
+ DRM_E_UNKNOWN_BINDING_KEY = (0x8004C056, "A matching binding key could not be found for the license.")
+ DRM_E_V1_LICENSE_CHAIN_NOT_SUPPORTED = (0x8004C057, "License chaining with V1 content is not supported.")
+ DRM_E_WRONG_TOKEN_TYPE = (0x8004C058, "The wrong type of token was used.")
+ DRM_E_POLICY_METERING_DISABLED = (0x8004C059, "Metering code was called but metering is disabled by group or user policy")
+ DRM_E_POLICY_ONLINE_DISABLED = (0x8004C05A, "online communication is disabled by group policy")
+ DRM_E_CLK_NOT_SET = (0x8004C05B, "Time based licenses can not be used because the secure clock is not set on the device.")
+ DRM_E_NO_CLK_SUPPORTED = (0x8004C05C, "Time based licenses can not be used because the device does not support any clock.")
+ DRM_E_NO_URL = (0x8004C05D, "Can not find URL info.")
+ DRM_E_UNKNOWN_DEVICE_PROPERTY = (0x8004C05E, "Unknown device property.")
+ DRM_E_METERING_MID_MISMATCH = (0x8004C05F, "The metering ID is not same in Metering Cert and metering response data")
+ DRM_E_METERING_RESPONSE_DECRYPT_FAILED = (0x8004C060, "The encrypted section of metering response can not be decrypted")
+ DRM_E_RIV_TOO_SMALL = (0x8004C063, "RIV on the machine is too small.")
+ DRM_E_STACK_ALREADY_INITIALIZED = (0x8004C064, "DRM_STK_Init called for initialized stack")
+ DRM_E_DEVCERT_REVOKED = (0x8004C065, "The device certificate given to DRM is revoked.")
+ DRM_E_OEM_RSA_DECRYPTION_ERROR = (0x8004C066, "Error in OEM RSA Decryption.")
+ DRM_E_INVALID_DEVSTORE_ATTRIBUTE = (0x8004C067, "Invalid device attributes in the device store")
+ DRM_E_INVALID_DEVSTORE_ENTRY = (0x8004C068, "The device store data entry is corrupted")
+ DRM_E_OEM_RSA_ENCRYPTION_ERROR = (0x8004C069, "Error in OEM RSA Encryption process")
+ DRM_E_DST_NAMESPACE_EXISTS = (0x8004C06A, "The DST Namespace already exists.")
+ DRM_E_PERF_SCOPING_ERROR = (0x8004C06B, "Error in performance scope context")
+ DRM_E_PRECISION_ARITHMETIC_FAIL = (0x8004C06C, "Operation involving multiple precision arithmetic fails")
+ DRM_E_OEM_RSA_INVALID_PRIVATE_KEY = (0x8004C06D, "Invalid private key.")
+ DRM_E_NO_OPL_CALLBACK = (0x8004C06E, "There is no callback function to process the output restrictions specified in the license")
+ DRM_E_INVALID_PLAYREADY_OBJECT = (0x8004C06F, "Structure of PlayReady object is invalid")
+ DRM_E_DUPLICATE_LICENSE = (0x8004C070, "There is already a license in the store with the same KID & LID")
+ DRM_E_REVOCATION_NOT_SUPPORTED = (0x8004C071, "Device does not support revocation, while revocation data was placed into license policy structure.")
+ DRM_E_RECORD_NOT_FOUND = (0x8004C072, "Record with requested type was not found in PlayReady object.")
+ DRM_E_BUFFER_BOUNDS_EXCEEDED = (0x8004C073, "An array is being referenced outside of it's bounds.")
+ DRM_E_INVALID_BASE64 = (0x8004C074, "An input string contains invalid Base64 characters.")
+ DRM_E_PROTOCOL_VERSION_NOT_SUPPORTED = (0x8004C075, "The protocol version is not supported.")
+ DRM_E_INVALID_LICENSE_RESPONSE_SIGNATURE = (0x8004C076, "Cannot verify license acquisition's response because signature is invalid.")
+ DRM_E_INVALID_LICENSE_RESPONSE_ID = (0x8004C077, "Cannot verify license acquisition's response because response ID is invalid.")
+ DRM_E_LICENSE_RESPONSE_SIGNATURE_MISSING = (0x8004C078, "Cannot verify license acquisition's response because either response ID, license nonce or signature is missing.")
+ DRM_E_INVALID_DOMAIN_JOIN_RESPONSE_SIGNATURE = (0x8004C079, "Cannot verify domain join response because signature is invalid.")
+ DRM_E_DOMAIN_JOIN_RESPONSE_SIGNATURE_MISSING = (0x8004C07A, "Cannot verify domain join response because either signing certificate chain or signature is missing.")
+ DRM_E_ACTIVATION_REQUIRED = (0x8004C07B, "The device must be activated before initialization can succeed.")
+ DRM_E_ACTIVATION_INTERNAL_ERROR = (0x8004C07C, "A server error occurred during device activation.")
+ DRM_E_ACTIVATION_GROUP_CERT_REVOKED_ERROR = (0x8004C07D, "The activation group cert has been revoked and the application must be updated with a new client lib.")
+ DRM_E_ACTIVATION_NEW_CLIENT_LIB_REQUIRED_ERROR = (0x8004C07E, "The client lib used by the application is not supported and must be updated.")
+ DRM_E_ACTIVATION_BAD_REQUEST = (0x8004C07F, "The activation request is invalid")
+ DRM_E_FILEIO_ERROR = (0x8004C080, "Encountered a system error during file I/O.")
+ DRM_E_DISKSPACE_ERROR = (0x8004C081, "Out of disk space for storing playready files.")
+ DRM_E_UPLINK_LICENSE_NOT_FOUND = (0x8004C082, "A license was found in the license store but no license was found for its uplink ID.")
+ DRM_E_ACTIVATION_CLIENT_ALREADY_CURRENT = (0x8004C083, "The activation client already has the lastest verion.")
+ DRM_E_LICENSE_REALTIME_EXPIRED = (0x8004C084, "The license has expired during decryption due to the RealTimeExpiration Restriction.")
+ DRM_E_DECRYPTOR_CANNOT_CLONE = (0x8004C085, "The decryptor cannot be cloned due to restrictions in the corresponding license.")
+ DRM_E_ACTIVATION_REQUIRED_REACTIVATION_POSSIBLE = (0x8004C086, "The device must be activated or reactivated before initialization can succeed.")
+ DRM_E_LRB_NOLGPUBKEY = (0x8004C0A0, "LRB does not contain a valid LGPUBKEY.")
+ DRM_E_LRB_INVALIDSIGNATURE = (0x8004C0A1, "Signature inside LRB is invalid.")
+ DRM_E_LRB_LGPUBKEY_MISMATCH = (0x8004C0A2, "LRB is signed with a pubkey different from LGPUBKEY")
+ DRM_E_LRB_INVALIDLICENSEDATA = (0x8004C0A3, "LRB is signed with a pubkey different from LGPUBKEY")
+ DRM_E_LICEVAL_LICENSE_NOT_SUPPLIED = (0x8004C0C0, "License not supplied in the liceval context")
+ DRM_E_LICEVAL_KID_MISMATCH = (0x8004C0C1, "Mismatch between KID from header and the one inside license")
+ DRM_E_LICEVAL_LICENSE_REVOKED = (0x8004C0C2, "License for this content has been revoked")
+ DRM_E_LICEVAL_UPDATE_FAILURE = (0x8004C0C3, "Failed to update content revocation")
+ DRM_E_LICEVAL_REQUIRED_REVOCATION_LIST_NOT_AVAILABLE = (0x8004C0C4, "Failed to update content revocation")
+ DRM_E_LICEVAL_INVALID_PRND_LICENSE = (0x8004C0C5, "License is an invalid PRND license. PRND license cannot have metering ID, expire-after-first-play or domain properties.")
+ DRM_E_XMR_OBJECT_ALREADY_EXISTS = (0x8004C0E0, "XMR builder context already has this object.")
+ DRM_E_XMR_OBJECT_NOTFOUND = (0x8004C0E1, "XMR object was not found.")
+ DRM_E_XMR_REQUIRED_OBJECT_MISSING = (0x8004C0E2, "XMR license doesn't have one or more required objects.")
+ DRM_E_XMR_INVALID_UNKNOWN_OBJECT = (0x8004C0E3, "Invalid unknown object")
+ DRM_E_XMR_LICENSE_BINDABLE = (0x8004C0E4, "XMR license does not contain the Cannot Bind right")
+ DRM_E_XMR_LICENSE_NOT_BINDABLE = (0x8004C0E5, "XMR license cannot be bound to because of the Cannot Bind right")
+ DRM_E_XMR_UNSUPPORTED_XMR_VERSION = (0x8004C0E6, "The version of XMR license is not supported for the current action")
+ DRM_E_NOT_CRL_BLOB = (0x8004C100, "CRL blob provided for parsing does not start with CBLB. It means file is not CRL blob at all.")
+ DRM_E_BAD_CRL_BLOB = (0x8004C101, "The file is structured as CRL blob, but there is some error in file structure or one of CRLs inside is invalid.")
+ DRM_E_INVALID_DEVCERT_ATTRIBUTE = (0x8004C200, "The attributes in the Device certificate are invalid")
+ DRM_E_TEST_PKCRYPTO_FAILURE = (0x8004C300, "Error in PK encryption/decryption crypto test cases.")
+ DRM_E_TEST_PKSIGN_VERIFY_ERROR = (0x8004C301, "Digital signature verification failed.")
+ DRM_E_TEST_ENCRYPT_ERROR = (0x8004C302, "Error in encryption of cipher text.")
+ DRM_E_TEST_RC4KEY_FAILED = (0x8004C303, "RC4 key failed during crypto operations.")
+ DRM_E_TEST_DECRYPT_ERROR = (0x8004C304, "Error in cipher text decryption.")
+ DRM_E_TEST_DESKEY_FAILED = (0x8004C305, "Decrypted data not equal to original data in a DES operation.")
+ DRM_E_TEST_CBC_INVERSEMAC_FAILURE = (0x8004C306, "Decrypted data not equal to original in Inverse MAC operation.")
+ DRM_E_TEST_HMAC_FAILURE = (0x8004C307, "Error in hashed data in HMAC operation.")
+ DRM_E_TEST_INVALIDARG = (0x8004C308, "Error in the number of arguments or argument data in Test files.")
+ DRM_E_TEST_DEVICE_PRIVATE_KEY_INCORRECTLY_STORED = (0x8004C30A, "DRMManager context should not contain the device private key.")
+ DRM_E_TEST_DRMMANAGER_CONTEXT_NULL = (0x8004C30B, "DRMManager context is NULL.")
+ DRM_E_TEST_UNEXPECTED_REVINFO_RESULT = (0x8004C30C, "Revocation cache result was not as expected.")
+ DRM_E_TEST_RIV_MISMATCH = (0x8004C30D, "Revocation Info Version(RIV) mismatch.")
+ DRM_E_TEST_URL_ERROR = (0x8004C310, "There is an error in the URL from the challenge generated.")
+ DRM_E_TEST_MID_MISMATCH = (0x8004C311, "The MIDs returned from the DRM_MANAGER_CONTEXT does not match the test input.")
+ DRM_E_TEST_METER_CERTIFICATE_MISMATCH = (0x8004C312, "The input data does not match with the Metering certificate returned from the license.")
+ DRM_E_TEST_LICENSE_STATE_MISMATCH = (0x8004C313, "The input data and license state returned from the license do not match.")
+ DRM_E_TEST_SOURCE_ID_MISMATCH = (0x8004C316, "The input data and license state returned from the license do not match.")
+ DRM_E_TEST_UNEXPECTED_LICENSE_COUNT = (0x8004C317, "The input data and the number of license from the KID do not match.")
+ DRM_E_TEST_UNEXPECTED_DEVICE_PROPERTY = (0x8004C318, "Unknown device property.")
+ DRM_E_TEST_DRMMANAGER_MISALIGNED_BYTES = (0x8004C319, "Error due to misalignment of bytes.")
+ DRM_E_TEST_LICENSE_RESPONSE_ERROR = (0x8004C31A, "The license response callbacks did not provide the expected data.")
+ DRM_E_TEST_OPL_MISMATCH = (0x8004C31B, "The minimum levels of the compressed/uncompressed Digital and Analog Video do not match the OPL.")
+ DRM_E_TEST_INVALID_OPL_CALLBACK = (0x8004C31C, "The callback type supplied is not valid.")
+ DRM_E_TEST_INCOMPLETE = (0x8004C31D, "The test function failed to complete.")
+ DRM_E_TEST_UNEXPECTED_OUTPUT = (0x8004C31E, "The output of the function being tested does not match the expected output.")
+ DRM_E_TEST_DLA_NO_CONTENT_HEADER = (0x8004C31F, "Content Header Information was not retrieved correctly in DLA Sync Tests.")
+ DRM_E_TEST_DLA_CONTENT_HEADER_FOUND = (0x8004C320, "Content Header Information was found when it should not have been in DLA Sync Tests.")
+ DRM_E_TEST_SYNC_LSD_INCORRECT = (0x8004C321, "DRM_SNC_GetSyncStoreEntry returned incorrect License State Data.")
+ DRM_E_TEST_TOO_SLOW = (0x8004C322, "The performance test failed because DRM took longer than its maximum time.")
+ DRM_E_TEST_LICENSESTORE_NOT_OPEN = (0x8004C323, "The License Store contexts in the App Manager context are not open.")
+ DRM_E_TEST_DEVICE_NOT_INITED = (0x8004C324, "The device instance has not been initialized prior to use.")
+ DRM_E_TEST_VARIABLE_NOT_SET = (0x8004C325, "A global variable needed for test execution has not been set correctly.")
+ DRM_E_TEST_NOMORE = (0x8004C326, "The same as DRM_E_NOMORE, only explicitly used in test code.")
+ DRM_E_TEST_FILE_LOAD_ERROR = (0x8004C327, "There was an error loading a test data file.")
+ DRM_E_TEST_LICENSE_ACQ_FAILED = (0x8004C328, "The attempt to acquire a license failed.")
+ DRM_E_TEST_UNSUPPORTED_FILE_FORMAT = (0x8004C329, "A file format is being used which is not supported by the test function.")
+ DRM_E_TEST_PARSING_ERROR = (0x8004C32A, "There was an error parsing input parameter.")
+ DRM_E_TEST_NOTIMPL = (0x8004C32B, "The specified test API is not implemented.")
+ DRM_E_TEST_VARIABLE_NOTFOUND = (0x8004C32C, "The specified test varaible was not found in the shared variable table.")
+ DRM_E_TEST_VARIABLE_LISTFULL = (0x8004C32D, "The shared test variable table is full.")
+ DRM_E_TEST_UNEXPECTED_CONTENT_PROPERTY = (0x8004C32E, "Unknown content property.")
+ DRM_E_TEST_PRO_HEADER_NOT_SET = (0x8004C32F, "PlayReady Object Header not set.")
+ DRM_E_TEST_NON_PRO_HEADER_TYPE = (0x8004C330, "Incompatible header - PlayReady Object Header expected.")
+ DRM_E_TEST_INVALID_DEVICE_WRAPPER = (0x8004C331, "The Device Simulator Device Wrapper is not valid.")
+ DRM_E_TEST_INVALID_WMDM_WRAPPER = (0x8004C332, "The Device Simulator WMDM Wrapper is not valid.")
+ DRM_E_TEST_INVALID_WPD_WRAPPER = (0x8004C333, "The Device Simulator WPD Wrapper is not valid.")
+ DRM_E_TEST_INVALID_FILE = (0x8004C334, "The data file given was invalid.")
+ DRM_E_TEST_PROPERTY_NOT_FOUND = (0x8004C335, "The object did not have the property which was queried.")
+ DRM_E_TEST_METERING_DATA_INCORRECT = (0x8004C336, "The metering data reported is incorrect.")
+ DRM_E_TEST_FILE_ALREADY_OPEN = (0x8004C337, "The handle variable for a test file is not NULL. This indicates that a file was opened and not closed properly.")
+ DRM_E_TEST_FILE_NOT_OPEN = (0x8004C338, "The handle variable for a test file is NULL. This indicates that a file was not opened.")
+ DRM_E_TEST_PICT_COLUMN_TOO_WIDE = (0x8004C339, "The PICT input file contains a column which is too wide for the test parser to handle.")
+ DRM_E_TEST_PICT_COLUMN_MISMATCH = (0x8004C33A, "The PICT input file contains a row which doesn't have the same number of columns as the header row.")
+ DRM_E_TEST_TUX_TEST_SKIPPED = (0x8004C33B, "TUX cannot find the speficied test case in target dll. Test Skipped.")
+ DRM_E_TEST_KEYFILE_VERIFICATION_FAILURE = (0x8004C33C, "Verification of the Keyfile context failed.")
+ DRM_E_TEST_DATA_VERIFICATION_FAILURE = (0x8004C33D, "Data does not match expected value and failed verification.")
+ DRM_E_TEST_NET_FAIL = (0x8004C33E, "The Test failed to perform Network I/O.")
+ DRM_E_TEST_CLEANUP_FAIL = (0x8004C33F, "A failure occurred during the test case cleanup phase.")
+ DRM_E_TEST_LICGEN_UNSUPPORTED_VALUE = (0x8004C340, "A property used during license generation is not supported.")
+ DRM_E_LOGICERR = (0x8004C3E8, "DRM code has a logic error in it. This result should never be returned. There is an unhandled code path if it is returned.")
+ DRM_E_INVALID_REV_INFO = (0x8004C3E9, "The rev info blob is invalid.")
+ DRM_E_SYNCLISTNOTSUPPORTED = (0x8004C3EA, "The device does not support synclist.")
+ DRM_E_REVOCATION_BUFFER_TOO_SMALL = (0x8004C3EB, "The revocation buffer is too small.")
+ DRM_E_DEVICE_ALREADY_REGISTERED = (0x8004C3EC, "There exists already a device in the device store with the same DEVICEID that was given.")
+ DRM_E_DST_NOT_COMPATIBLE = (0x8004C3ED, "The data store version is incompatible with this version of DRM.")
+ DRM_E_RSA_DECRYPTION_ERROR = (0x8004C3F0, "The data block/Encoded message used in OAEP decoding is incorrect.")
+ DRM_E_OEM_RSA_MESSAGE_TOO_BIG = (0x8004C3F1, "The base message buffer is larger than the given modulus.")
+ DRM_E_METERCERT_NOT_FOUND = (0x8004C3F2, "The metering certificate was not found in the store.")
+ DRM_E_MODULAR_ARITHMETIC_FAILURE = (0x8004C3F3, "A failure occurred in bignum modular arithmetic.")
+ DRM_E_FEATURE_NOT_SUPPORTED = (0x8004C3F4, "The feature is not supported in this release.")
+ DRM_E_REVOCATION_INVALID_PACKAGE = (0x8004C3F5, "The revocation package is invalid")
+ DRM_E_HWID_ERROR = (0x8004C3F6, "Failed to get the hardware ID.")
+ DRM_E_VAR_NOT_INITIALIZED = (0x8004C3F7, "Variable was not initialized.")
+ DRM_E_DOMAIN_INVALID_GUID = (0x8004C500, "Not a correct GUID.")
+ DRM_E_DOMAIN_INVALID_CUSTOM_DATA_TYPE = (0x8004C501, "Not a valid custom data type.")
+ DRM_E_DOMAIN_STORE_ADD_DATA = (0x8004C502, "Failed to add data into the domain store.")
+ DRM_E_DOMAIN_STORE_GET_DATA = (0x8004C503, "Failed to retrieve data from the domain store.")
+ DRM_E_DOMAIN_STORE_DELETE_DATA = (0x8004C504, "Failed to delete data from the domain store.")
+ DRM_E_DOMAIN_STORE_OPEN_STORE = (0x8004C505, "Failed to open the domain store.")
+ DRM_E_DOMAIN_STORE_CLOSE_STORE = (0x8004C506, "Failed to close the domain store.")
+ DRM_E_DOMAIN_BIND_LICENSE = (0x8004C507, "Failed to bind to the domain license.")
+ DRM_E_DOMAIN_INVALID_CUSTOM_DATA = (0x8004C508, "Not a valid custom data.")
+ DRM_E_DOMAIN_NOT_FOUND = (0x8004C509, "No domain information is found.")
+ DRM_E_DOMAIN_INVALID_DOMKEYXMR_DATA = (0x8004C50A, "The domain join response contains invalid domain privkey XMR data.")
+ DRM_E_DOMAIN_STORE_INVALID_KEY_RECORD = (0x8004C50B, "Invalid format of domain private key record read from the domain store.")
+ DRM_E_DOMAIN_JOIN_TOO_MANY_KEYS = (0x8004C50C, "The server returned too many domain keys for the client to handle.")
+ DRM_E_DEVICE_DOMAIN_JOIN_REQUIRED = (0x8004C580, "This error code communicates to the application that the device is not a member of a domain. The app can uses this error code in turn to decide whether it needs to join the domain or not")
+ DRM_E_SERVER_INTERNAL_ERROR = (0x8004C600, "An internal server error occurred.")
+ DRM_E_SERVER_INVALID_MESSAGE = (0x8004C601, "The message sent to the server was invalid.")
+ DRM_E_SERVER_DEVICE_LIMIT_REACHED = (0x8004C602, "The device limit for the domain has been reached.")
+ DRM_E_SERVER_INDIV_REQUIRED = (0x8004C603, "Individualization of the client is required.")
+ DRM_E_SERVER_SERVICE_SPECIFIC = (0x8004C604, "An error specific to the service has occurred.")
+ DRM_E_SERVER_DOMAIN_REQUIRED = (0x8004C605, "A Domain certificate is required.")
+ DRM_E_SERVER_RENEW_DOMAIN = (0x8004C606, "The Domain certificate needs to be renewed.")
+ DRM_E_SERVER_UNKNOWN_METERINGID = (0x8004C607, "The metering identifier is unknown.")
+ DRM_E_SERVER_COMPUTER_LIMIT_REACHED = (0x8004C608, "The computer limit for the domain has been reached.")
+ DRM_E_SERVER_PROTOCOL_FALLBACK = (0x8004C609, "The client should fallback to the V2 license acquisition protocol.")
+ DRM_E_SERVER_NOT_A_MEMBER = (0x8004C60A, "The client was removed from the domain in an offline fashion and thus still has a domain cert, but not a valid domain membership.")
+ DRM_E_SERVER_PROTOCOL_VERSION_MISMATCH = (0x8004C60B, "The protocol version specified was not supported by the server.")
+ DRM_E_SERVER_UNKNOWN_ACCOUNTID = (0x8004C60C, "The account identifier is unknown.")
+ DRM_E_SERVER_PROTOCOL_REDIRECT = (0x8004C60D, "The protocol has a redirect.")
+ DRM_E_SERVER_UNKNOWN_TRANSACTIONID = (0x8004C610, "The transaction identifier is unknown.")
+ DRM_E_SERVER_INVALID_LICENSEID = (0x8004C611, "The license identifier is invalid.")
+ DRM_E_SERVER_MAXIMUM_LICENSEID_EXCEEDED = (0x8004C612, "The maximum number of license identifiers in the request was exceeded.")
+ DRM_E_LICACQ_TOO_MANY_LICENSES = (0x8004C700, "There are too many licenses in the license response.")
+ DRM_E_LICACQ_ACK_TRANSACTION_ID_TOO_BIG = (0x8004C701, "The Transaction ID specified by the server exceeds the allocated buffer.")
+ DRM_E_LICACQ_ACK_MESSAGE_NOT_CREATED = (0x8004C702, "The license acquisition acknowledgement message could not be created.")
+ DRM_E_INITIATORS_UNKNOWN_TYPE = (0x8004C780, "The initiator type is unknown.")
+ DRM_E_INITIATORS_INVALID_SERVICEID = (0x8004C781, "The service ID data is not valid.")
+ DRM_E_INITIATORS_INVALID_ACCOUNTID = (0x8004C782, "The account ID data is not valid.")
+ DRM_E_INITIATORS_INVALID_MID = (0x8004C783, "The account ID data is not valid.")
+ DRM_E_INITIATORS_MISSING_DC_URL = (0x8004C784, "Domain Controller URL is missing.")
+ DRM_E_INITIATORS_MISSING_CONTENT_HEADER = (0x8004C785, "Content header is missing.")
+ DRM_E_INITIATORS_MISSING_LAURL_IN_CONTENT_HEADER = (0x8004C786, "Missing license acquisition URL in content header.")
+ DRM_E_INITIATORS_MISSING_METERCERT_URL = (0x8004C787, "Meter certificate server URL is missing.")
+ DRM_E_BCERT_INVALID_SIGNATURE_TYPE = (0x8004C800, "An invalid signature type was encountered")
+ DRM_E_BCERT_CHAIN_TOO_DEEP = (0x8004C801, "There are, or there would be, too many certificates in the certificate chain")
+ DRM_E_BCERT_INVALID_CERT_TYPE = (0x8004C802, "An invalid certificate type was encountered")
+ DRM_E_BCERT_INVALID_FEATURE = (0x8004C803, "An invalid feature entry was encountered OR the porting kit was linked with mutually incompatible features or features incompatible with the certificate")
+ DRM_E_BCERT_INVALID_KEY_USAGE = (0x8004C804, "An invalid public key usage was encountered")
+ DRM_E_BCERT_INVALID_SECURITY_VERSION = (0x8004C805, "An invalid Indiv Box security version was encountered")
+ DRM_E_BCERT_INVALID_KEY_TYPE = (0x8004C806, "An invalid public key type was encountered")
+ DRM_E_BCERT_INVALID_KEY_LENGTH = (0x8004C807, "An invalid public key length was encountered")
+ DRM_E_BCERT_INVALID_MAX_LICENSE_SIZE = (0x8004C808, "An invalid maximum license size value was encountered")
+ DRM_E_BCERT_INVALID_MAX_HEADER_SIZE = (0x8004C809, "An invalid maximum license header size value was encountered")
+ DRM_E_BCERT_INVALID_MAX_LICENSE_CHAIN_DEPTH = (0x8004C80A, "An invalid maximum license chain depth was encountered")
+ DRM_E_BCERT_INVALID_SECURITY_LEVEL = (0x8004C80B, "An invalid security level was encountered")
+ DRM_E_BCERT_PRIVATE_KEY_NOT_SPECIFIED = (0x8004C80C, "A private key for signing the certificate was not provided to the builder")
+ DRM_E_BCERT_ISSUER_KEY_NOT_SPECIFIED = (0x8004C80D, "An issuer key was not provided to the builder")
+ DRM_E_BCERT_ACCOUNT_ID_NOT_SPECIFIED = (0x8004C80E, "An account ID was not provided to the builder")
+ DRM_E_BCERT_SERVICE_ID_NOT_SPECIFIED = (0x8004C80F, "A service provider ID was not provided to the builder")
+ DRM_E_BCERT_CLIENT_ID_NOT_SPECIFIED = (0x8004C810, "A client ID was not provided to the builder")
+ DRM_E_BCERT_DOMAIN_URL_NOT_SPECIFIED = (0x8004C811, "A domain URL was not provided to the builder")
+ DRM_E_BCERT_DOMAIN_URL_TOO_LONG = (0x8004C812, "The domain URL contains too many ASCII characters")
+ DRM_E_BCERT_HARDWARE_ID_NOT_SPECIFIED = (0x8004C813, "A hardware ID was not provided to the builder")
+ DRM_E_BCERT_HARDWARE_ID_TOO_LONG = (0x8004C814, "A hardware ID is longer than the maximum supported bytes")
+ DRM_E_BCERT_SERIAL_NUM_NOT_SPECIFIED = (0x8004C815, "A device serial number was not provided to the builder")
+ DRM_E_BCERT_CERT_ID_NOT_SPECIFIED = (0x8004C816, "A certificate ID was not provided to the builder")
+ DRM_E_BCERT_PUBLIC_KEY_NOT_SPECIFIED = (0x8004C817, "A public key for the certificate was not provided to the builder or not found by the parser")
+ DRM_E_BCERT_KEY_USAGES_NOT_SPECIFIED = (0x8004C818, "The public key usage information was not provided to the builder or not found by the parser")
+ DRM_E_BCERT_STRING_NOT_NULL_TERMINATED = (0x8004C819, "Data string is not null-teminated")
+ DRM_E_BCERT_OBJECTHEADER_LEN_TOO_BIG = (0x8004C81A, "Object length in object header is too big")
+ DRM_E_BCERT_INVALID_ISSUERKEY_LENGTH = (0x8004C81B, "IssuerKey Length value is invalid")
+ DRM_E_BCERT_BASICINFO_CERT_EXPIRED = (0x8004C81C, "Certificate is expired")
+ DRM_E_BCERT_UNEXPECTED_OBJECT_HEADER = (0x8004C81D, "Object header has unexpected values")
+ DRM_E_BCERT_ISSUERKEY_KEYINFO_MISMATCH = (0x8004C81E, "The cert's Issuer Key does not match key info in the next cert")
+ DRM_E_BCERT_INVALID_MAX_KEY_USAGES = (0x8004C81F, "Number of key usage entries is invalid")
+ DRM_E_BCERT_INVALID_MAX_FEATURES = (0x8004C820, "Number of features is invalid")
+ DRM_E_BCERT_INVALID_CHAIN_HEADER_TAG = (0x8004C821, "Cert chain header tag is invalid")
+ DRM_E_BCERT_INVALID_CHAIN_VERSION = (0x8004C822, "Cert chain version is invalid")
+ DRM_E_BCERT_INVALID_CHAIN_LENGTH = (0x8004C823, "Cert chain length value is invalid")
+ DRM_E_BCERT_INVALID_CERT_HEADER_TAG = (0x8004C824, "Cert header tag is invalid")
+ DRM_E_BCERT_INVALID_CERT_VERSION = (0x8004C825, "Cert version is invalid")
+ DRM_E_BCERT_INVALID_CERT_LENGTH = (0x8004C826, "Cert length value is invalid")
+ DRM_E_BCERT_INVALID_SIGNEDCERT_LENGTH = (0x8004C827, "Length of signed portion of certificate is invalid")
+ DRM_E_BCERT_INVALID_PLATFORM_IDENTIFIER = (0x8004C828, "An invalid Platform Identifier was specified")
+ DRM_E_BCERT_INVALID_NUMBER_EXTDATARECORDS = (0x8004C829, "An invalid number of extended data records")
+ DRM_E_BCERT_INVALID_EXTDATARECORD = (0x8004C82A, "An invalid extended data record")
+ DRM_E_BCERT_EXTDATA_LENGTH_MUST_PRESENT = (0x8004C82B, "Extended data record length must be present.")
+ DRM_E_BCERT_EXTDATA_PRIVKEY_MUST_PRESENT = (0x8004C82C, "Extended data record length must be present.")
+ DRM_E_BCERT_INVALID_EXTDATA_LENGTH = (0x8004C82D, "Calculated and written extended data object lengths do not match.")
+ DRM_E_BCERT_EXTDATA_IS_NOT_PROVIDED = (0x8004C82E, "Extended data is not provided, the cert builder cannot write it.")
+ DRM_E_BCERT_HWIDINFO_IS_MISSING = (0x8004C82F, "The PC certificate is correct but is not ready to use because has no HWID information")
+ DRM_E_BCERT_INVALID_EXTDATA_SIGNED_LENGTH = (0x8004C830, "Length of signed portion of extended data info is invalid")
+ DRM_E_BCERT_INVALID_EXTDATA_RECORD_TYPE = (0x8004C831, "Extended data record type is invalid")
+ DRM_E_BCERT_EXTDATAFLAG_CERT_TYPE_MISMATCH = (0x8004C832, "Certificate of this type cannot have extended data flag set")
+ DRM_E_BCERT_METERING_ID_NOT_SPECIFIED = (0x8004C833, "An metering ID was not provided to the builder")
+ DRM_E_BCERT_METERING_URL_NOT_SPECIFIED = (0x8004C834, "A metering URL was not provided to the builder")
+ DRM_E_BCERT_METERING_URL_TOO_LONG = (0x8004C835, "The metering URL contains too many ASCII characters")
+ DRM_E_BCERT_VERIFICATION_ERRORS = (0x8004C836, "Verification errors are found while parsing cert chain")
+ DRM_E_BCERT_REQUIRED_KEYUSAGE_MISSING = (0x8004C837, "Required key usage is missing")
+ DRM_E_BCERT_NO_PUBKEY_WITH_REQUESTED_KEYUSAGE = (0x8004C838, "The certificate does not contain a public key with the requested key usage")
+ DRM_E_BCERT_MANUFACTURER_STRING_TOO_LONG = (0x8004C839, "The manufacturer string is too long")
+ DRM_E_BCERT_TOO_MANY_PUBLIC_KEYS = (0x8004C83A, "There are too many public keys in the certificate")
+ DRM_E_BCERT_OBJECTHEADER_LEN_TOO_SMALL = (0x8004C83B, "Object length in object header is too small")
+ DRM_E_BCERT_INVALID_WARNING_DAYS = (0x8004C83C, "An invalid server certificate expiration warning days. Warning days must be greater than zero.")
+ DRM_E_BCERT_INVALID_DIGEST = (0x8004C83D, "The certificate digest is invalid.")
+ DRM_E_BCERT_MANUFACTURING_INFO_REQUIRED = (0x8004C83E, "This certificate type requires Manufacturer Name, Model Name, and Model Number to be set.")
+ DRM_E_XMLSIG_ECDSA_VERIFY_FAILURE = (0x8004C900, "Error in ECDSA signature verification.")
+ DRM_E_XMLSIG_SHA_VERIFY_FAILURE = (0x8004C901, "Error in SHA verification.")
+ DRM_E_XMLSIG_FORMAT = (0x8004C902, "The format of XML signature or encryption segment is incorrect.")
+ DRM_E_XMLSIG_PUBLIC_KEY_ID = (0x8004C903, "Invalud pre-shared public key ID.")
+ DRM_E_XMLSIG_INVALID_KEY_FORMAT = (0x8004C904, "Invalid type of public/private key format.")
+ DRM_E_XMLSIG_SHA_HASH_SIZE = (0x8004C905, "Size of hash is unexpected.")
+ DRM_E_XMLSIG_ECDSA_SIGNATURE_SIZE = (0x8004C906, "Size of ECDSA signature is unexpected.")
+ DRM_E_UTF_UNEXPECTED_END = (0x8004CA00, "Unexpected end of data in the middle of multibyte character.")
+ DRM_E_UTF_INVALID_CODE = (0x8004CA01, "UTF character maps into a code with invalid value.")
+ DRM_E_SOAPXML_INVALID_STATUS_CODE = (0x8004CB00, "Status code contained in the server error response is invalid.")
+ DRM_E_SOAPXML_XML_FORMAT = (0x8004CB01, "Cannot parse out expected XML node.")
+ DRM_E_SOAPXML_WRONG_MESSAGE_TYPE = (0x8004CB02, "The message type associated with the soap message is wrong.")
+ DRM_E_SOAPXML_SIGNATURE_MISSING = (0x8004CB03, "The message did not have a signature and needed one")
+ DRM_E_SOAPXML_PROTOCOL_NOT_SUPPORTED = (0x8004CB04, "The requested protocol is not supported by the DRM SOAP parser.")
+ DRM_E_SOAPXML_DATA_NOT_FOUND = (0x8004CB05, "The requested data is not found in the response.")
+ DRM_E_CRYPTO_PUBLIC_KEY_NOT_MATCH = (0x8004CC00, "The public key associated with an encrypted domain private from the server does not match any public key on the device.")
+ DRM_E_UNABLE_TO_RESOLVE_LOCATION_TREE = (0x8004CC01, "Unable to derive the key. May be due to blackout or no rights to the service, etc.")
+ DRM_E_SECURE_TRACE_BAD_GLOBAL_DATA_POINTER = (0x8004CD00, "The secure trace global data pointer is NULL")
+ DRM_E_SECURE_TRACE_INVALID_GLOBAL_DATA = (0x8004CD01, "The secure trace global data structure is invalid")
+ DRM_E_SECURE_TRACE_FORMATTING_ERROR = (0x8004CD02, "An error occured in formatting the trace message")
+ DRM_E_SECURE_TRACE_BAD_SCHEME_DATA_POINTER = (0x8004CD03, "A secure trace scheme data pointer is NULL")
+ DRM_E_SECURE_TRACE_BAD_PER_THREAD_AES_DATA_POINTER = (0x8004CD04, "The secure trace per thread AES data pointer is NULL")
+ DRM_E_SECURE_TRACE_BAD_PER_THREAD_AES_BUFFER_POINTER = (0x8004CD05, "A secure trace per thread AES buffer pointer is NULL")
+ DRM_E_SECURE_TRACE_AES_INSUFFICIENT_BUFFER = (0x8004CD06, "There is no space left in the secure trace AES buffer")
+ DRM_E_SECURE_TRACE_VERSION_MISMATCH = (0x8004CD07, "All drm dlls do not agree on the same secure trace version")
+ DRM_E_SECURE_TRACE_UNEXPECTED_ERROR = (0x8004CD08, "An expected error was encountered in secure tracing system")
+ DRM_E_TEE_INVALID_KEY_DATA = (0x8004CD10, "The key data given to the TEE was invalid.")
+ DRM_E_TEE_PROVISIONING_REQUIRED = (0x8004CD11, "Provisioning is required.")
+ DRM_E_TEE_INVALID_HWDRM_STATE = (0x8004CD12, "The HWDRM state is invalid, e.g. the TEE context is invalid. Reinitialization is required.")
+ DRM_E_TEE_PROVISIONING_REQUEST_EXPIRED = (0x8004CD13, "Provisioning request expired.")
+ DRM_E_TEE_CLOCK_NOT_SET = (0x8004CD14, "The TEE secure clock needs to be reset.")
+ DRM_E_TEE_BLOB_ACCESS_DENIED = (0x8004CD15, "The blob data is protected and cannot be transfered outside of the TEE.")
+ DRM_E_TEE_PROVISIONING_BAD_NONCE = (0x8004CD16, "Malformed nonce")
+ DRM_E_TEE_PROVISIONING_NONCE_MISMATCH = (0x8004CD17, "Nonce mismatch. Possibly another request has happened in parallel.")
+ DRM_E_TEE_ROOT_KEY_CHANGED = (0x8004CD18, "The root-most TEE key has changed without maintaining key history. All TEE-bound data is now invalid.")
+ DRM_E_TEE_PROVISIONING_INVALID_RESPONSE = (0x8004CD19, "Invalid provisioning response.")
+ DRM_E_TEE_PROXY_INVALID_SERIALIZATION_MESSAGE = (0x8004CD1A, "Invalid TEE proxy serialization message.")
+ DRM_E_TEE_PROXY_INVALID_SERIALIZATION_TYPE = (0x8004CD1B, "Invalid TEE proxy serialization type.")
+ DRM_E_TEE_LAYER_UNINITIALIZED = (0x8004CD1C, "TEE Layer is not initialized.")
+ DRM_E_TEE_INVALID_HEADER_FOOTER_SIZE = (0x8004CD1D, "The OEM defined TEE message header/footer size was not a multiple of 8 bytes.")
+ DRM_E_TEE_MESSAGE_TOO_LARGE = (0x8004CD1E, "TEE method invocation message is too large.")
+ DRM_E_TEE_CLOCK_DRIFTED = (0x8004CD1F, "TEE clock drift detected.")
+ DRM_E_TEE_PROXY_INVALID_BUFFER_ALIGNMENT = (0x8004CD20, "The TEE serialization buffer is incorrectly aligned. It requires 8-byte alignment.")
+ DRM_E_TEE_PROXY_INVALID_ALIGNMENT = (0x8004CD21, "The TEE serialization buffer has parameters that are not properly aligned.")
+ DRM_E_TEE_OUTPUT_PROTECTION_REQUIREMENTS_NOT_MET = (0x8004CD22, "The TEE has detected that certain output requirements are not being satisfied. Most commonly HDCP is required but not enabled on all available outputs.")
+ DRM_E_ND_MUST_REVALIDATE = (0x8004CE00, "The client must be revalidated before executing the intended operation.")
+ DRM_E_ND_INVALID_MESSAGE = (0x8004CE01, "A received message is garbled.")
+ DRM_E_ND_INVALID_MESSAGE_TYPE = (0x8004CE02, "A received message contains an invalid message type.")
+ DRM_E_ND_INVALID_MESSAGE_VERSION = (0x8004CE03, "A received message contains an invalid message version.")
+ DRM_E_ND_INVALID_SESSION = (0x8004CE04, "The requested session is invalid.")
+ DRM_E_ND_MEDIA_SESSION_LIMIT_REACHED = (0x8004CE05, "A new session cannot be opened because the maximum number of sessions has already been opened.")
+ DRM_E_ND_UNABLE_TO_VERIFY_PROXIMITY = (0x8004CE06, "The proximity detection procedure could not confirm that the receiver is near the transmitter in the network.")
+ DRM_E_ND_INVALID_PROXIMITY_RESPONSE = (0x8004CE07, "The response to the proximity detection challenge is invalid.")
+ DRM_E_ND_DEVICE_LIMIT_REACHED = (0x8004CE08, "The maximum number of devices in use has been reached. Unable to open additional devices.")
+ DRM_E_ND_BAD_REQUEST = (0x8004CE09, "The message format is invalid.")
+ DRM_E_ND_FAILED_SEEK = (0x8004CE0A, "It is not possible to seek to the specified mark-in point.")
+ DRM_E_ND_INVALID_CONTEXT = (0x8004CE0B, "Manager context or at least one of it's children is missing (or corrupt).")
+ DRM_E_ASF_BAD_ASF_HEADER = (0x8004CF00, "The ASF file has a bad ASF header.")
+ DRM_E_ASF_BAD_PACKET_HEADER = (0x8004CF01, "The ASF file has a bad packet header.")
+ DRM_E_ASF_BAD_PAYLOAD_HEADER = (0x8004CF02, "The ASF file has a bad payload header.")
+ DRM_E_ASF_BAD_DATA_HEADER = (0x8004CF03, "The ASF file has a bad data header.")
+ DRM_E_ASF_INVALID_OPERATION = (0x8004CF04, "The intended operation is invalid given the current processing state of the ASF file.")
+ DRM_E_ASF_AES_PAYLOAD_FOUND = (0x8004CF05, "ND payload extension system found; the file may be encrypted with AES already.")
+ DRM_E_ASF_EXTENDED_STREAM_PROPERTIES_OBJ_NOT_FOUND = (0x8004CF06, "Extended stream properties object is not found; the file may be in non-supported outdated format.")
+ DRM_E_ASF_INVALID_DATA = (0x8004CF20, "The packet is overstuffed with data.")
+ DRM_E_ASF_TOO_MANY_PAYLOADS = (0x8004CF21, "The number of payloads in the packet is greater than the maximum allowed.")
+ DRM_E_ASF_BANDWIDTH_OVERRUN = (0x8004CF22, "An object is overflowing the leaky bucket.")
+ DRM_E_ASF_INVALID_STREAM_NUMBER = (0x8004CF23, "The stream number is invalid; it is either zero, greater than the maximum value allowed, or has no associated data.")
+ DRM_E_ASF_LATE_SAMPLE = (0x8004CF24, "A sample was encountered with a presentation time outside of the mux's send window.")
+ DRM_E_ASF_NOT_ACCEPTING = (0x8004CF25, "The sample does not fit in the remaining payload space.")
+ DRM_E_ASF_UNEXPECTED = (0x8004CF26, "An unexpected error occurred.")
+ DRM_E_NONCE_STORE_TOKEN_NOT_FOUND = (0x8004D000, "The matching nonce store token is not found.")
+ DRM_E_NONCE_STORE_OPEN_STORE = (0x8004D001, "Fail to open nonce store.")
+ DRM_E_NONCE_STORE_CLOSE_STORE = (0x8004D002, "Fail to close nonce store.")
+ DRM_E_NONCE_STORE_ADD_LICENSE = (0x8004D003, "There is already a license associated with the nonce store token.")
+ DRM_E_LICGEN_POLICY_NOT_SUPPORTED = (0x8004D100, "The license generation policy combination is not supported.")
+ DRM_E_POLICYSTATE_NOT_FOUND = (0x8004D200, "The policy state is not found in the secure store.")
+ DRM_E_POLICYSTATE_CORRUPTED = (0x8004D201, "The policy state is not stored as a valid internal format in the secure store.")
+ DRM_E_MOVE_DENIED = (0x8004D300, "The requested move operation was denied by the service.")
+ DRM_E_INVALID_MOVE_RESPONSE = (0x8004D301, "The move response was incorrectly formed.")
+ DRM_E_MOVE_NONCE_MISMATCH = (0x8004D302, "The nonce in the repsonse did not match the expected value.")
+ DRM_E_MOVE_TXID_MISMATCH = (0x8004D303, "The transaction id in the repsonse did not match the expected value.")
+ DRM_E_MOVE_STORE_OPEN_STORE = (0x8004D304, "Failed to open the move store.")
+ DRM_E_MOVE_STORE_CLOSE_STORE = (0x8004D305, "Failed to close the move store.")
+ DRM_E_MOVE_STORE_ADD_DATA = (0x8004D306, "Failed to add data into the move store.")
+ DRM_E_MOVE_STORE_GET_DATA = (0x8004D307, "Failed to retrieve data from the move store.")
+ DRM_E_MOVE_FORMAT_INVALID = (0x8004D308, "The format of a move page or index is invalid.")
+ DRM_E_MOVE_SIGNATURE_INVALID = (0x8004D309, "The signature of a move index is invalid.")
+ DRM_E_COPY_DENIED = (0x8004D30A, "The requested copy operation was denied by the service.")
+ DRM_E_XB_OBJECT_NOTFOUND = (0x8004D400, "The extensible binary object was not found.")
+ DRM_E_XB_INVALID_OBJECT = (0x8004D401, "The extensible binary object format was invalid.")
+ DRM_E_XB_OBJECT_ALREADY_EXISTS = (0x8004D402, "A single instance extensible binary object was encountered more than once.")
+ DRM_E_XB_REQUIRED_OBJECT_MISSING = (0x8004D403, "A required extensible binary object was not found during building.")
+ DRM_E_XB_UNKNOWN_ELEMENT_TYPE = (0x8004D404, "An extensible binary object description contained an element of an unknown type.")
+ DRM_E_XB_INVALID_VERSION = (0x8004D405, "The serialized object version could not be found in the extensible binary object description.")
+ DRM_E_XB_MAX_UNKNOWN_CONTAINER_DEPTH = (0x8004D406, "The maximum unknown container depth was reached.")
+ DRM_E_XB_INVALID_ALIGNMENT = (0x8004D407, "The serialized message buffer is not properly aligned according to the XBinary format description.")
+ DRM_E_XB_OBJECT_OUT_OF_RANGE = (0x8004D408, "An extensible binary object size or count is out of the range specified by the attributes 'MinSize' and 'MaxSize'.")
+ DRM_E_KEYFILE_INVALID_PLATFORM = (0x8004D500, "The keyfile does not support the current platform.")
+ DRM_E_KEYFILE_TOO_LARGE = (0x8004D501, "The keyfile is larger than the maximum supported size.")
+ DRM_E_KEYFILE_PRIVATE_KEY_NOT_FOUND = (0x8004D502, "The private key requested was not found in the keyfile.")
+ DRM_E_KEYFILE_CERTIFICATE_CHAIN_NOT_FOUND = (0x8004D503, "The certificate chain requested was not found in the keyfile.")
+ DRM_E_KEYFILE_KEY_NOT_FOUND = (0x8004D504, "The AES Key ID was not found in the keyfile.")
+ DRM_E_KEYFILE_UNKNOWN_DECRYPTION_METHOD = (0x8004D505, "Unknown keyfile decryption method.")
+ DRM_E_KEYFILE_INVALID_SIGNATURE = (0x8004D506, "The keyfile signature was not valid.")
+ DRM_E_KEYFILE_INTERNAL_DECRYPTION_BUFFER_TOO_SMALL = (0x8004D507, "The internal decryption buffer is too small to hold the encrypted key from the keyfile.")
+ DRM_E_KEYFILE_PLATFORMID_MISMATCH = (0x8004D508, "Platform ID in the certificate does not match expected value.")
+ DRM_E_KEYFILE_CERTIFICATE_ISSUER_KEY_MISMATCH = (0x8004D509, "Issuer key of the device certificate does not match public key of the model certificate.")
+ DRM_E_KEYFILE_ROBUSTNESSVERSION_MISMATCH = (0x8004D50A, "Robustness version in the certificate does not match expected value.")
+ DRM_E_KEYFILE_FILE_NOT_CLOSED = (0x8004D50B, "The KeyFile Close function was not called before trying to unintialize the KeyFile context.")
+ DRM_E_KEYFILE_NOT_INITED = (0x8004D50C, "The KeyFile Context was not initialized before trying to use it.")
+ DRM_E_KEYFILE_FORMAT_INVALID = (0x8004D50D, "The format of the KeyFile was invalid.")
+ DRM_E_KEYFILE_UPDATE_NOT_ALLOWED = (0x8004D50E, "The keyfile of the device is read only, and updates are not permitted.")
+ DRM_E_EMPTY_LA_URL = (0x8004D50F, "")
+ DRM_E_PRND_MESSAGE_VERSION_INVALID = (0x8004D700, "The PRND message version is not supported.")
+ DRM_E_PRND_MESSAGE_WRONG_TYPE = (0x8004D701, "This method does not processs this PRND message type.")
+ DRM_E_PRND_MESSAGE_INVALID = (0x8004D702, "The PRND message does not conform to the PRND spec and is therefore invalid.")
+ DRM_E_PRND_SESSION_ID_INVALID = (0x8004D703, "The Transmitter is unable to process a renewal Registration Request Message using a different session. Use the session matching the previous session ID.")
+ DRM_E_PRND_PROXIMITY_DETECTION_REQUEST_CHANNEL_TYPE_UNSUPPORTED = (0x8004D704, "The PRND Registration Request Message indicated that it only supports Proximity Detection Channel Types that the Transmitter does not support.")
+ DRM_E_PRND_PROXIMITY_DETECTION_RESPONSE_INVALID = (0x8004D705, "The PRND Proximity Detection Response Message was successfully parsed but the nonce is invalid.")
+ DRM_E_PRND_PROXIMITY_DETECTION_RESPONSE_TIMEOUT = (0x8004D706, "The PRND Proximity Detection Response Message was successfully processed but did not arrive in time to verify that the Receiver is Near the Transmitter.")
+ DRM_E_PRND_LICENSE_REQUEST_CID_CALLBACK_REQUIRED = (0x8004D707, "The PRND License Request Message used Content Identifier Type Custom. A Content Identifier Callback to convert the value to a KID is required.")
+ DRM_E_PRND_LICENSE_RESPONSE_CLMID_INVALID = (0x8004D708, "The PRND License Response Message had an invalid Current License Message ID.")
+ DRM_E_PRND_CERTIFICATE_NOT_RECEIVER = (0x8004D709, "The PRND Registration Request Message did not include a PlayReady certificate that supports the RECEIVER feature.")
+ DRM_E_PRND_CANNOT_RENEW_USING_NEW_SESSION = (0x8004D70A, "The Receiver is unable to generate a renewal Registration Request Message using a new session. Use the existing session.")
+ DRM_E_PRND_INVALID_CUSTOM_DATA_TYPE = (0x8004D70B, "The Custom Data type is invalid. The first four bytes of Custom Data Type ID cannot be 0x4d534654 ( MSFT in ascii ).")
+ DRM_E_PRND_CLOCK_OUT_OF_SYNC = (0x8004D70C, "The clock on the Receiver is not synchronized with the clock on the Transmitter. Synchronize the clocks.")
+ DRM_E_PRND_CANNOT_REBIND_PRND_RECEIVED_LICENSE = (0x8004D70D, "The license cannot be rebound to the PRND Receiver because it was itself received from a PRND Transmitter.")
+ DRM_E_PRND_CANNOT_REGISTER_USING_EXISTING_SESSION = (0x8004D70E, "The Receiver is unable to generate a non-renewal Registration Request Message using an existing session. End the existing session first or use a new session.")
+ DRM_E_PRND_BUSY_PERFORMING_RENEWAL = (0x8004D70F, "The Receiver is currently unable to process a message of this type because it is in the middle of renewing the session.")
+ DRM_E_PRND_LICENSE_REQUEST_INVALID_ACTION = (0x8004D710, "Play with no qualifier during license request is all that's supported in v1 of the PRND protocol.")
+ DRM_E_PRND_TRANSMITTER_UNAUTHORIZED = (0x8004D711, "The Transmitter attempted to authorize with the Receiver but was unsuccessful.")
+ DRM_E_PRND_TX_SESSION_EXPIRED = (0x8004D712, "The Transmitter session is expired.")
+ DRM_E_PRND_INCOMPLETE_PROXIMITY_DETECTION = (0x8004D713, "The proximity detection hasn't completed successfully.")
+ DRM_E_PRND_INVALID_CERT_DIGEST = (0x8004D714, "The given certificate digest doesn't match the one stored in the MTKB")
+ DRM_E_OEMHAL_NOT_INITIALIZED = (0x8004D780, "The OEM HAL is not initialized.")
+ DRM_E_OEMHAL_OUT_OF_KEY_REGISTERS = (0x8004D781, "There are no more key registers available in the OEM HAL implementation.")
+ DRM_E_OEMHAL_KEYS_IN_USE = (0x8004D782, "The OEM HAL is being shutdown whilst keys are still allocated.")
+ DRM_E_OEMHAL_NO_KEY = (0x8004D783, "The requested preloaded key is not available or the key handle is otherwise invalid.")
+ DRM_E_OEMHAL_UNSUPPORTED_KEY_TYPE = (0x8004D784, "The specified key type cannot be used for the operation requested.")
+ DRM_E_OEMHAL_UNSUPPORTED_KEY_WRAPPING_FORMAT = (0x8004D785, "The specified wrapping key type cannot be used to unwrap the specified key.")
+ DRM_E_OEMHAL_UNSUPPORTED_KEY_LENGTH = (0x8004D786, "The key buffer provided is of the wrong length for the specified key/wrapping key combination.")
+ DRM_E_OEMHAL_UNSUPPORTED_HASH_TYPE = (0x8004D787, "The specified hash type is not supported.")
+ DRM_E_OEMHAL_UNSUPPORTED_SIGNATURE_SCHEME = (0x8004D788, "The specified signature scheme is not supported.")
+ DRM_E_OEMHAL_BUFFER_TOO_LARGE = (0x8004D789, "The output buffer is larger than the input buffer and must be the same size.")
+ DRM_E_OEMHAL_SAMPLE_ENCRYPTION_MODE_NOT_PERMITTED = (0x8004D78A, "The sample encryption mode is not permitted for this combination of encrypt parameters.")
+ DRM_E_M2TS_PAT_PID_IS_NOT_ZERO = (0x8004D800, "PID 0 is reserved for PAT and cannot be used for other type of packet.")
+ DRM_E_M2TS_PTS_NOT_EXIST = (0x8004D801, "The audio/video PES doesn' have the PTS data.")
+ DRM_E_M2TS_PES_PACKET_LENGTH_NOT_SPECIFIED = (0x8004D802, "The audio PES' packet length is 0 which is not allowed.")
+ DRM_E_M2TS_OUTPUT_BUFFER_FULL = (0x8004D803, "The output buffer for receiving the encrypted packets is full.")
+ DRM_E_M2TS_CONTEXT_NOT_INITIALIZED = (0x8004D804, "The encryptor context hasn't been initialized yet.")
+ DRM_E_M2TS_NEED_KEY_DATA = (0x8004D805, "The key data for encrypting the sample is either hasn't been set or the encryptor needs next key due to key rotation.")
+ DRM_E_M2TS_DDPLUS_FORMAT_INVALID = (0x8004D806, "Not supported DDPlus format.")
+ DRM_E_M2TS_NOT_UNIT_START_PACKET = (0x8004D807, "The encryptor expects a unit start packet. The unit start packet should appear before the rest of the packets in the unit.")
+ DRM_E_M2TS_TOO_MANY_SUBSAMPLES = (0x8004D808, "Too many subsamples over the limit that the ECM allows.")
+ DRM_E_M2TS_TABLE_ID_INVALID = (0x8004D809, "The PAT or PMT packet contains an invalid table ID.")
+ DRM_E_M2TS_PACKET_SYNC_BYTE_INVALID = (0x8004D80A, "The TS packet doesn't start with the 0x47 (sync byte).")
+ DRM_E_M2TS_ADAPTATION_LENGTH_INVALID = (0x8004D80B, "The adaptation field length is invalid.")
+ DRM_E_M2TS_PAT_HEADER_INVALID = (0x8004D80C, "There is an error in PAT header, unable to parse it.")
+ DRM_E_M2TS_PMT_HEADER_INVALID = (0x8004D80D, "There is an error in PMT header, unable to parse it.")
+ DRM_E_M2TS_PES_START_CODE_NOT_FOUND = (0x8004D80E, "Cannot find the PES start code (0x000001).")
+ DRM_E_M2TS_STREAM_OR_PACKET_TYPE_CHANGED = (0x8004D80F, "The stream type or packet type of an existing PID has changed")
+ DRM_E_M2TS_INTERNAL_ERROR = (0x8004D810, "An internal error occurred during encryptrion.")
+ DRM_E_M2TS_ADTS_FORMAT_INVALID = (0x8004D811, "Not supported ADTS format.")
+ DRM_E_M2TS_MPEGA_FORMAT_INVALID = (0x8004D812, "Not supported MPEGA format.")
+ DRM_E_M2TS_CA_DESCRIPTOR_LENGTH_INVALID = (0x8004D813, "The CA_descruptor length is greater than ES_info length.")
+ DRM_E_M2TS_CRC_FIELD_INVALID = (0x8004D814, "The CRC field in the PAT or PMT packet is invalid.")
+ DRM_E_M2TS_INCOMPLETE_SECTION_HEADER = (0x8004D815, "The section header of a PES is not completed while the next PES packet has started already")
+ DRM_E_M2TS_INVALID_UNALIGNED_DATA = (0x8004D816, "Not allowed to have the overflow of the unaligned payload to accross more than one PES")
+ DRM_E_M2TS_GET_ENCRYPTED_DATA_FIRST = (0x8004D817, "Do not pass additional data for encryption when the last encryption result is DRM_S_MORE_DATA")
+ DRM_E_M2TS_CANNOT_CHANGE_PARAMETER = (0x8004D818, "Not allowed to change the encryption parameter once the encryption started, i.e. after Drm_M2ts_Encrypt is called")
+ DRM_E_M2TS_UNKNOWN_PACKET = (0x8004D819, "This packet appears before the first PAT and/or PMT and will be dropped.")
+ DRM_E_M2TS_DROP_PACKET = (0x8004D820, "This packet should be dropped because at least one field in the packet contains an invalid data.")
+ DRM_E_M2TS_DROP_PES = (0x8004D821, "This PES packet should be dropped because the PES packet is not valid.")
+ DRM_E_M2TS_INCOMPLETE_PES = (0x8004D822, "This PES packet has one or more missing packets.")
+ DRM_E_M2TS_WAITED_TOO_LONG = (0x8004D823, "This packet is dropped because its unit is not completed after a long period of time.")
+ DRM_E_M2TS_SECTION_LENGTH_INVALID = (0x8004D824, "The section length inside the PAT or PMT is less than the minimun PAT or PMT section.")
+ DRM_E_M2TS_PROGRAM_INFO_LENGTH_INVALID = (0x8004D825, "The sum of the program info length and the other fields in the PMT section don't match with the section length.")
+ DRM_E_M2TS_PES_HEADER_INVALID = (0x8004D826, "Failed to parse the PES header, the PES maybe too short.")
+ DRM_E_M2TS_ECM_PAYLOAD_OVER_LIMIT = (0x8004D827, "The size of the ECM payload exceeds the limit of 64k bytes")
+ DRM_E_M2TS_SET_CA_PID_FAILED = (0x8004D828, "Unable to assign a PID for CA_PID, all PIDs in the range of 0x0010 to 0x1FFE are used.")
+ DRM_E_LICGEN_CANNOT_PERSIST_LICENSE = (0x8004D901, "A non-persistent license cannot be stored in the license store.")
+ DRM_E_LICGEN_PERSISTENT_REMOTE_LICENSE = (0x8004D902, "A remote bound license should be non-persistent.")
+ DRM_E_LICGEN_EXPIRE_AFTER_FIRST_PLAY_REMOTE_LICENSE = (0x8004D903, "A remote bound license should not have expire after first play property.")
+ DRM_E_LICGEN_ROOT_LICENSE_CANNOT_ENCRYPT = (0x8004D904, "A root license should not be used to encrypt content.")
+ DRM_E_LICGEN_EMBED_LOCAL_LICENSE = (0x8004D905, "A local bound license cannot be embedded.")
+ DRM_E_LICGEN_LOCAL_LICENSE_WITH_REMOTE_CERTIFICATE = (0x8004D906, "A local bound license cannot be bound to a remote certificate.")
+ DRM_E_LICGEN_PLAY_ENABLER_REMOTE_LICENSE = (0x8004D907, "A remote bound license cannot have play enablers other than Passing to Unknown Output or Passing Constrained Resolution to Unknown Output.")
+ DRM_E_LICGEN_DUPLICATE_PLAY_ENABLER = (0x8004D908, "A license descriptor contains a duplicate play enabler.")
+ DRM_E_LICGEN_CHILD_SECURITY_LEVEL_TOO_LOW = (0x8004D909, "The security level of the chained license is too low.")
+ DRM_E_H264_PARSING_FAILED = (0x8004DA00, "The H264 was unable to be parsed.")
+ DRM_E_H264_SPS_PROFILE = (0x8004DA01, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_IDC = (0x8004DA02, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_SPSID = (0x8004DA03, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_FRAMENUM = (0x8004DA04, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_POCTYPE = (0x8004DA05, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_POCLSB = (0x8004DA06, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_POCCYCLE = (0x8004DA07, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_NUMREFFRAMES = (0x8004DA08, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_CHROMATOP = (0x8004DA09, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_CHROMABOTTOM = (0x8004DA0A, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_NALHRD = (0x8004DA0B, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_VLDHRD = (0x8004DA0C, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_VUIBPPD = (0x8004DA0D, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_VUIBPMD = (0x8004DA0E, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_VUIMMLH = (0x8004DA0F, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_VUIMMLV = (0x8004DA10, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_VUINRF = (0x8004DA11, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_VUIMDFB = (0x8004DA12, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_WIDTH_HEIGHT = (0x8004DA13, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_AREA = (0x8004DA14, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_MINHEIGHT2 = (0x8004DA15, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_MINHEIGHT3 = (0x8004DA16, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_CROPWIDTH = (0x8004DA17, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_CROPHEIGHT = (0x8004DA18, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_MORE_RBSP = (0x8004DA19, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_CHROMA_IDC = (0x8004DA1A, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_BITDEPTHLUMA = (0x8004DA1B, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_BITDEPTHCHROMA = (0x8004DA1C, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_DELTASCALE1 = (0x8004DA1D, "SPS-specific H264 parsing error")
+ DRM_E_H264_SPS_DELTASCALE2 = (0x8004DA1E, "SPS-specific H264 parsing error")
+ DRM_E_H264_BITSTREAM_TOOMANY = (0x8004DA30, "Bitstream-specific H264 parsing error")
+ DRM_E_H264_BITSTREAM_TOOSHORT1 = (0x8004DA31, "Bitstream-specific H264 parsing error")
+ DRM_E_H264_BITSTREAM_TOOSHORT2 = (0x8004DA32, "Bitstream-specific H264 parsing error")
+ DRM_E_H264_BITSTREAM_TOOSHORT3 = (0x8004DA33, "Bitstream-specific H264 parsing error")
+ DRM_E_H264_BITSTREAM_TOOSHORT4 = (0x8004DA34, "Bitstream-specific H264 parsing error")
+ DRM_E_H264_BITSTREAM_TOOSHORT5 = (0x8004DA35, "Bitstream-specific H264 parsing error")
+ DRM_E_H264_BITSTREAM_EXGOLOBMTOOLONG1 = (0x8004DA36, "Bitstream-specific H264 parsing error")
+ DRM_E_H264_BITSTREAM_EXGOLOBMTOOLONG2 = (0x8004DA37, "Bitstream-specific H264 parsing error")
+ DRM_E_H264_NALU_NO_START_CODE = (0x8004DA40, "Nalu-specific H264 parsing error")
+ DRM_E_H264_NALU_ALL_ZERO = (0x8004DA41, "Nalu-specific H264 parsing error")
+ DRM_E_H264_NALU_EMULATION = (0x8004DA42, "Nalu-specific H264 parsing error")
+ DRM_E_H264_PPS_PPSID = (0x8004DA50, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_SPSID = (0x8004DA51, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_SPS_NOT_FOUND = (0x8004DA52, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_NUM_SLICE_GROUPS = (0x8004DA53, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_SLICE_GROUP_MAX = (0x8004DA54, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_RUN_LENGTH = (0x8004DA55, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_TOP_LEFT = (0x8004DA56, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_SLICE_GROUP_RATE = (0x8004DA57, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_SLICE_GROUP_MAP = (0x8004DA58, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_SLICE_GROUP_ID = (0x8004DA59, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_REF_IDX_L0 = (0x8004DA5A, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_REF_IDX_L1 = (0x8004DA5B, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_WEIGHTED_BIPRED = (0x8004DA5C, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_PIC_INIT_QP = (0x8004DA5D, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_PIC_INIT_QS = (0x8004DA5E, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_PIC_CHROMA_QP = (0x8004DA5F, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_REDUN_PIC_COUNT = (0x8004DA61, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_DELTA_SCALE1 = (0x8004DA62, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_DELTA_SCALE2 = (0x8004DA63, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_SECOND_CHROMA_QP = (0x8004DA64, "PPS-specific H264 parsing error")
+ DRM_E_H264_PPS_MORE_RBSP = (0x8004DA65, "PPS-specific H264 parsing error")
+ DRM_E_H264_SH_SLICE_TYPE = (0x8004DA70, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_SLICE_TYPE_UNSUPPORTED = (0x8004DA71, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_PPSID = (0x8004DA72, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_PPS_NOT_FOUND = (0x8004DA73, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_SPS_NOT_FOUND = (0x8004DA74, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_SLICE_TYPE_PROFILE = (0x8004DA75, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_IDR_FRAME_NUM = (0x8004DA76, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_FIRST_MB_IN_SLICE = (0x8004DA77, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_IDR_PIC_ID = (0x8004DA78, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_REDUN_PIC_COUNT = (0x8004DA79, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_NUM_REF_IDX_LX0 = (0x8004DA7A, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_NUM_REF_IDX_LX1 = (0x8004DA7B, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_REF_PIC_LIST_REORDER0 = (0x8004DA7C, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_REF_PIC_LIST_REORDER1 = (0x8004DA7D, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_LUMA_WEIGHT_DENOM = (0x8004DA7E, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_CHROMA_WEIGHT_DENOM = (0x8004DA7F, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_WP_WEIGHT_LUMA0 = (0x8004DA80, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_WP_OFFSET_LUMA0 = (0x8004DA81, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_WP_WEIGHT_CHROMA0 = (0x8004DA82, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_WP_OFFSET_CHROMA0 = (0x8004DA83, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_WP_WEIGHT_LUMA1 = (0x8004DA84, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_WP_OFFSET_LUMA1 = (0x8004DA85, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_WP_WEIGHT_CHROMA1 = (0x8004DA86, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_WP_OFFSET_CHROMA1 = (0x8004DA87, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_NUM_REF_PIC_MARKING = (0x8004DA88, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_MMCO4_DUPLICATE = (0x8004DA89, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_MMCO4_MAX_LONG_TERM_FRAME = (0x8004DA8A, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_MMCO5_DUPLICATE = (0x8004DA8B, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_MMCO5_FOLLOWS_MMC06 = (0x8004DA8C, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_MMCO5_COEXIST_MMCO_1_OR_3 = (0x8004DA8D, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_MMCO6_DUPLICATE = (0x8004DA8E, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_MODEL_NUMBER = (0x8004DA8F, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_SLICE_QP = (0x8004DA90, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_LF_ALPHA_C0_OFFSET = (0x8004DA91, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_LF_BETA_OFFSET = (0x8004DA92, "Slice-Header-specific H264 parsing error")
+ DRM_E_H264_SH_SLICE_GROUP_CHANGE = (0x8004DA93, "Slice-Header-specific H264 parsing error")
+ DRM_E_RPROV_INVALID_REQUEST = (0x8004DB00, "Invalid Remote provisioning request received.")
+ DRM_E_RPROV_VERSION_MISSMATCH = (0x8004DB01, "Invalid Remote provisioning version received.")
+ DRM_E_RPROV_INVALID_RESPONSE = (0x8004DB02, "Invalid response received.")
+ DRM_E_RPROV_BOOTSTRAP_FAILURE = (0x8004DB03, "Remote provisioning bootstrap failed.")
+ DRM_E_FIRMWARE_REVOKED = (0x8004DB04, "TEE Firmware is revoked; firmware update necessary.")
+ DRM_E_RPROV_SKIP_BOOTSTRAP = (0x8004DB05, "Remote provisioning does not need bootstrap.")
+ DRM_E_SECURESTOP_STORE_CORRUPT = (0x8004DC00, "The secure stop store is corrupted.")
+ DRM_E_SECURESTOP_SESSION_LOCKED = (0x8004DC02, "The secure stop session is locked and may not be modified.")
+ DRM_E_SECURESTOP_SESSION_CORRUPT = (0x8004DC03, "The secure stop session data is corrupted.")
+ DRM_E_SECURESTOP_SESSION_ACTIVE = (0x8004DC04, "The secure stop session is active and cannot be locked.")
+ DRM_E_SECURESTOP_SESSION_NOT_FOUND = (0x8004DC05, "The secure stop session could not be found in the data store.")
+ DRM_E_SECURESTOP_INVALID_RESPONSE = (0x8004DC06, "The secure stop response is invalid.")
+ DRM_E_SECURESTOP_SESSION_STOPPED = (0x8004DC07, "The secure stop session is stopped and may not be used for decryption.")
+ DRM_E_SECURESTOP_INVALID_PUBLISHER_ID = (0x8004DC08, "Trying to generate a challenge with a publisher ID that doesn't match the one associated with the session.")
+ DRM_E_SECURESTOP_PUBLISHER_ID_INCONSISTENT = (0x8004DC09, "Licenses acquired within the same session don't have the same secure stop publisher ID.")
+ DRM_E_SECURESTOP_INCONSISTENT = (0x8004DC0A, "Some licenses acquired within the same session have secure stop while others don't.")
+ DRM_E_MUTEX_ACQUIRE_FAILED = (0x8004DD00, "The mutex acquire for critical section failed.")
+ DRM_E_MUTEX_LEAVE_FAILED = (0x8004DD01, "The mutex leave for critical section failed.")
+ DRM_E_OEM_DPU_IS_BUSY = (0x8004DD02, "The DPU is still busy handling the previous request.")
+ DRM_E_OEM_DPU_TIMEOUT_FOR_HDCP_TYPE1_LOCK_REQUEST = (0x8004DD03, "The DPU can not finish the command handling from SEC2 before timeout.")
+ DRM_E_OEM_HDCP_TYPE1_LOCK_FAILED = (0x8004DD04, "The DPU failed to lock HDCP2.2 type as requested by SEC2.")
+ DRM_E_OEM_HDCP_TYPE1_LOCK_UNKNOWN = (0x8004DD05, "The HDCP type1 lock RESPONSE value set by DPU is unknown.")
+ DRM_E_OEM_BAR0_PRIV_WRITE_ERROR = (0x8004DD06, "The register could not be written due to PRI failure.")
+ DRM_E_OEM_BAR0_PRIV_READ_ERROR = (0x8004DD07, "The register could not be read due to PRI failure.")
+ DRM_E_OEM_DMA_FAILURE = (0x8004DD08, "The DMA transaction failure.")
+ DRM_E_OEM_UNSUPPORTED_HS_ACTION = (0x8004DD09, "The requested HS action is not supported.")
+ DRM_E_OEM_UNSUPPORTED_KDF = (0x8004DD0A, "The KDF is not supported in OEM's encryption/decryption implementation.")
+ DRM_E_OEM_INVALID_AES_CRYPTO_MODE = (0x8004DD0B, "The requested AES crypto mode is invalid.")
+ DRM_E_OEM_HS_CHK_INVALID_INPUT = (0x8004DD0C, "One or more of the input arguments to HS mode are invalid.")
+ DRM_E_OEM_HS_CHK_CHIP_NOT_SUPPORTED = (0x8004DD0D, "The GPU does not support playready.")
+ DRM_E_OEM_HS_CHK_UCODE_REVOKED = (0x8004DD0E, "Current ucode was revoked.")
+ DRM_E_OEM_HS_CHK_NOT_IN_LSMODE = (0x8004DD0F, "Relevant falcon (depending upon the context from which this error code is being returned) is not in LS mode.")
+ DRM_E_OEM_HS_CHK_INVALID_LS_PRIV_LEVEL = (0x8004DD10, "Relevant falcon (depending upon the context from which this error code is being returned) is not at proper LS priv level.")
+ DRM_E_OEM_HS_CHK_INVALID_REGIONCFG = (0x8004DD11, "The REGIONCFG is not set to correct WPR region.")
+ DRM_E_OEM_HS_CHK_PRIV_SEC_DISABLED_ON_PROD = (0x8004DD12, "The priv sec is unexpectedly disabled on production board.")
+ DRM_E_OEM_HS_CHK_SW_FUSING_ALLOWED_ON_PROD = (0x8004DD13, "The SW fusing is unexpectedly allowed on production board.")
+ DRM_E_OEM_HS_CHK_INTERNAL_SKU_ON_PROD = (0x8004DD14, "The SKU is internal on production board.")
+ DRM_E_OEM_HS_CHK_DEVID_OVERRIDE_ENABLED_ON_PROD = (0x8004DD15, "The devid override is enabled on production board.")
+ DRM_E_OEM_HS_CHK_INCONSISTENT_PROD_MODES = (0x8004DD16, "The falcons in GPU are not in consistent debug/production mode.")
+ DRM_E_OEM_HS_CHK_HUB_ENCRPTION_DISABLED = (0x8004DD17, "The hub encryption is not enabled on FB.")
+ DRM_E_OEM_HS_PR_ILLEGAL_LASSAHS_STATE_AT_HS_ENTRY = (0x8004DD18, "LASSAHS is not in a correct state before doing HS entry")
+ DRM_E_OEM_HS_PR_ILLEGAL_LASSAHS_STATE_AT_MPK_DECRYPT = (0x8004DD19, "LASSAHS is not in a correct state before doing MPK decryption")
+ DRM_E_OEM_HS_PR_ILLEGAL_LASSAHS_STATE_AT_HS_EXIT = (0x8004DD1A, "LASSAHS is not in a correct state before doing HS exit")
+ DRM_E_OEM_PREENTRY_GDK_OUT_OF_MEMORY = (0x8004DD1B, "Preallocation for GDK failed")
+ DRM_E_OEM_GDK_IMEM_BLOCK_REVALIDATION_FAILED = (0x8004DD1C, "Failed to revalidate imem blocks invalidated during LASSAHS")
+ DRM_E_OEM_INVALID_PDI = (0x8004DD1D, "The read PDI value is invalid")
+ DRM_E_OEM_INVALID_SIZE_OF_CDKBDATA = (0x8004DD1E, "The size of NV_KB_CDKBData is not multiple of 16")
+ DRM_E_SE_CRYPTO_MUTEX_ACQUIRE_FAILED = (0x8004DD1F, "Failed to acquire the Security Engine mutex for cypto operations")
+ DRM_E_SE_CRYPTO_MUTEX_RELEASE_FAILED = (0x8004DD20, "Failed to release the Security Engine mutex for cypto operations")
+ DRM_E_SE_CRYPTO_POINT_NOT_ON_CURVE = (0x8004DD21, "Point is not on the elliptic curve")
+ DRM_E_OEM_WAIT_FOR_BAR0_IDLE_FAILED = (0x8004DD22, "WFI on a PRI read/write failed")
+ DRM_E_OEM_ERROR_PRI_UNEXPECTED_FAILURE = (0x8004DD23, "DRM errors.")
+ DRM_E_OEM_CSB_PRIV_WRITE_ERROR = (0x8004DD24, "The register could not be written due to CSB PRI failure.")
+ DRM_E_OEM_CSB_PRIV_READ_ERROR = (0x8004DD25, "The register could not be read due to CSB PRI failure.")
+ DRM_E_OEM_HS_MUTEX_ACQUIRE_FAILED = (0x8004DD26, "The mutex acquire for critical section at HS mode failed.")
+ DRM_E_OEM_HS_MUTEX_RELEASE_FAILED = (0x8004DD27, "The mutex leave for critical section at HS mode failed.")
+ DRM_E_MUTEX_INVALIDARG = (0x8004DDC1, "The Mutex ID is invalid.")
+ DRM_E_MUTEX_ACQUIRE_GETTIME_FAILED = (0x8004DDC2, "Failed to get current time during mutex acquisition.")
+ DRM_E_MUTEX_ACQUIRE_BAD_ID_VALUE = (0x8004DDC3, "The token ID obtained during mutex acquisition is invalid.")
+ DRM_E_MUTEX_ACQUIRE_BAD_SHAREDATA = (0x8004DDC4, "Frame Buffer address of SEC-NVDEC shared structure is invalid.")
+ DRM_E_MUTEX_ACQUIRE_TIMEOUT = (0x8004DDC5, "Timeout occured during mutex acquisition.")
+ DRM_E_SECUREBUS_TIMEOUT = (0x8004DDC6, "Timeout occured on secure bus while waiting for DOC to become empty.")
+ DRM_E_SECUREBUS_READ_FAILED = (0x8004DDC7, "Read request on secure bus failed.")
+ DRM_E_MUTEX_OWNERSHIP_MATCH_FAILED = (0x8004DDC8, "Token ID reserved for the mutex in hardware doesn't match with specified token ID.")
+ DRM_E_INVALID_PDI = (0x8004DDC9, "The read PDI value is invalid.")
+ DRM_E_SECURETIME_INVALID_REQUEST_DATA = (0x8004DE00, "The secure time client request data is invalid.")
+ DRM_E_SECURETIME_CLOCK_NOT_SET = (0x8004DE01, "The secure time clock has not been set.")
+ DRM_E_SECURETIME_RESPONSE_TIMEOUT = (0x8004DE02, "The secure time server response timed out.")
+ DRM_E_SECURETIME_SERVER_SECURITY_LEVEL_TOO_LOW = (0x8004DE03, "The secure time server's security level is too low for the client.")
+ DRM_E_LICENSESERVERTIME_MUST_REACQUIRE_LICENSE = (0x8004DE04, "This license was acquired before the LicenseServerTime feature was enabled. It must be reacquired.")
+ DRM_E_LSRD_DETECTED = (0x8004DF00, "HDS file rollback is detected.")
+ DRM_E_LSRD_INVALID_ACL = (0x8004DF01, "The ACL of the HDS Registry Subkey is invalid.")
+ DRM_E_LSRD_DETECTION_IN_PROGRESS = (0x8004DF02, "The client is currently processing LSRD check operation. Concurrent operations are not allowed.")
+ DRM_E_LSRD_ACL_NOT_PRESENT = (0x8004DF03, "The security descriptor does not contain an ACL.")
+ DRM_E_LSRD_INVALID_COMMAND = (0x8004DF04, "The PlayReady Process received an invalid command.")
+ DRM_E_LSRD_SEQUENCE_NUMBER_IS_AT_MAX_LIMIT = (0x8004DF05, "The LSRD sequence number has reached its maximum limit.")
+ DRM_E_SECUREDELETE_INVALID_RESPONSE = (0x8004DFA0, "The secure delete response is invalid.")
+ DRM_E_PROVENANCE_VALIDATION_FAILED = (0x8004DFB0, "The provenance validation failed. The media file has been tampered with.")
+ DRM_E_INVALID_PROVENANCE_MANIFEST = (0x8004DFB1, "The provenance manifest is invalid.")
+ DRM_E_INVALID_PROVENANCE_CERTIFICATE_CHAIN = (0x8004DFB2, "The provenance certificate chain stored in the manifest is invalid or a valid certificate chain could not be established.")
+ DRM_E_PROVENANCE_UNTRUSTED_ROOT_CERTIFICATE = (0x8004DFB3, "The provenance certificate chain could not be validated because no root certificates were provided in the trusted list.")
+ DRM_E_MP4_EXCEEDED_NUM_CHUNKS = (0x8004DFB4, "A query was made to the MP4 parser to get chunk information for a chunk that does not exist.")
+ DRM_E_MP4_NULL_FILE_STREAM = (0x8004DFB5, "A file stream pointer was unexpectedly null.")
+ DRM_E_MP4_INVALID_MP4_FILE = (0x8004DFB6, "The MP4 file is malformed.")
+ DRM_E_MP4_PARSING_ABORTED = (0x8004DFB7, "MP4 parsing was aborted by the caller.")
+ DRM_E_MP4_EXCEEDED_BOX_SIZE = (0x8004DFB8, "The MP4 file has a box that references data beyond the end of the box.")
+ DRM_E_MP4_INVALID_BOX_SIZE = (0x8004DFB9, "The MP4 file has an invalid box size.")
+ DRM_E_MP4_INVALID_PARSING_STATE = (0x8004DFBA, "MP4 parser functions were invoked in an invalid sequence.")
+ DRM_E_MP4_INVALID_BOX_ATTRIBUTE = (0x8004DFBB, "An MP4 box has a malformed attribute.")
+ DRM_E_MP4_INVALID_BOX_VERSION = (0x8004DFBC, "An MP4 box had an unrecognized version.")
+ DRM_E_MP4_INVALID_STTS_CONTAINS_ENTRIES = (0x8004DFBD, "The 'stts' box should not contain entries in purely fragmented Mp4 files.")
+ DRM_E_C2PA_FTYP_NOT_SET = (0x8004DFBE, "The 'ftyp' box lacks the 'c2pa' compatible_brands attribute.")
+ DRM_E_MP4_BOX_LARGER_THAN_4GB = (0x8004DFBF, "The MP4 file has a box that is larger than 4 GB in size which is not supported by this parsre.")
+ DRM_E_MP4_C2PA_BOX_ALREADY_PRESENT = (0x8004DFC0, "The MP4 file already contains an unexpected C2PA Box.")
+ DRM_E_MP4_CIB_BOX_ALREADY_PRESENT = (0x8004DFC1, "The MP4 file already contains an unexpected Content Integrity Box.")
+ DRM_E_C2PA_MANIFEST_BOX_NOT_PRESENT = (0x8004DFC2, "The MP4 file lacks the expected c2pa Manifest Box.")
+ DRM_E_C2PA_MERKLE_BOX_NOT_PRESENT = (0x8004DFC3, "The MP4 file lacks the expected c2pa Merkle Box.")
+ DRM_E_MP4_INVALID_C2PA_MANIFEST_BOX = (0x8004DFC4, "The MP4 file contains an invalid c2pa box with type Manifest.")
+ DRM_E_MP4_INVALID_C2PA_MERKLE_BOX = (0x8004DFC5, "The MP4 file contains an invalid c2pa box with type Merkle.")
+ DRM_E_MP4_INVALID_PARSING_TYPE = (0x8004DFC6, "The MP4 parser was initialized with an invalid type.")
+ DRM_E_MP4_FILE_LACKS_MOOV_BOX = (0x8004DFC7, "The MP4 file does not have a 'moov' box.")
+ DRM_E_MP4_EXCEEDED_NUM_TRACK_IDS = (0x8004DFC8, "A query was made to the MP4 parser to get track information for a track that does not exist.")
+ DRM_E_MP4_INVALID_EXCLUSION_RULE = (0x8004DFC9, "An Exclusion Rule was passed into the MP4 Parser that was incorrectly formatted.")
+ DRM_E_WIN32_FILE_NOT_FOUND = (0x80070002, "The system cannot find the file specified.")
+ DRM_E_HANDLE = (0x80070006, "Invalid handle.")
+ DRM_E_WIN32_NO_MORE_FILES = (0x80070012, "There are no more files.")
+ DRM_E_INVALIDARG = (0x80070057, "The parameter is incorrect.")
+ DRM_E_BUFFERTOOSMALL = (0x8007007A, "The data area passed to a function is too small.")
+ DRM_E_NOMORE = (0x80070103, "No more data is available.")
+ DRM_E_ARITHMETIC_OVERFLOW = (0x80070216, "Arithmetic result exceeded maximum value.")
+ DRM_E_NOT_FOUND = (0x80070490, "Element not found.")
+ DRM_E_INVALID_COMMAND_LINE = (0x80070667, "Invalid command line argument.")
+ DRM_E_FAILED_TO_STORE_LICENSE = (0xC00D2712, "License storage is not working.")
+ DRM_E_PARAMETERS_MISMATCHED = (0xC00D272F, "A problem has occurred in the Digital Rights Management component.")
+ DRM_E_NOT_ALL_STORED = (0xC00D275F, "Some of the licenses could not be stored.")
+
+ @property
+ def code(self):
+ """Return the numeric code of the error."""
+ return hex(self.value[0])
+
+ @property
+ def message(self):
+ """Return the descriptive message of the error."""
+ return self.value[1]
+
+ @staticmethod
+ def from_code(code: str):
+ """Get the error message for a given error code."""
+ for error in DRMResult:
+ if error.value[0] == int(code, 16):
+ return error
+ raise ValueError("Invalid DRMResult")
diff --git a/scripts/pyplayready/pyplayready/exceptions.py b/scripts/pyplayready/pyplayready/exceptions.py
index a2ca2c3..a388cb2 100644
--- a/scripts/pyplayready/pyplayready/exceptions.py
+++ b/scripts/pyplayready/pyplayready/exceptions.py
@@ -36,3 +36,7 @@ class InvalidCertificateChain(PyPlayreadyException):
class OutdatedDevice(PyPlayreadyException):
"""The PlayReady Device is outdated and does not support a specific operation."""
+
+
+class ServerException(PyPlayreadyException):
+ """Recasted on the client if found in license response."""
\ No newline at end of file
diff --git a/scripts/pyplayready/pyplayready/license/xmrlicense.py b/scripts/pyplayready/pyplayready/license/xmrlicense.py
index 1e0421d..3ce6492 100644
--- a/scripts/pyplayready/pyplayready/license/xmrlicense.py
+++ b/scripts/pyplayready/pyplayready/license/xmrlicense.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import base64
+from enum import IntEnum
from typing import Union
from Crypto.Cipher import AES
@@ -8,6 +9,102 @@ from Crypto.Hash import CMAC
from construct import Const, GreedyRange, Struct, Int32ub, Bytes, Int16ub, this, Switch, LazyBound, Array, Container
+class XMRObjectTypes(IntEnum):
+ INVALID = 0x0000
+ OUTER_CONTAINER = 0x0001
+ GLOBAL_POLICY_CONTAINER = 0x0002
+ MINIMUM_ENVIRONMENT_OBJECT = 0x0003
+ PLAYBACK_POLICY_CONTAINER = 0x0004
+ OUTPUT_PROTECTION_OBJECT = 0x0005
+ UPLINK_KID_OBJECT = 0x0006
+ EXPLICIT_ANALOG_VIDEO_OUTPUT_PROTECTION_CONTAINER = 0x0007
+ ANALOG_VIDEO_OUTPUT_CONFIGURATION_OBJECT = 0x0008
+ KEY_MATERIAL_CONTAINER = 0x0009
+ CONTENT_KEY_OBJECT = 0x000A
+ SIGNATURE_OBJECT = 0x000B
+ SERIAL_NUMBER_OBJECT = 0x000C
+ SETTINGS_OBJECT = 0x000D
+ COPY_POLICY_CONTAINER = 0x000E
+ ALLOW_PLAYLISTBURN_POLICY_CONTAINER = 0x000F
+ INCLUSION_LIST_OBJECT = 0x0010
+ PRIORITY_OBJECT = 0x0011
+ EXPIRATION_OBJECT = 0x0012
+ ISSUEDATE_OBJECT = 0x0013
+ EXPIRATION_AFTER_FIRSTUSE_OBJECT = 0x0014
+ EXPIRATION_AFTER_FIRSTSTORE_OBJECT = 0x0015
+ METERING_OBJECT = 0x0016
+ PLAYCOUNT_OBJECT = 0x0017
+ GRACE_PERIOD_OBJECT = 0x001A
+ COPYCOUNT_OBJECT = 0x001B
+ COPY_PROTECTION_OBJECT = 0x001C
+ PLAYLISTBURN_COUNT_OBJECT = 0x001F
+ REVOCATION_INFORMATION_VERSION_OBJECT = 0x0020
+ RSA_DEVICE_KEY_OBJECT = 0x0021
+ SOURCEID_OBJECT = 0x0022
+ REVOCATION_CONTAINER = 0x0025
+ RSA_LICENSE_GRANTER_KEY_OBJECT = 0x0026
+ USERID_OBJECT = 0x0027
+ RESTRICTED_SOURCEID_OBJECT = 0x0028
+ DOMAIN_ID_OBJECT = 0x0029
+ ECC_DEVICE_KEY_OBJECT = 0x002A
+ GENERATION_NUMBER_OBJECT = 0x002B
+ POLICY_METADATA_OBJECT = 0x002C
+ OPTIMIZED_CONTENT_KEY_OBJECT = 0x002D
+ EXPLICIT_DIGITAL_AUDIO_OUTPUT_PROTECTION_CONTAINER = 0x002E
+ RINGTONE_POLICY_CONTAINER = 0x002F
+ EXPIRATION_AFTER_FIRSTPLAY_OBJECT = 0x0030
+ DIGITAL_AUDIO_OUTPUT_CONFIGURATION_OBJECT = 0x0031
+ REVOCATION_INFORMATION_VERSION_2_OBJECT = 0x0032
+ EMBEDDING_BEHAVIOR_OBJECT = 0x0033
+ SECURITY_LEVEL = 0x0034
+ COPY_TO_PC_CONTAINER = 0x0035
+ PLAY_ENABLER_CONTAINER = 0x0036
+ MOVE_ENABLER_OBJECT = 0x0037
+ COPY_ENABLER_CONTAINER = 0x0038
+ PLAY_ENABLER_OBJECT = 0x0039
+ COPY_ENABLER_OBJECT = 0x003A
+ UPLINK_KID_2_OBJECT = 0x003B
+ COPY_POLICY_2_CONTAINER = 0x003C
+ COPYCOUNT_2_OBJECT = 0x003D
+ RINGTONE_ENABLER_OBJECT = 0x003E
+ EXECUTE_POLICY_CONTAINER = 0x003F
+ EXECUTE_POLICY_OBJECT = 0x0040
+ READ_POLICY_CONTAINER = 0x0041
+ EXTENSIBLE_POLICY_RESERVED_42 = 0x0042
+ EXTENSIBLE_POLICY_RESERVED_43 = 0x0043
+ EXTENSIBLE_POLICY_RESERVED_44 = 0x0044
+ EXTENSIBLE_POLICY_RESERVED_45 = 0x0045
+ EXTENSIBLE_POLICY_RESERVED_46 = 0x0046
+ EXTENSIBLE_POLICY_RESERVED_47 = 0x0047
+ EXTENSIBLE_POLICY_RESERVED_48 = 0x0048
+ EXTENSIBLE_POLICY_RESERVED_49 = 0x0049
+ EXTENSIBLE_POLICY_RESERVED_4A = 0x004A
+ EXTENSIBLE_POLICY_RESERVED_4B = 0x004B
+ EXTENSIBLE_POLICY_RESERVED_4C = 0x004C
+ EXTENSIBLE_POLICY_RESERVED_4D = 0x004D
+ EXTENSIBLE_POLICY_RESERVED_4E = 0x004E
+ EXTENSIBLE_POLICY_RESERVED_4F = 0x004F
+ REMOVAL_DATE_OBJECT = 0x0050
+ AUX_KEY_OBJECT = 0x0051
+ UPLINKX_OBJECT = 0x0052
+ INVALID_RESERVED_53 = 0x0053
+ APPLICATION_ID_LIST = 0x0054
+ REAL_TIME_EXPIRATION = 0x0055
+ ND_TX_AUTH_CONTAINER = 0x0056
+ ND_TX_AUTH_OBJECT = 0x0057
+ EXPLICIT_DIGITAL_VIDEO_PROTECTION = 0x0058
+ DIGITAL_VIDEO_OPL = 0x0059
+ SECURESTOP = 0x005A
+ SECURESTOP2 = 0x005C
+ OPTIMIZED_CONTENT_KEY2 = 0x005D
+ COPY_UNKNOWN_OBJECT = 0xFFFD
+ PLAYBACK_UNKNOWN_OBJECT = 0xFFFD
+ GLOBAL_POLICY_UNKNOWN_OBJECT = 0xFFFD
+ COPY_UNKNOWN_CONTAINER = 0xFFFE
+ PLAYBACK_UNKNOWN_CONTAINER = 0xFFFE
+ UNKNOWN_CONTAINERS = 0xFFFE
+
+
class _XMRLicenseStructs:
PlayEnablerType = Struct(
"player_enabler_type" / Bytes(16)
@@ -159,34 +256,34 @@ class _XMRLicenseStructs:
"data" / Switch(
lambda ctx: ctx.type,
{
- 0x0005: OutputProtectionLevelRestrictionObject,
- 0x0008: AnalogVideoOutputConfigurationRestriction,
- 0x000a: ContentKeyObject,
- 0x000b: SignatureObject,
- 0x000d: RightsSettingsObject,
- 0x0012: ExpirationRestrictionObject,
- 0x0013: IssueDateObject,
- 0x0016: MeteringRestrictionObject,
- 0x001a: GracePeriodObject,
- 0x0022: SourceIdObject,
- 0x002a: ECCKeyObject,
- 0x002c: PolicyMetadataObject,
- 0x0029: DomainRestrictionObject,
- 0x0030: ExpirationAfterFirstPlayRestrictionObject,
- 0x0031: DigitalAudioOutputRestrictionObject,
- 0x0032: RevInfoVersionObject,
- 0x0033: EmbeddedLicenseSettingsObject,
- 0x0034: SecurityLevelObject,
- 0x0037: MoveObject,
- 0x0039: PlayEnablerType,
- 0x003a: CopyEnablerObject,
- 0x003b: UplinkKIDObject,
- 0x003d: CopyCountRestrictionObject,
- 0x0050: RemovalDateObject,
- 0x0051: AuxiliaryKeysObject,
- 0x0052: UplinkKeyObject3,
- 0x005a: SecureStopRestrictionObject,
- 0x0059: DigitalVideoOutputRestrictionObject
+ XMRObjectTypes.OUTPUT_PROTECTION_OBJECT: OutputProtectionLevelRestrictionObject,
+ XMRObjectTypes.ANALOG_VIDEO_OUTPUT_CONFIGURATION_OBJECT: AnalogVideoOutputConfigurationRestriction,
+ XMRObjectTypes.CONTENT_KEY_OBJECT: ContentKeyObject,
+ XMRObjectTypes.SIGNATURE_OBJECT: SignatureObject,
+ XMRObjectTypes.SETTINGS_OBJECT: RightsSettingsObject,
+ XMRObjectTypes.EXPIRATION_OBJECT: ExpirationRestrictionObject,
+ XMRObjectTypes.ISSUEDATE_OBJECT: IssueDateObject,
+ XMRObjectTypes.METERING_OBJECT: MeteringRestrictionObject,
+ XMRObjectTypes.GRACE_PERIOD_OBJECT: GracePeriodObject,
+ XMRObjectTypes.SOURCEID_OBJECT: SourceIdObject,
+ XMRObjectTypes.ECC_DEVICE_KEY_OBJECT: ECCKeyObject,
+ XMRObjectTypes.DOMAIN_ID_OBJECT: DomainRestrictionObject,
+ XMRObjectTypes.POLICY_METADATA_OBJECT: PolicyMetadataObject,
+ XMRObjectTypes.EXPIRATION_AFTER_FIRSTPLAY_OBJECT: ExpirationAfterFirstPlayRestrictionObject,
+ XMRObjectTypes.DIGITAL_AUDIO_OUTPUT_CONFIGURATION_OBJECT: DigitalAudioOutputRestrictionObject,
+ XMRObjectTypes.REVOCATION_INFORMATION_VERSION_2_OBJECT: RevInfoVersionObject,
+ XMRObjectTypes.EMBEDDING_BEHAVIOR_OBJECT: EmbeddedLicenseSettingsObject,
+ XMRObjectTypes.SECURITY_LEVEL: SecurityLevelObject,
+ XMRObjectTypes.MOVE_ENABLER_OBJECT: MoveObject,
+ XMRObjectTypes.PLAY_ENABLER_OBJECT: PlayEnablerType,
+ XMRObjectTypes.COPY_ENABLER_OBJECT: CopyEnablerObject,
+ XMRObjectTypes.UPLINK_KID_2_OBJECT: UplinkKIDObject,
+ XMRObjectTypes.COPYCOUNT_2_OBJECT: CopyCountRestrictionObject,
+ XMRObjectTypes.REMOVAL_DATE_OBJECT: RemovalDateObject,
+ XMRObjectTypes.AUX_KEY_OBJECT: AuxiliaryKeysObject,
+ XMRObjectTypes.UPLINKX_OBJECT: UplinkKeyObject3,
+ XMRObjectTypes.DIGITAL_VIDEO_OPL: DigitalVideoOutputRestrictionObject,
+ XMRObjectTypes.SECURESTOP: SecureStopRestrictionObject,
},
default=LazyBound(lambda ctx: _XMRLicenseStructs.XmrObject)
)
@@ -227,9 +324,6 @@ class XMRLicense(_XMRLicenseStructs):
def dumps(self) -> bytes:
return self._license_obj.build(self.parsed)
- def struct(self) -> _XMRLicenseStructs.XmrLicense:
- return self._license_obj
-
def _locate(self, container: Container):
if container.flags == 2 or container.flags == 3:
return self._locate(container.data)
@@ -243,12 +337,12 @@ class XMRLicense(_XMRLicenseStructs):
yield container.data
def get_content_keys(self):
- yield from self.get_object(10)
+ yield from self.get_object(XMRObjectTypes.CONTENT_KEY_OBJECT)
def check_signature(self, integrity_key: bytes) -> bool:
cmac = CMAC.new(integrity_key, ciphermod=AES)
- signature_data = next(self.get_object(11))
+ signature_data = next(self.get_object(XMRObjectTypes.SIGNATURE_OBJECT))
cmac.update(self.dumps()[:-(signature_data.signature_data_length + 12)])
return signature_data.signature_data == cmac.digest()
diff --git a/scripts/pyplayready/pyplayready/main.py b/scripts/pyplayready/pyplayready/main.py
index e18a927..7362500 100644
--- a/scripts/pyplayready/pyplayready/main.py
+++ b/scripts/pyplayready/pyplayready/main.py
@@ -7,7 +7,7 @@ import click
import requests
from Crypto.Random import get_random_bytes
-from pyplayready import __version__, InvalidCertificateChain
+from pyplayready import __version__, InvalidCertificateChain, InvalidLicense
from pyplayready.system.bcert import CertificateChain, Certificate
from pyplayready.cdm import Cdm
from pyplayready.device import Device
@@ -28,7 +28,7 @@ def main(version: bool, debug: bool) -> None:
copyright_years = f"2024-{current_year}"
log.info("pyplayready version %s Copyright (c) %s DevLARLEY, Erevoc, DevataDev", __version__, copyright_years)
- log.info("https://github.com/ready-dl/pyplayready")
+ log.info("https://git.gay/ready-dl/pyplayready")
log.info("Run 'pyplayready --help' for help")
if version:
return
@@ -56,11 +56,11 @@ def license_(device_path: Path, pssh: PSSH, server: str) -> None:
session_id = cdm.open()
log.info("Opened Session")
- challenge = cdm.get_license_challenge(session_id, pssh.get_wrm_headers()[0])
+ challenge = cdm.get_license_challenge(session_id, pssh.wrm_headers[0])
log.info("Created License Request (Challenge)")
log.debug(challenge)
- license_res = requests.post(
+ license_response = requests.post(
url=server,
headers={
'Content-Type': 'text/xml; charset=UTF-8',
@@ -68,15 +68,15 @@ def license_(device_path: Path, pssh: PSSH, server: str) -> None:
data=challenge
)
- if license_res.status_code != 200:
- log.error(f"Failed to send challenge [{license_res.status_code}]: {license_res.text}")
- return
-
- licence = license_res.text
- log.info("Got License Message")
+ licence = license_response.text
log.debug(licence)
- cdm.parse_license(session_id, licence)
+ try:
+ cdm.parse_license(session_id, licence)
+ except InvalidLicense as e:
+ log.error(e)
+ return
+
log.info("License Parsed Successfully")
for key in cdm.get_keys(session_id):
@@ -89,7 +89,7 @@ def license_(device_path: Path, pssh: PSSH, server: str) -> None:
@main.command()
@click.argument("device", type=Path)
@click.option("-c", "--ckt", type=click.Choice(["aesctr", "aescbc"], case_sensitive=False), default="aesctr", help="Content Key Encryption Type")
-@click.option("-sl", "--security_level", type=click.Choice(["150", "2000", "3000"], case_sensitive=False), default="2000", help="Minimum Security Level")
+@click.option("-sl", "--security_level", type=click.Choice(["150", "2000", "3000"]), default="2000", help="Minimum Security Level")
@click.pass_context
def test(ctx: click.Context, device: Path, ckt: str, security_level: str) -> None:
"""
@@ -127,12 +127,16 @@ def test(ctx: click.Context, device: Path, ckt: str, security_level: str) -> Non
@main.command()
@click.option("-k", "--group_key", type=Path, required=True, help="Device ECC private group key")
+@click.option("-e", "--encryption_key", type=Path, required=False, help="Optional Device ECC private encryption key")
+@click.option("-s", "--signing_key", type=Path, required=False, help="Optional Device ECC private signing key")
@click.option("-c", "--group_certificate", type=Path, required=True, help="Device group certificate chain")
@click.option("-o", "--output", type=Path, default=None, help="Output Path or Directory")
@click.pass_context
def create_device(
ctx: click.Context,
group_key: Path,
+ encryption_key: Optional[Path],
+ signing_key: Optional[Path],
group_certificate: Path,
output: Optional[Path] = None
) -> None:
@@ -144,8 +148,8 @@ def create_device(
log = logging.getLogger("create-device")
- encryption_key = ECCKey.generate()
- signing_key = ECCKey.generate()
+ encryption_key = ECCKey.load(encryption_key) if encryption_key else ECCKey.generate()
+ signing_key = ECCKey.load(signing_key) if signing_key else ECCKey.generate()
group_key = ECCKey.load(group_key)
certificate_chain = CertificateChain.load(group_certificate)
@@ -199,9 +203,17 @@ def create_device(
@main.command()
@click.argument("prd_path", type=Path)
+@click.option("-e", "--encryption_key", type=Path, required=False, help="Optional Device ECC private encryption key")
+@click.option("-s", "--signing_key", type=Path, required=False, help="Optional Device ECC private signing key")
@click.option("-o", "--output", type=Path, default=None, help="Output Path or Directory")
@click.pass_context
-def reprovision_device(ctx: click.Context, prd_path: Path, output: Optional[Path] = None) -> None:
+def reprovision_device(
+ ctx: click.Context,
+ prd_path: Path,
+ encryption_key: Optional[Path],
+ signing_key: Optional[Path],
+ output: Optional[Path] = None
+) -> None:
"""
Reprovision a Playready Device (.prd) by creating a new leaf certificate and new encryption/signing keys.
Will override the device if an output path or directory is not specified
@@ -221,8 +233,8 @@ def reprovision_device(ctx: click.Context, prd_path: Path, output: Optional[Path
device.group_certificate.remove(0)
- encryption_key = ECCKey.generate()
- signing_key = ECCKey.generate()
+ encryption_key = ECCKey.load(encryption_key) if encryption_key else ECCKey.generate()
+ signing_key = ECCKey.load(signing_key) if signing_key else ECCKey.generate()
device.encryption_key = encryption_key
device.signing_key = signing_key
@@ -299,7 +311,7 @@ def export_device(ctx: click.Context, prd_path: Path, out_dir: Optional[Path] =
log.info("Exported Group Certificate to bgroupcert.dat")
-@main.command("serve", short_help="Serve your local CDM and Playready Devices Remotely.")
+@main.command("serve", short_help="Serve your local CDM and Playready Devices remotely.")
@click.argument("config_path", type=Path)
@click.option("-h", "--host", type=str, default="127.0.0.1", help="Host to serve from.")
@click.option("-p", "--port", type=int, default=7723, help="Port to serve from.")
diff --git a/scripts/pyplayready/pyplayready/remote/remotecdm.py b/scripts/pyplayready/pyplayready/remote/remotecdm.py
index 9afd6b3..404858f 100644
--- a/scripts/pyplayready/pyplayready/remote/remotecdm.py
+++ b/scripts/pyplayready/pyplayready/remote/remotecdm.py
@@ -1,14 +1,17 @@
from __future__ import annotations
-import re
+import logging
+from typing import Union
import requests
+from pyplayready import InvalidLicense
from pyplayready.cdm import Cdm
from pyplayready.device import Device
from pyplayready.license.key import Key
from pyplayready.exceptions import (DeviceMismatch, InvalidInitData)
+from pyplayready.system.wrmheader import WRMHeader
class RemoteCdm(Cdm):
@@ -49,99 +52,101 @@ class RemoteCdm(Cdm):
# spoof certificate_chain and ecc_key just so we can construct via super call
super().__init__(security_level, None, None, None)
+ self._logger = logging.getLogger()
+
self.__session = requests.Session()
self.__session.headers.update({
"X-Secret-Key": secret
})
- r = requests.head(self.host)
- if r.status_code != 200:
- raise ValueError(f"Could not test Remote API version [{r.status_code}]")
- server = r.headers.get("Server")
- if not server or "pyplayready serve" not in server.lower():
- raise ValueError(f"This Remote CDM API does not seem to be a pyplayready serve API ({server}).")
- server_version_re = re.search(r"pyplayready serve v([\d.]+)", server, re.IGNORECASE)
- if not server_version_re:
- raise ValueError("The pyplayready server API is not stating the version correctly, cannot continue.")
- server_version = server_version_re.group(1)
- if server_version < "0.3.1":
- raise ValueError(f"This pyplayready serve API version ({server_version}) is not supported.")
+ response = requests.head(self.host)
+
+ if response.status_code != 200:
+ self._logger.warning(f"Could not test Remote API version [{response.status_code}]")
+
+ server = response.headers.get("Server")
+ if not server or "playready serve" not in server.lower():
+ self._logger.warning(f"This Remote CDM API does not seem to be a playready serve API ({server}).")
@classmethod
def from_device(cls, device: Device) -> RemoteCdm:
raise NotImplementedError("You cannot load a RemoteCdm from a local Device file.")
def open(self) -> bytes:
- r = self.__session.get(
+ response = self.__session.get(
url=f"{self.host}/{self.device_name}/open"
- ).json()
+ )
+ response_json = response.json()
- if r['status'] != 200:
- raise ValueError(f"Cannot Open CDM Session, {r['message']} [{r['status']}]")
- r = r["data"]
+ if response.status_code != 200:
+ raise ValueError(f"Cannot Open CDM Session, {response_json['message']} [{response.status_code}]")
- if int(r["device"]["security_level"]) != self.security_level:
+ if int(response_json["data"]["device"]["security_level"]) != self.security_level:
raise DeviceMismatch("The Security Level specified does not match the one specified in the API response.")
- return bytes.fromhex(r["session_id"])
+ return bytes.fromhex(response_json["data"]["session_id"])
def close(self, session_id: bytes) -> None:
- r = self.__session.get(
+ response = self.__session.get(
url=f"{self.host}/{self.device_name}/close/{session_id.hex()}"
- ).json()
- if r["status"] != 200:
- raise ValueError(f"Cannot Close CDM Session, {r['message']} [{r['status']}]")
+ )
+ response_json = response.json()
- def get_license_challenge(
- self,
- session_id: bytes,
- wrm_header: str,
- ) -> str:
+ if response.status_code != 200:
+ raise ValueError(f"Cannot Close CDM Session, {response_json['message']} [{response.status_code}]")
+
+ def get_license_challenge(self, session_id: bytes, wrm_header: Union[WRMHeader, str]) -> str:
if not wrm_header:
raise InvalidInitData("A wrm_header must be provided.")
+ if isinstance(wrm_header, WRMHeader):
+ wrm_header = wrm_header.dumps()
if not isinstance(wrm_header, str):
- raise InvalidInitData(f"Expected wrm_header to be a {str}, not {wrm_header!r}")
+ raise ValueError(f"Expected WRMHeader to be a {str} or {WRMHeader} not {wrm_header!r}")
- r = self.__session.post(
+ response = self.__session.post(
url=f"{self.host}/{self.device_name}/get_license_challenge",
json={
"session_id": session_id.hex(),
- "init_data": wrm_header,
+ "init_data": wrm_header
}
- ).json()
- if r["status"] != 200:
- raise ValueError(f"Cannot get Challenge, {r['message']} [{r['status']}]")
- r = r["data"]
+ )
+ response_json = response.json()
- return r["challenge"]
+ if response.status_code != 200:
+ raise ValueError(f"Cannot get Challenge, {response_json['message']} [{response.status_code}]")
+
+ return response_json["data"]["challenge"]
def parse_license(self, session_id: bytes, license_message: str) -> None:
if not license_message:
- raise Exception("Cannot parse an empty license_message")
+ raise InvalidLicense("Cannot parse an empty license_message")
if not isinstance(license_message, str):
- raise Exception(f"Expected license_message to be a {str}, not {license_message!r}")
+ raise InvalidLicense(f"Expected license_message to be a {str}, not {license_message!r}")
- r = self.__session.post(
+ response = self.__session.post(
url=f"{self.host}/{self.device_name}/parse_license",
json={
"session_id": session_id.hex(),
"license_message": license_message
}
- ).json()
- if r["status"] != 200:
- raise ValueError(f"Cannot parse License, {r['message']} [{r['status']}]")
+ )
+ response_json = response.json()
+
+ if response.status_code != 200:
+ raise ValueError(f"Cannot parse License, {response_json['message']} [{response.status_code}]")
def get_keys(self, session_id: bytes) -> list[Key]:
- r = self.__session.post(
+ response = self.__session.post(
url=f"{self.host}/{self.device_name}/get_keys",
json={
"session_id": session_id.hex()
}
- ).json()
- if r["status"] != 200:
- raise ValueError(f"Could not get Keys, {r['message']} [{r['status']}]")
- r = r["data"]
+ )
+ response_json = response.json()
+
+ if response.status_code != 200:
+ raise ValueError(f"Could not get Keys, {response_json['message']} [{response.status_code}]")
return [
Key(
@@ -151,7 +156,7 @@ class RemoteCdm(Cdm):
cipher_type=key["cipher_type"],
key_length=key["key_length"]
)
- for key in r["keys"]
+ for key in response_json["data"]["keys"]
]
diff --git a/scripts/pyplayready/pyplayready/remote/serve.py b/scripts/pyplayready/pyplayready/remote/serve.py
index 64a0b69..c83695b 100644
--- a/scripts/pyplayready/pyplayready/remote/serve.py
+++ b/scripts/pyplayready/pyplayready/remote/serve.py
@@ -34,10 +34,7 @@ async def _cleanup(app: web.Application) -> None:
@routes.get("/")
async def ping(_: Any) -> web.Response:
- return web.json_response({
- "status": 200,
- "message": "Pong!"
- })
+ return web.json_response({"message": "OK"})
@routes.get("/{device}/open")
@@ -49,10 +46,7 @@ async def open_(request: web.Request) -> web.Response:
if device_name not in user["devices"] or device_name not in request.app["config"]["devices"]:
# we don't want to be verbose with the error as to not reveal device names
# by trial and error to users that are not authorized to use them
- return web.json_response({
- "status": 403,
- "message": f"Device '{device_name}' is not found or you are not authorized to use it."
- }, status=403)
+ return web.json_response({"message": f"Device '{device_name}' is not found or you are not authorized to use it."}, status=403)
cdm: Optional[Cdm] = request.app["cdms"].get((secret_key, device_name))
if not cdm:
@@ -62,13 +56,9 @@ async def open_(request: web.Request) -> web.Response:
try:
session_id = cdm.open()
except TooManySessions as e:
- return web.json_response({
- "status": 400,
- "message": str(e)
- }, status=400)
+ return web.json_response({"message": str(e)}, status=400)
return web.json_response({
- "status": 200,
"message": "Success",
"data": {
"session_id": session_id.hex(),
@@ -87,23 +77,14 @@ async def close(request: web.Request) -> web.Response:
cdm: Optional[Cdm] = request.app["cdms"].get((secret_key, device_name))
if not cdm:
- return web.json_response({
- "status": 400,
- "message": f"No Cdm session for {device_name} has been opened yet. No session to close."
- }, status=400)
+ return web.json_response({"message": f"No Cdm session for {device_name} has been opened yet. No session to close."}, status=400)
try:
cdm.close(session_id)
except InvalidSession:
- return web.json_response({
- "status": 400,
- "message": f"Invalid Session ID '{session_id.hex()}', it may have expired."
- }, status=400)
+ return web.json_response({"message": f"Invalid Session ID '{session_id.hex()}', it may have expired."}, status=400)
- return web.json_response({
- "status": 200,
- "message": f"Successfully closed Session '{session_id.hex()}'."
- })
+ return web.json_response({"message": f"Successfully closed Session '{session_id.hex()}'."})
@routes.post("/{device}/get_license_challenge")
@@ -112,58 +93,36 @@ async def get_license_challenge(request: web.Request) -> web.Response:
device_name = request.match_info["device"]
body = await request.json()
- for required_field in ("session_id", "init_data"):
+ for required_field in ("init_data", "session_id"):
if not body.get(required_field):
- return web.json_response({
- "status": 400,
- "message": f"Missing required field '{required_field}' in JSON body."
- }, status=400)
+ return web.json_response({"message": f"Missing required field '{required_field}' in JSON body."}, status=400)
- # get session id
- session_id = bytes.fromhex(body["session_id"])
-
- # get cdm
cdm: Optional[Cdm] = request.app["cdms"].get((secret_key, device_name))
if not cdm:
- return web.json_response({
- "status": 400,
- "message": f"No Cdm session for {device_name} has been opened yet. No session to use."
- }, status=400)
+ return web.json_response({"message": f"No Cdm session for {device_name} has been opened yet. No session to use."}, status=400)
- # get init data
+ session_id = bytes.fromhex(body["session_id"])
init_data = body["init_data"]
if not init_data.startswith(" web.Response:
device_name = request.match_info["device"]
body = await request.json()
- for required_field in ("session_id", "license_message"):
+ for required_field in ("license_message", "session_id"):
if not body.get(required_field):
- return web.json_response({
- "status": 400,
- "message": f"Missing required field '{required_field}' in JSON body."
- }, status=400)
+ return web.json_response({"message": f"Missing required field '{required_field}' in JSON body."}, status=400)
- # get session id
- session_id = bytes.fromhex(body["session_id"])
-
- # get cdm
cdm: Optional[Cdm] = request.app["cdms"].get((secret_key, device_name))
if not cdm:
- return web.json_response({
- "status": 400,
- "message": f"No Cdm session for {device_name} has been opened yet. No session to use."
- }, status=400)
+ return web.json_response({"message": f"No Cdm session for {device_name} has been opened yet. No session to use."}, status=400)
+
+ session_id = bytes.fromhex(body["session_id"])
+ license_message = body["license_message"]
- # parse the license message
try:
- cdm.parse_license(session_id, body["license_message"])
+ cdm.parse_license(session_id, license_message)
except InvalidSession:
- return web.json_response({
- "status": 400,
- "message": f"Invalid Session ID '{session_id.hex()}', it may have expired."
- }, status=400)
+ return web.json_response({"message": f"Invalid Session ID '{session_id.hex()}', it may have expired."}, status=400)
except InvalidLicense as e:
- return web.json_response({
- "status": 400,
- "message": f"Invalid License, {e}"
- }, status=400)
+ return web.json_response({"message": f"Invalid License, {e}"}, status=400)
except Exception as e:
- return web.json_response({
- "status": 500,
- "message": f"Error, {e}"
- }, status=500)
+ return web.json_response({"message": f"Error, {e}"}, status=500)
- return web.json_response({
- "status": 200,
- "message": "Successfully parsed and loaded the Keys from the License message."
- })
+ return web.json_response({"message": "Successfully parsed and loaded the Keys from the License message."})
@routes.post("/{device}/get_keys")
@@ -228,37 +167,21 @@ async def get_keys(request: web.Request) -> web.Response:
body = await request.json()
for required_field in ("session_id",):
if not body.get(required_field):
- return web.json_response({
- "status": 400,
- "message": f"Missing required field '{required_field}' in JSON body."
- }, status=400)
+ return web.json_response({"message": f"Missing required field '{required_field}' in JSON body."}, status=400)
- # get session id
session_id = bytes.fromhex(body["session_id"])
- # get cdm
cdm = request.app["cdms"].get((secret_key, device_name))
if not cdm:
- return web.json_response({
- "status": 400,
- "message": f"No Cdm session for {device_name} has been opened yet. No session to use."
- }, status=400)
+ return web.json_response({"message": f"No Cdm session for {device_name} has been opened yet. No session to use."}, status=400)
- # get keys
try:
keys = cdm.get_keys(session_id)
except InvalidSession:
- return web.json_response({
- "status": 400,
- "message": f"Invalid Session ID '{session_id.hex()}', it may have expired."
- }, status=400)
+ return web.json_response({"message": f"Invalid Session ID '{session_id.hex()}', it may have expired."}, status=400)
except Exception as e:
- return web.json_response({
- "status": 500,
- "message": f"Error, {e}"
- }, status=500)
+ return web.json_response({"message": f"Error, {e}"}, status=500)
- # get the keys in json form
keys_json = [
{
"key_id": key.key_id.hex,
@@ -271,7 +194,6 @@ async def get_keys(request: web.Request) -> web.Response:
]
return web.json_response({
- "status": 200,
"message": "Success",
"data": {
"keys": keys_json
@@ -285,28 +207,19 @@ async def authentication(request: web.Request, handler: Handler) -> web.Response
if request.path != "/" and not secret_key:
request.app.logger.debug(f"{request.remote} did not provide authorization.")
- response = web.json_response({
- "status": "401",
- "message": "Secret Key is Empty."
- }, status=401)
+ response = web.json_response({"message": "Secret Key is Empty."}, status=401)
elif request.path != "/" and secret_key not in request.app["config"]["users"]:
request.app.logger.debug(f"{request.remote} failed authentication with '{secret_key}'.")
- response = web.json_response({
- "status": "401",
- "message": "Secret Key is Invalid, the Key is case-sensitive."
- }, status=401)
+ response = web.json_response({"message": "Secret Key is Invalid, the Key is case-sensitive."}, status=401)
else:
try:
- response = await handler(request) # type: ignore[assignment]
+ response = await handler(request)
except web.HTTPException as e:
request.app.logger.error(f"An unexpected error has occurred, {e}")
- response = web.json_response({
- "status": 500,
- "message": e.reason
- }, status=500)
+ response = web.json_response({"message": e.reason}, status=500)
response.headers.update({
- "Server": f"https://github.com/ready-dl/pyplayready serve v{__version__}"
+ "Server": f"https://git.gay/ready-dl/pyplayready serve v{__version__}"
})
return response
diff --git a/scripts/pyplayready/pyplayready/system/bcert.py b/scripts/pyplayready/pyplayready/system/bcert.py
index f6a1b2c..293cce7 100644
--- a/scripts/pyplayready/pyplayready/system/bcert.py
+++ b/scripts/pyplayready/pyplayready/system/bcert.py
@@ -1,28 +1,131 @@
from __future__ import annotations
import collections.abc
-from Crypto.PublicKey import ECC
-
-from pyplayready.crypto import Crypto
-from pyplayready.exceptions import InvalidCertificateChain, InvalidCertificate
-
# monkey patch for construct 2.8.8 compatibility
if not hasattr(collections, 'Sequence'):
collections.Sequence = collections.abc.Sequence
import base64
from pathlib import Path
-from typing import Union
+from typing import Union, Optional
+from enum import IntEnum
+
+from Crypto.PublicKey import ECC
from construct import Bytes, Const, Int32ub, GreedyRange, Switch, Container, ListContainer
from construct import Int16ub, Array
from construct import Struct, this
+from pyplayready.crypto import Crypto
+from pyplayready.exceptions import InvalidCertificateChain, InvalidCertificate
from pyplayready.crypto.ecc_key import ECCKey
+class BCertCertType(IntEnum):
+ UNKNOWN = 0x00000000
+ PC = 0x00000001
+ DEVICE = 0x00000002
+ DOMAIN = 0x00000003
+ ISSUER = 0x00000004
+ CRL_SIGNER = 0x00000005
+ SERVICE = 0x00000006
+ SILVERLIGHT = 0x00000007
+ APPLICATION = 0x00000008
+ METERING = 0x00000009
+ KEYFILESIGNER = 0x0000000a
+ SERVER = 0x0000000b
+ LICENSESIGNER = 0x0000000c
+ SECURETIMESERVER = 0x0000000d
+ RPROVMODELAUTH = 0x0000000e
+
+
+class BCertObjType(IntEnum):
+ BASIC = 0x0001
+ DOMAIN = 0x0002
+ PC = 0x0003
+ DEVICE = 0x0004
+ FEATURE = 0x0005
+ KEY = 0x0006
+ MANUFACTURER = 0x0007
+ SIGNATURE = 0x0008
+ SILVERLIGHT = 0x0009
+ METERING = 0x000A
+ EXTDATASIGNKEY = 0x000B
+ EXTDATACONTAINER = 0x000C
+ EXTDATASIGNATURE = 0x000D
+ EXTDATA_HWID = 0x000E
+ SERVER = 0x000F
+ SECURITY_VERSION = 0x0010
+ SECURITY_VERSION_2 = 0x0011
+ UNKNOWN_OBJECT_ID = 0xFFFD
+
+
+class BCertFlag(IntEnum):
+ EMPTY = 0x00000000
+ EXTDATA_PRESENT = 0x00000001
+
+
+class BCertObjFlag(IntEnum):
+ EMPTY = 0x0000
+ MUST_UNDERSTAND = 0x0001
+ CONTAINER_OBJ = 0x0002
+
+
+class BCertSignatureType(IntEnum):
+ P256 = 0x0001
+
+
+class BCertKeyType(IntEnum):
+ ECC256 = 0x0001
+
+
+class BCertKeyUsage(IntEnum):
+ UNKNOWN = 0x00000000
+ SIGN = 0x00000001
+ ENCRYPT_KEY = 0x00000002
+ SIGN_CRL = 0x00000003
+ ISSUER_ALL = 0x00000004
+ ISSUER_INDIV = 0x00000005
+ ISSUER_DEVICE = 0x00000006
+ ISSUER_LINK = 0x00000007
+ ISSUER_DOMAIN = 0x00000008
+ ISSUER_SILVERLIGHT = 0x00000009
+ ISSUER_APPLICATION = 0x0000000a
+ ISSUER_CRL = 0x0000000b
+ ISSUER_METERING = 0x0000000c
+ ISSUER_SIGN_KEYFILE = 0x0000000d
+ SIGN_KEYFILE = 0x0000000e
+ ISSUER_SERVER = 0x0000000f
+ ENCRYPTKEY_SAMPLE_PROTECTION_RC4 = 0x00000010
+ RESERVED2 = 0x00000011
+ ISSUER_SIGN_LICENSE = 0x00000012
+ SIGN_LICENSE = 0x00000013
+ SIGN_RESPONSE = 0x00000014
+ PRND_ENCRYPT_KEY_DEPRECATED = 0x00000015
+ ENCRYPTKEY_SAMPLE_PROTECTION_AES128CTR = 0x00000016
+ ISSUER_SECURETIMESERVER = 0x00000017
+ ISSUER_RPROVMODELAUTH = 0x00000018
+
+
+class BCertFeatures(IntEnum):
+ TRANSMITTER = 0x00000001
+ RECEIVER = 0x00000002
+ SHARED_CERTIFICATE = 0x00000003
+ SECURE_CLOCK = 0x00000004
+ ANTIROLLBACK_CLOCK = 0x00000005
+ RESERVED_METERING = 0x00000006
+ RESERVED_LICSYNC = 0x00000007
+ RESERVED_SYMOPT = 0x00000008
+ SUPPORTS_CRLS = 0x00000009
+ SERVER_BASIC_EDITION = 0x0000000A
+ SERVER_STANDARD_EDITION = 0x0000000B
+ SERVER_PREMIUM_EDITION = 0x0000000C
+ SUPPORTS_PR3_FEATURES = 0x0000000D
+ DEPRECATED_SECURE_STOP = 0x0000000E
+
+
class _BCertStructs:
- DrmBCertBasicInfo = Struct(
+ BasicInfo = Struct(
"cert_id" / Bytes(16),
"security_level" / Int32ub,
"flags" / Int32ub,
@@ -33,7 +136,7 @@ class _BCertStructs:
)
# TODO: untested
- DrmBCertDomainInfo = Struct(
+ DomainInfo = Struct(
"service_id" / Bytes(16),
"account_id" / Bytes(16),
"revision_timestamp" / Int32ub,
@@ -42,23 +145,22 @@ class _BCertStructs:
)
# TODO: untested
- DrmBCertPCInfo = Struct(
+ PCInfo = Struct(
"security_version" / Int32ub
)
- # TODO: untested
- DrmBCertDeviceInfo = Struct(
+ DeviceInfo = Struct(
"max_license" / Int32ub,
"max_header" / Int32ub,
"max_chain_depth" / Int32ub
)
- DrmBCertFeatureInfo = Struct(
+ FeatureInfo = Struct(
"feature_count" / Int32ub, # max. 32
"features" / Array(this.feature_count, Int32ub)
)
- DrmBCertKeyInfo = Struct(
+ KeyInfo = Struct(
"key_count" / Int32ub,
"cert_keys" / Array(this.key_count, Struct(
"type" / Int16ub,
@@ -70,7 +172,7 @@ class _BCertStructs:
))
)
- DrmBCertManufacturerInfo = Struct(
+ ManufacturerInfo = Struct(
"flags" / Int32ub,
"manufacturer_name_length" / Int32ub,
"manufacturer_name" / Bytes((this.manufacturer_name_length + 3) & 0xfffffffc),
@@ -80,7 +182,7 @@ class _BCertStructs:
"model_number" / Bytes((this.model_number_length + 3) & 0xfffffffc),
)
- DrmBCertSignatureInfo = Struct(
+ SignatureInfo = Struct(
"signature_type" / Int16ub,
"signature_size" / Int16ub,
"signature" / Bytes(this.signature_size),
@@ -89,20 +191,19 @@ class _BCertStructs:
)
# TODO: untested
- DrmBCertSilverlightInfo = Struct(
+ SilverlightInfo = Struct(
"security_version" / Int32ub,
"platform_identifier" / Int32ub
)
# TODO: untested
- DrmBCertMeteringInfo = Struct(
+ MeteringInfo = Struct(
"metering_id" / Bytes(16),
"metering_url_length" / Int32ub,
"metering_url" / Bytes((this.metering_url_length + 3) & 0xfffffffc)
)
- # TODO: untested
- DrmBCertExtDataSignKeyInfo = Struct(
+ ExtDataSignKeyInfo = Struct(
"key_type" / Int16ub,
"key_length" / Int16ub,
"flags" / Int32ub,
@@ -110,32 +211,30 @@ class _BCertStructs:
)
# TODO: untested
- BCertExtDataRecord = Struct(
+ DataRecord = Struct(
"data_size" / Int32ub,
"data" / Bytes(this.data_size)
)
- # TODO: untested
- DrmBCertExtDataSignature = Struct(
+ ExtDataSignature = Struct(
"signature_type" / Int16ub,
"signature_size" / Int16ub,
"signature" / Bytes(this.signature_size)
)
- # TODO: untested
- BCertExtDataContainer = Struct(
+ ExtDataContainer = Struct(
"record_count" / Int32ub, # always 1
- "records" / Array(this.record_count, BCertExtDataRecord),
- "signature" / DrmBCertExtDataSignature
+ "records" / Array(this.record_count, DataRecord),
+ "signature" / ExtDataSignature
)
# TODO: untested
- DrmBCertServerInfo = Struct(
+ ServerInfo = Struct(
"warning_days" / Int32ub
)
# TODO: untested
- DrmBcertSecurityVersion = Struct(
+ SecurityVersion = Struct(
"security_version" / Int32ub,
"platform_identifier" / Int32ub
)
@@ -147,23 +246,23 @@ class _BCertStructs:
"attribute" / Switch(
lambda this_: this_.tag,
{
- 1: DrmBCertBasicInfo,
- 2: DrmBCertDomainInfo,
- 3: DrmBCertPCInfo,
- 4: DrmBCertDeviceInfo,
- 5: DrmBCertFeatureInfo,
- 6: DrmBCertKeyInfo,
- 7: DrmBCertManufacturerInfo,
- 8: DrmBCertSignatureInfo,
- 9: DrmBCertSilverlightInfo,
- 10: DrmBCertMeteringInfo,
- 11: DrmBCertExtDataSignKeyInfo,
- 12: BCertExtDataContainer,
- 13: DrmBCertExtDataSignature,
- 14: Bytes(this.length - 8),
- 15: DrmBCertServerInfo,
- 16: DrmBcertSecurityVersion,
- 17: DrmBcertSecurityVersion
+ BCertObjType.BASIC: BasicInfo,
+ BCertObjType.DOMAIN: DomainInfo,
+ BCertObjType.PC: PCInfo,
+ BCertObjType.DEVICE: DeviceInfo,
+ BCertObjType.FEATURE: FeatureInfo,
+ BCertObjType.KEY: KeyInfo,
+ BCertObjType.MANUFACTURER: ManufacturerInfo,
+ BCertObjType.SIGNATURE: SignatureInfo,
+ BCertObjType.SILVERLIGHT: SilverlightInfo,
+ BCertObjType.METERING: MeteringInfo,
+ BCertObjType.EXTDATASIGNKEY: ExtDataSignKeyInfo,
+ BCertObjType.EXTDATACONTAINER: ExtDataContainer,
+ BCertObjType.EXTDATASIGNATURE: ExtDataSignature,
+ BCertObjType.EXTDATA_HWID: Bytes(this.length - 8),
+ BCertObjType.SERVER: ServerInfo,
+ BCertObjType.SECURITY_VERSION: SecurityVersion,
+ BCertObjType.SECURITY_VERSION_2: SecurityVersion
},
default=Bytes(this.length - 8)
)
@@ -213,16 +312,16 @@ class Certificate(_BCertStructs):
basic_info = Container(
cert_id=cert_id,
security_level=security_level,
- flags=0,
- cert_type=2,
+ flags=BCertFlag.EMPTY,
+ cert_type=BCertCertType.DEVICE,
public_key_digest=signing_key.public_sha256_digest(),
expiration_date=expiry,
client_id=client_id
)
basic_info_attribute = Container(
- flags=1,
- tag=1,
- length=len(_BCertStructs.DrmBCertBasicInfo.build(basic_info)) + 8,
+ flags=BCertObjFlag.MUST_UNDERSTAND,
+ tag=BCertObjType.BASIC,
+ length=len(_BCertStructs.BasicInfo.build(basic_info)) + 8,
attribute=basic_info
)
@@ -232,58 +331,51 @@ class Certificate(_BCertStructs):
max_chain_depth=2
)
device_info_attribute = Container(
- flags=1,
- tag=4,
- length=len(_BCertStructs.DrmBCertDeviceInfo.build(device_info)) + 8,
+ flags=BCertObjFlag.MUST_UNDERSTAND,
+ tag=BCertObjType.DEVICE,
+ length=len(_BCertStructs.DeviceInfo.build(device_info)) + 8,
attribute=device_info
)
feature = Container(
feature_count=3,
features=ListContainer([
- # 1, # Transmitter
- # 2, # Receiver
- # 3, # SharedCertificate
- 4, # SecureClock
- # 5, # AntiRollBackClock
- # 6, # ReservedMetering
- # 7, # ReservedLicSync
- # 8, # ReservedSymOpt
- 9, # CRLS (Revocation Lists)
- # 10, # ServerBasicEdition
- # 11, # ServerStandardEdition
- # 12, # ServerPremiumEdition
- 13, # PlayReady3Features
- # 14, # DeprecatedSecureStop
+ BCertFeatures.SECURE_CLOCK,
+ BCertFeatures.SUPPORTS_CRLS,
+ BCertFeatures.SUPPORTS_PR3_FEATURES
])
)
feature_attribute = Container(
- flags=1,
- tag=5,
- length=len(_BCertStructs.DrmBCertFeatureInfo.build(feature)) + 8,
+ flags=BCertObjFlag.MUST_UNDERSTAND,
+ tag=BCertObjType.FEATURE,
+ length=len(_BCertStructs.FeatureInfo.build(feature)) + 8,
attribute=feature
)
+ signing_key_public_bytes = signing_key.public_bytes()
cert_key_sign = Container(
- type=1,
- length=512, # bits
- flags=0,
- key=signing_key.public_bytes(),
+ type=BCertKeyType.ECC256,
+ length=len(signing_key_public_bytes) * 8, # bits
+ flags=BCertFlag.EMPTY,
+ key=signing_key_public_bytes,
usages_count=1,
usages=ListContainer([
- 1 # KEYUSAGE_SIGN
+ BCertKeyUsage.SIGN
])
)
+
+ encryption_key_public_bytes = encryption_key.public_bytes()
cert_key_encrypt = Container(
- type=1,
- length=512, # bits
- flags=0,
- key=encryption_key.public_bytes(),
+ type=BCertKeyType.ECC256,
+ length=len(encryption_key_public_bytes) * 8, # bits
+ flags=BCertFlag.EMPTY,
+ key=encryption_key_public_bytes,
usages_count=1,
usages=ListContainer([
- 2 # KEYUSAGE_ENCRYPT_KEY
+ BCertKeyUsage.ENCRYPT_KEY
])
)
+
key_info = Container(
key_count=2,
cert_keys=ListContainer([
@@ -292,13 +384,13 @@ class Certificate(_BCertStructs):
])
)
key_info_attribute = Container(
- flags=1,
- tag=6,
- length=len(_BCertStructs.DrmBCertKeyInfo.build(key_info)) + 8,
+ flags=BCertObjFlag.MUST_UNDERSTAND,
+ tag=BCertObjType.KEY,
+ length=len(_BCertStructs.KeyInfo.build(key_info)) + 8,
attribute=key_info
)
- manufacturer_info = parent.get(0).get_attribute(7)
+ manufacturer_info = parent.get(0).get_attribute(BCertObjType.MANUFACTURER)
new_bcert_container = Container(
signature=b"CERT",
@@ -321,17 +413,19 @@ class Certificate(_BCertStructs):
sign_payload = _BCertStructs.BCert.build(new_bcert_container)
signature = Crypto.ecc256_sign(group_key, sign_payload)
+ group_key_public_bytes = group_key.public_bytes()
+
signature_info = Container(
- signature_type=1,
- signature_size=64,
+ signature_type=BCertSignatureType.P256,
+ signature_size=len(signature),
signature=signature,
- signature_key_size=512, # bits
- signature_key=group_key.public_bytes()
+ signature_key_size=len(group_key_public_bytes) * 8, # bits
+ signature_key=group_key_public_bytes
)
signature_info_attribute = Container(
- flags=1,
- tag=8,
- length=len(_BCertStructs.DrmBCertSignatureInfo.build(signature_info)) + 8,
+ flags=BCertObjFlag.MUST_UNDERSTAND,
+ tag=BCertObjType.SIGNATURE,
+ length=len(_BCertStructs.SignatureInfo.build(signature_info)) + 8,
attribute=signature_info
)
new_bcert_container.attributes.append(signature_info_attribute)
@@ -357,7 +451,7 @@ class Certificate(_BCertStructs):
return attribute
def get_security_level(self) -> int:
- basic_info_attribute = self.get_attribute(1).attribute
+ basic_info_attribute = self.get_attribute(BCertObjType.BASIC).attribute
if basic_info_attribute:
return basic_info_attribute.security_level
@@ -366,12 +460,12 @@ class Certificate(_BCertStructs):
return name.rstrip(b'\x00').decode("utf-8", errors="ignore")
def get_name(self):
- manufacturer_info = self.get_attribute(7).attribute
+ manufacturer_info = self.get_attribute(BCertObjType.MANUFACTURER).attribute
if manufacturer_info:
return f"{self._unpad(manufacturer_info.manufacturer_name)} {self._unpad(manufacturer_info.model_name)} {self._unpad(manufacturer_info.model_number)}"
- def get_issuer_key(self) -> Union[bytes, None]:
- key_info_object = self.get_attribute(6)
+ def get_issuer_key(self) -> Optional[bytes]:
+ key_info_object = self.get_attribute(BCertObjType.KEY)
if not key_info_object:
return
@@ -381,11 +475,8 @@ class Certificate(_BCertStructs):
def dumps(self) -> bytes:
return self._BCERT.build(self.parsed)
- def struct(self) -> _BCertStructs.BCert:
- return self._BCERT
-
def verify(self, public_key: bytes, index: int):
- signature_object = self.get_attribute(8)
+ signature_object = self.get_attribute(BCertObjType.SIGNATURE)
if not signature_object:
raise InvalidCertificate(f"No signature object found in certificate {index}")
@@ -449,9 +540,6 @@ class CertificateChain(_BCertStructs):
def dumps(self) -> bytes:
return self._BCERT_CHAIN.build(self.parsed)
- def struct(self) -> _BCertStructs.BCertChain:
- return self._BCERT_CHAIN
-
def get_security_level(self) -> int:
# not sure if there's a better way than this
return self.get(0).get_security_level()
diff --git a/scripts/pyplayready/pyplayready/system/pssh.py b/scripts/pyplayready/pyplayready/system/pssh.py
index 16f6f12..760c01b 100644
--- a/scripts/pyplayready/pyplayready/system/pssh.py
+++ b/scripts/pyplayready/pyplayready/system/pssh.py
@@ -2,7 +2,8 @@ import base64
from typing import Union, List
from uuid import UUID
-from construct import Struct, Int32ul, Int16ul, Array, this, Bytes, Switch, Int32ub, Const, Container, ConstructError
+from construct import Struct, Int32ul, Int16ul, this, Bytes, Switch, Int8ub, Int24ub, Int32ub, Const, Container, \
+ ConstructError, Rebuild, Default, If, PrefixedArray, Prefixed, GreedyBytes
from pyplayready.exceptions import InvalidPssh
from pyplayready.system.wrmheader import WRMHeader
@@ -12,10 +13,11 @@ class _PlayreadyPSSHStructs:
PSSHBox = Struct(
"length" / Int32ub,
"pssh" / Const(b"pssh"),
- "fullbox" / Int32ub,
+ "version" / Rebuild(Int8ub, lambda ctx: 1 if (hasattr(ctx, "key_ids") and ctx.key_ids) else 0),
+ "flags" / Const(Int24ub, 0),
"system_id" / Bytes(16),
- "data_length" / Int32ub,
- "data" / Bytes(this.data_length)
+ "key_ids" / Default(If(this.version == 1, PrefixedArray(Int32ub, Bytes(16))), None),
+ "data" / Prefixed(Int32ub, GreedyBytes)
)
PlayreadyObject = Struct(
@@ -32,8 +34,7 @@ class _PlayreadyPSSHStructs:
PlayreadyHeader = Struct(
"length" / Int32ul,
- "record_count" / Int16ul,
- "records" / Array(this.record_count, PlayreadyObject)
+ "records" / PrefixedArray(Int16ul, PlayreadyObject)
)
@@ -58,8 +59,11 @@ class PSSH(_PlayreadyPSSHStructs):
try:
# PSSH Box -> PlayReady Header
box = self.PSSHBox.parse(data)
- prh = self.PlayreadyHeader.parse(box.data)
- self.wrm_headers = self._read_playready_objects(prh)
+ if self._is_utf_16_le(box.data):
+ self.wrm_headers = [WRMHeader(box.data)]
+ else:
+ prh = self.PlayreadyHeader.parse(box.data)
+ self.wrm_headers = self._read_playready_objects(prh)
except ConstructError:
if int.from_bytes(data[:2], byteorder="little") > 3:
try:
@@ -76,6 +80,23 @@ class PSSH(_PlayreadyPSSHStructs):
except ConstructError:
raise InvalidPssh("Could not parse data as a PSSH Box nor a PlayReady Object")
+
+ @staticmethod
+ def _is_utf_16_le(data: bytes) -> bool:
+ if len(data) % 2 != 0:
+ return False
+
+ try:
+ decoded = data.decode('utf-16-le')
+ except UnicodeDecodeError:
+ return False
+
+ for char in decoded:
+ if not (0x20 <= ord(char) <= 0x7E):
+ return False
+
+ return True
+
@staticmethod
def _read_playready_objects(header: Container) -> List[WRMHeader]:
return list(map(
@@ -85,12 +106,3 @@ class PSSH(_PlayreadyPSSHStructs):
header.records
)
))
-
- def get_wrm_headers(self) -> List[str]:
- """
- Return a list of all WRM Headers in the PSSH as plaintext strings
- """
- return list(map(
- lambda wrm_header: wrm_header.dumps(),
- self.wrm_headers
- ))
diff --git a/scripts/pyplayready/pyplayready/system/session.py b/scripts/pyplayready/pyplayready/system/session.py
index f6be649..59f0570 100644
--- a/scripts/pyplayready/pyplayready/system/session.py
+++ b/scripts/pyplayready/pyplayready/system/session.py
@@ -1,3 +1,5 @@
+from typing import Optional
+
from Crypto.Random import get_random_bytes
from pyplayready.license.key import Key
@@ -10,8 +12,8 @@ class Session:
self.number = number
self.id = get_random_bytes(16)
self.xml_key = XmlKey()
- self.signing_key: ECCKey = None
- self.encryption_key: ECCKey = None
+ self.signing_key: Optional[ECCKey] = None
+ self.encryption_key: Optional[ECCKey] = None
self.keys: list[Key] = []
diff --git a/scripts/pyplayready/pyplayready/system/wrmheader.py b/scripts/pyplayready/pyplayready/system/wrmheader.py
index 6d70d66..292c0a3 100644
--- a/scripts/pyplayready/pyplayready/system/wrmheader.py
+++ b/scripts/pyplayready/pyplayready/system/wrmheader.py
@@ -1,5 +1,4 @@
import base64
-import binascii
from enum import Enum
from typing import Optional, List, Union, Tuple
@@ -34,7 +33,7 @@ class WRMHeader:
def _missing_(cls, value):
return cls.UNKNOWN
- _RETURN_STRUCTURE = Tuple[List[SignedKeyID], Union[str, None], Union[str, None], Union[str, None]]
+ _RETURN_STRUCTURE = Tuple[List[SignedKeyID], Optional[str], Optional[str], Optional[str]]
def __init__(self, data: Union[str, bytes]):
"""Load a WRM Header from either a string, base64 encoded data or bytes"""
@@ -45,8 +44,8 @@ class WRMHeader:
if isinstance(data, str):
try:
data = base64.b64decode(data).decode()
- except (binascii.Error, binascii.Incomplete):
- data = data.encode()
+ except Exception:
+ data = data.encode("utf-16-le")
self._raw_data: bytes = data
self._parsed = xmltodict.parse(self._raw_data)
@@ -92,12 +91,7 @@ class WRMHeader:
checksum=kid.get("@CHECKSUM")
)]
- return (
- key_ids,
- data.get("LA_URL"),
- data.get("LUI_URL"),
- data.get("DS_ID")
- )
+ return key_ids, data.get("LA_URL"), data.get("LUI_URL"), data.get("DS_ID")
@staticmethod
def _read_v4_2_0_0(data: dict) -> _RETURN_STRUCTURE:
@@ -114,12 +108,7 @@ class WRMHeader:
checksum=kid.get("@CHECKSUM")
))
- return (
- key_ids,
- data.get("LA_URL"),
- data.get("LUI_URL"),
- data.get("DS_ID")
- )
+ return key_ids, data.get("LA_URL"), data.get("LUI_URL"), data.get("DS_ID")
@staticmethod
def _read_v4_3_0_0(data: dict) -> _RETURN_STRUCTURE:
@@ -135,19 +124,10 @@ class WRMHeader:
checksum=kid.get("@CHECKSUM")
))
- return (
- key_ids,
- data.get("LA_URL"),
- data.get("LUI_URL"),
- data.get("DS_ID")
- )
+ return key_ids, data.get("LA_URL"), data.get("LUI_URL"), data.get("DS_ID")
def read_attributes(self) -> _RETURN_STRUCTURE:
- """
- Read any non-custom XML attributes
-
- Returns a tuple structured like this: Tuple[List[SignedKeyID], , , ]
- """
+ """Read any non-custom XML attributes"""
data = self._header.get("DATA")
if not data:
raise ValueError("Not a valid PlayReady Header Record, WRMHEADER/DATA required")
diff --git a/scripts/pyplayready/pyproject.toml b/scripts/pyplayready/pyproject.toml
index 990c7ee..eae9b53 100644
--- a/scripts/pyplayready/pyproject.toml
+++ b/scripts/pyplayready/pyproject.toml
@@ -4,12 +4,12 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "pyplayready"
-version = "0.5.0"
+version = "0.6.0"
description = "pyplayready CDM (Content Decryption Module) implementation in Python."
license = "CC BY-NC-ND 4.0"
authors = ["DevLARLEY, Erevoc", "DevataDev"]
readme = "README.md"
-repository = "https://github.com/ready-dl/pyplayready"
+repository = "https://git.gay/ready-dl/pyplayready"
keywords = ["python", "drm", "playready", "microsoft"]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -27,13 +27,13 @@ include = [
]
[tool.poetry.urls]
-"Issues" = "https://github.com/ready-dl/pyplayready/issues"
+"Issues" = "https://git.gay/ready-dl/pyplayready/issues"
[tool.poetry.dependencies]
python = ">=3.8,<4.0"
requests = "^2.32.3"
pycryptodome = "^3.21.0"
-construct = "^2.8.8"
+construct = "2.8.8"
ECPy = "^1.2.5"
click = "^8.1.7"
xmltodict = "^0.14.2"
diff --git a/scripts/pyplayready/requirements.txt b/scripts/pyplayready/requirements.txt
new file mode 100644
index 0000000..c543673
--- /dev/null
+++ b/scripts/pyplayready/requirements.txt
@@ -0,0 +1,8 @@
+requests
+pycryptodome
+ecpy
+construct==2.8.8
+click
+PyYAML
+aiohttp
+xmltodict
\ No newline at end of file
diff --git a/scripts/pyplayready/serve.example.yml b/scripts/pyplayready/serve.example.yml
new file mode 100644
index 0000000..ef6f285
--- /dev/null
+++ b/scripts/pyplayready/serve.example.yml
@@ -0,0 +1,15 @@
+# This data serves as an example configuration file for the `serve` command.
+# None of the sensitive data should be re-used.
+
+# List of Playready Device (.prd) file paths to use with serve.
+# Note: Each individual user needs explicit permission to use a device listed.
+devices:
+ - 'C:\Users\ready-dl\Documents\PRDs\test_device_001.prd'
+
+# List of User's by Secret Key. The Secret Key must be supplied by the User to use the API.
+users:
+ fx206W0FaB2O34HzGsgb8rcDe9e3ijsf: # secret key, a-zA-Z-09{32} is recommended, case-sensitive
+ username: chloe # only for internal logging, user will not see this name
+ devices: # list of allowed devices by filename
+ - test_device_001
+ # ...
\ No newline at end of file
diff --git a/vinetrimmer/commands/dl.py b/vinetrimmer/commands/dl.py
index dbfa404..a50be7f 100644
--- a/vinetrimmer/commands/dl.py
+++ b/vinetrimmer/commands/dl.py
@@ -40,42 +40,42 @@ from Crypto.Random import get_random_bytes
def reprovision_device(prd_path) -> None:
- """
- Reprovision a Playready Device (.prd) by creating a new leaf certificate and new encryption/signing keys.
- Will override the device if an output path or directory is not specified
+ """
+ Reprovision a Playready Device (.prd) by creating a new leaf certificate and new encryption/signing keys.
+ Will override the device if an output path or directory is not specified
- Only works on PRD Devices of v3 or higher
- """
- prd_path = Path(prd_path)
- if not prd_path.is_file():
- raise Exception("prd_path: Not a path to a file, or it doesn't exist.")
+ Only works on PRD Devices of v3 or higher
+ """
+ prd_path = Path(prd_path)
+ if not prd_path.is_file():
+ raise Exception("prd_path: Not a path to a file, or it doesn't exist.")
- device = DevicePR.load(prd_path)
+ device = DevicePR.load(prd_path)
- if device.group_key is None:
- raise Exception("Device does not support reprovisioning, re-create it or use a Device with a version of 3 or higher")
+ if device.group_key is None:
+ raise Exception("Device does not support reprovisioning, re-create it or use a Device with a version of 3 or higher")
- device.group_certificate.remove(0)
+ device.group_certificate.remove(0)
- encryption_key = ECCKey.generate()
- signing_key = ECCKey.generate()
+ encryption_key = ECCKey.generate()
+ signing_key = ECCKey.generate()
- device.encryption_key = encryption_key
- device.signing_key = signing_key
+ device.encryption_key = encryption_key
+ device.signing_key = signing_key
- new_certificate = Certificate.new_leaf_cert(
- cert_id=get_random_bytes(16),
- security_level=device.group_certificate.get_security_level(),
- client_id=get_random_bytes(16),
- signing_key=signing_key,
- encryption_key=encryption_key,
- group_key=device.group_key,
- parent=device.group_certificate
- )
- device.group_certificate.prepend(new_certificate)
+ new_certificate = Certificate.new_leaf_cert(
+ cert_id=get_random_bytes(16),
+ security_level=device.group_certificate.get_security_level(),
+ client_id=get_random_bytes(16),
+ signing_key=signing_key,
+ encryption_key=encryption_key,
+ group_key=device.group_key,
+ parent=device.group_certificate
+ )
+ device.group_certificate.prepend(new_certificate)
- prd_path.parent.mkdir(parents=True, exist_ok=True)
- prd_path.write_bytes(device.dumps())
+ prd_path.parent.mkdir(parents=True, exist_ok=True)
+ prd_path.write_bytes(device.dumps())
def get_cdm(log, service, profile=None, cdm_name=None):
@@ -128,8 +128,7 @@ def get_service_config(service):
"""Get both service config and service secrets as one merged dictionary."""
service_config = load_yaml(filenames.service_config.format(service=service.lower()))
- user_config = (load_yaml(filenames.user_service_config.format(service=service.lower()))
- or load_yaml(filenames.user_service_config.format(service=service)))
+ user_config = (load_yaml(filenames.user_service_config.format(service=service.lower())) or load_yaml(filenames.user_service_config.format(service=service)))
if user_config:
merge_dict(service_config, user_config)
@@ -212,7 +211,7 @@ def get_credentials(service, profile="default"):
@click.option("-w", "--wanted", callback=wanted_param, default=None,
help="Wanted episodes, e.g. `S01-S05,S07`, `S01E01-S02E03`, `S02-S02E03`, e.t.c, defaults to all.")
@click.option("-le", "--latest-episode", is_flag=True, default=False,
- help="Download the latest episode on episodes list.")
+ help="Download the latest episode on episodes list.")
@click.option("-al", "--alang", callback=language_param, default="orig",
help="Language wanted for audio.")
@click.option("-sl", "--slang", callback=language_param, default="all",
@@ -345,7 +344,7 @@ def result(ctx, service, quality, range_, wanted, alang, slang, audio_only, subs
titles.print()
if latest_episode:
- titles = Titles(as_list(titles[-1]))
+ titles = Titles(as_list(titles[-1]))
for title in titles.with_wanted(wanted):
if title.type == Title.Types.TV:
@@ -446,7 +445,12 @@ def result(ctx, service, quality, range_, wanted, alang, slang, audio_only, subs
if track.encrypted:
if not track.get_pssh(service.session):
raise log.exit(" - Failed to get PSSH")
- log.info(f" + PSSH: {track.pssh}")
+
+ if track.psshPR:
+ log.info(f" + PSSH (PR): {track.psshPR}")
+ if track.psshWV:
+ log.info(f" + PSSH (WV): {track.psshWV}")
+
if not track.get_kid(service.session):
raise log.exit(" - Failed to get KID")
log.info(f" + KID: {track.kid}")
@@ -481,9 +485,11 @@ def result(ctx, service, quality, range_, wanted, alang, slang, audio_only, subs
if cache:
skip_title = True
break
- if "common_privacy_cert" in dir(ctx.obj.cdm):
- session_id = ctx.obj.cdm.open()
- log.info(f"CDM Session ID - {session_id.hex()}")
+
+ session_id = ctx.obj.cdm.open()
+ log.info(f"CDM Session ID - {session_id.hex()}")
+
+ if "common_privacy_cert" in dir(ctx.obj.cdm) and track.psshWV:
ctx.obj.cdm.set_service_certificate(
session_id,
service.certificate(
@@ -496,30 +502,24 @@ def result(ctx, service, quality, range_, wanted, alang, slang, audio_only, subs
ctx.obj.cdm.parse_license(
session_id,
service.license(
- challenge=ctx.obj.cdm.get_license_challenge(session_id=session_id, pssh=PSSHWV(track.pssh)),
+ challenge=ctx.obj.cdm.get_license_challenge(session_id=session_id, pssh=PSSHWV(track.psshWV)),
title=title,
track=track,
session_id=session_id
)
)
- else:
- session_id = ctx.obj.cdm.open()
- log.info(f"CDM Session ID - {session_id.hex()}")
- wrm_header = PSSH(track.pssh).get_wrm_headers()[0]
- challenge = ctx.obj.cdm.get_license_challenge(session_id, wrm_header)
- #log.info(f"{wrm_header} --> {challenge}")
- licence = service.license(
- challenge=challenge,
- title=title,
- track=track,
- )
- log.debug(licence)
+ elif "common_privacy_cert" not in dir(ctx.obj.cdm) and track.psshPR:
+ challenge = ctx.obj.cdm.get_license_challenge(session_id, PSSH(track.psshPR).wrm_headers[0])
ctx.obj.cdm.parse_license(
session_id,
- licence # expects the XML License not base64 encoded str.
+ service.license(
+ challenge=challenge,
+ title=title,
+ track=track,
+ ) # expects the XML License not base64 encoded str.
)
-
-
+ else:
+ raise log.exit("Unable to license")
content_keys = [
(str(x.kid).replace("-", ""), x.key.hex()) for x in ctx.obj.cdm.get_keys(session_id) if x.type == "CONTENT"
] if "common_privacy_cert" in dir(ctx.obj.cdm) else [
diff --git a/vinetrimmer/config/Services/amazon.yml b/vinetrimmer/config/Services/amazon.yml
index d7f7390..b658235 100644
--- a/vinetrimmer/config/Services/amazon.yml
+++ b/vinetrimmer/config/Services/amazon.yml
@@ -61,6 +61,10 @@ device:
device_name: '%FIRST_NAME%''s%DUPE_STRATEGY_1ST% Hisense'
device_serial: '3cc61028646759e273'
+#Hisense_HU32E5600FHWV: A2RGJ95OVLR12U
+#Hisense_HU50A6100UW: AAJ692ZPT1X85
+#Hisense_HE55A700EUWTS: A3REWRVYBYPKUM
+#MTC_ATV: A2HYAJ0FEWP6N3
device_types:
browser: 'AOAGZA014O5RE' # all browsers? all platforms?
diff --git a/vinetrimmer/devices/hisense_smarttv_he55a7000euwts_sl3000.prd b/vinetrimmer/devices/hisense_smarttv_he55a7000euwts_sl3000.prd
index f41fa8e..50da192 100644
Binary files a/vinetrimmer/devices/hisense_smarttv_he55a7000euwts_sl3000.prd and b/vinetrimmer/devices/hisense_smarttv_he55a7000euwts_sl3000.prd differ
diff --git a/vinetrimmer/devices/hisense_smarttv_hu32e5600fhwv_sl3000.prd b/vinetrimmer/devices/hisense_smarttv_hu32e5600fhwv_sl3000.prd
new file mode 100644
index 0000000..60b9792
Binary files /dev/null and b/vinetrimmer/devices/hisense_smarttv_hu32e5600fhwv_sl3000.prd differ
diff --git a/vinetrimmer/devices/hisense_smarttv_hu50a6100uw_sl3000.prd b/vinetrimmer/devices/hisense_smarttv_hu50a6100uw_sl3000.prd
new file mode 100644
index 0000000..7bd9fce
Binary files /dev/null and b/vinetrimmer/devices/hisense_smarttv_hu50a6100uw_sl3000.prd differ
diff --git a/vinetrimmer/devices/mtc_mtc_atv_atv_sl3000.prd b/vinetrimmer/devices/mtc_mtc_atv_atv_sl3000.prd
new file mode 100644
index 0000000..557239d
Binary files /dev/null and b/vinetrimmer/devices/mtc_mtc_atv_atv_sl3000.prd differ
diff --git a/vinetrimmer/key_store.db b/vinetrimmer/key_store.db
index f454e12..913e02e 100644
Binary files a/vinetrimmer/key_store.db and b/vinetrimmer/key_store.db differ
diff --git a/vinetrimmer/objects/tracks.py b/vinetrimmer/objects/tracks.py
index 61d6e72..9ca7419 100644
--- a/vinetrimmer/objects/tracks.py
+++ b/vinetrimmer/objects/tracks.py
@@ -11,6 +11,8 @@ import uuid
from collections import defaultdict
from enum import Enum
from io import BytesIO, TextIOWrapper
+from pathlib import Path
+
import humanfriendly
import m3u8
import pycaption
@@ -66,7 +68,7 @@ class Track:
ISM = 4 # https://bitmovin.com/blog/microsoft-smooth-streaming-mss/
def __init__(self, id_, source, url, codec, language=None, descriptor=Descriptor.URL,
- needs_proxy=False, needs_repack=False, encrypted=False, pssh=None, note=None, kid=None, key=None, extra=None):
+ needs_proxy=False, needs_repack=False, encrypted=False, psshWV=None, psshPR=None, note=None, kid=None, key=None, extra=None):
self.id = id_
self.source = source
self.url = url
@@ -82,7 +84,8 @@ class Track:
self.needs_repack = bool(needs_repack)
# decryption
self.encrypted = bool(encrypted)
- self.pssh = pssh
+ self.psshWV = psshWV
+ self.psshPR = psshPR
self.kid = kid
self.key = key
# extra data
@@ -160,7 +163,7 @@ class Track:
automatically.
"""
- if self.pssh or not self.encrypted:
+ if self.psshWV or self.psshPR or not self.encrypted:
return True
if self.descriptor == self.Descriptor.M3U:
@@ -168,30 +171,52 @@ class Track:
master = m3u8.loads(session.get(as_list(self.url)[0]).text, uri=self.url)
for x in master.session_keys:
if x and x.keyformat.lower == "com.microsoft.playready":
- self.pssh = x.uri.split(",")[-1]
+ self.psshPR = x.uri.split(",")[-1]
break
for x in master.keys:
if x and "com.microsoft.playready" in str(x):
- self.pssh = str(x).split("\"")[1].split(",")[-1]
+ self.psshPR = str(x).split("\"")[1].split(",")[-1]
break
-
- try:
- xml_str = base64.b64decode(self.pssh).decode("utf-16-le", "ignore")
- xml_str = xml_str[xml_str.index("<"):]
+ boxes.extend([
+ Box.parse(base64.b64decode(x.uri.split(",")[-1]))
+ for x in (master.session_keys or master.keys)
+ if x and x.keyformat.lower() == f"urn:uuid:{uuid.UUID('edef8ba979d64acea3c827dcd51d21ed')}"
+ ])
- xml = load_xml(xml_str).find("DATA") # root: WRMHEADER
+ data = self.get_data_chunk(session)
+ if data:
+ boxes.extend(list(get_boxes(data, b"pssh")))
- self.kid = xml.findtext("KID") # v4.0.0.0
- if not self.kid: # v4.1.0.0
- self.kid = next(iter(xml.xpath("PROTECTINFO/KID/@VALUE")), None)
- if not self.kid: # v4.3.0.0
- self.kid = next(iter(xml.xpath("PROTECTINFO/KIDS/KID/@VALUE")), None) # can be multiple?
+ for box in boxes:
+ if box.system_ID == uuid.UUID("edef8ba979d64acea3c827dcd51d21ed"):
+ self.psshWV = box
+ return True
- self.kid = uuid.UUID(base64.b64decode(self.kid).hex()).bytes_le.hex()
-
- return True
+ # Below converts PlayReady PSSH to WideVine PSSH
+ for box in boxes:
+ if box.system_ID == uuid.UUID("9a04f07998404286ab92e65be0885f95") and not self.PSSHWV:
+ xml_str = Box.build(box)
+ xml_str = xml_str.decode("utf-16-le", "ignore")
+ xml_str = xml_str[xml_str.index("<"):]
- except: pass
+ xml = load_xml(xml_str).find("DATA") # root: WRMHEADER
+
+ kid = xml.findtext("KID") # v4.0.0.0
+ if not kid: # v4.1.0.0
+ kid = next(iter(xml.xpath("PROTECTINFO/KID/@VALUE")), None)
+ if not kid: # v4.3.0.0
+ kid = next(iter(xml.xpath("PROTECTINFO/KIDS/KID/@VALUE")), None) # can be multiple?
+
+ self.kid = uuid.UUID(base64.b64decode(self.kid).hex()).bytes_le.hex()
+
+ self.psshWV = Box.parse(Box.build(dict(
+ type=b"pssh",
+ version=0,
+ flags=0,
+ system_ID="9a04f079-9840-4286-ab92-e65be0885f95",
+ init_data=b"\x12\x10" + base64.b64decode(kid)
+ )))
+ return True
# boxes = []
@@ -420,7 +445,14 @@ class Track:
proxy if self.needs_proxy else None
))
if self.__class__.__name__ == "AudioTrack":
- save_path = save_path.replace(".mp4", f".{str(self.language)[:2]}.m4a")
+ save_path_orig = save_path
+ save_path = save_path_orig.replace(".mp4", f".m4a")
+ if not Path(save_path).is_file():
+ save_path = save_path_orig.replace(".mp4", f".{str(self.language)[:2]}.m4a")
+ if not Path(save_path).is_file():
+ save_path = save_path_orig
+ if not Path(save_path).is_file():
+ raise
else:
asyncio.run(aria2c(
self.url,
diff --git a/vinetrimmer/parsers/ism.py b/vinetrimmer/parsers/ism.py
index 3ffb5b2..efb1515 100644
--- a/vinetrimmer/parsers/ism.py
+++ b/vinetrimmer/parsers/ism.py
@@ -90,7 +90,7 @@ def parse(*, url=None, data=None, source, session=None, downloader=None):
pr_pssh_dec = pr_pssh_dec[pr_pssh_dec.index('<'):]
pr_pssh_xml = xmltodict.parse(pr_pssh_dec)
kid_hex = base64.b64decode(pr_pssh_xml['WRMHEADER']['DATA']['KID']).hex()
- kid = uuid.UUID(kid_hex).bytes_le
+ kid = uuid.UUID(kid_hex).hex
stream_indices = ism['SmoothStreamingMedia']['StreamIndex']
@@ -212,7 +212,7 @@ def parse(*, url=None, data=None, source, session=None, downloader=None):
# decryption
needs_repack=True, # Necessary
encrypted=encrypted,
- pssh=pssh,
+ psshPR=pssh,
kid=kid,
# extra
extra=(list(quality_level), list(stream_info),) # Either set size as a attribute of VideoTrack or append to extra here.
diff --git a/vinetrimmer/parsers/m3u8.py b/vinetrimmer/parsers/m3u8.py
index 07cf8b6..1cbe7f0 100644
--- a/vinetrimmer/parsers/m3u8.py
+++ b/vinetrimmer/parsers/m3u8.py
@@ -37,7 +37,11 @@ def parse(master, source=None):
# uses master.data.session_keys instead of master.keys as master.keys is ONLY EXT-X-KEYS and
# doesn't include EXT-X-SESSION-KEYS which is whats used for variant playlist M3U8.
keys = [x.uri for x in master.session_keys if x.keyformat.lower() == "com.microsoft.playready"]
- pssh = keys[0].split(",")[-1] if keys else None
+ psshPR = keys[0].split(",")[-1] if keys else None
+
+
+ widevine_keys = [x.uri for x in master.session_keys if x.keyformat.lower() == "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"]
+ psshWV = widevine_keys[0].split(",")[-1] if widevine_keys else None
# if pssh:
# pssh = base64.b64decode(pssh)
# # noinspection PyBroadException
@@ -74,7 +78,8 @@ def parse(master, source=None):
descriptor=Track.Descriptor.M3U,
# decryption
encrypted=bool(master.keys or master.session_keys),
- pssh=pssh,
+ psshWV=psshWV,
+ psshPR=psshPR,
# extra
extra=x
) for x in master.playlists],
@@ -94,7 +99,8 @@ def parse(master, source=None):
descriptor=Track.Descriptor.M3U,
# decryption
encrypted=False, # don't know for sure if encrypted
- pssh=pssh,
+ psshWV=psshWV,
+ psshPR=psshPR,
# extra
extra=x
) for x in master.media if x.type == "AUDIO" and x.uri],
diff --git a/vinetrimmer/parsers/mpd.py b/vinetrimmer/parsers/mpd.py
index 115b7d5..819748a 100644
--- a/vinetrimmer/parsers/mpd.py
+++ b/vinetrimmer/parsers/mpd.py
@@ -134,7 +134,8 @@ def parse(*, url=None, data=None, source, session=None, downloader=None):
# content protection
protections = rep.findall("ContentProtection") + adaptation_set.findall("ContentProtection")
encrypted = bool(protections)
- pssh = None
+ psshWV = None
+ psshPR = None
kid = None
for protection in protections:
# For HMAX, the PSSH has multiple keys but the PlayReady ContentProtection tag
@@ -147,9 +148,12 @@ def parse(*, url=None, data=None, source, session=None, downloader=None):
kid = uuid.UUID(bytes_le=base64.b64decode(kid)).hex
else:
kid = uuid.UUID(kid).hex
- if (protection.get("schemeIdUri") or "").lower() not in ["urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95", "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"]:
+ if (protection.get("schemeIdUri") or "").lower() == "urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95":
+ psshPR = protection.findtext("pssh")
+ elif (protection.get("schemeIdUri") or "").lower() == "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed":
+ psshWV = protection.findtext("pssh")
+ else:
continue
- pssh = protection.findtext("pssh")
"""
if pssh and ((protection.get("schemeIdUri") or "").lower() == "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed3"):
@@ -310,7 +314,8 @@ def parse(*, url=None, data=None, source, session=None, downloader=None):
descriptor=Track.Descriptor.MPD,
# decryption
encrypted=encrypted,
- pssh=pssh,
+ psshWV=psshWV if psshWV else None,
+ psshPR=psshPR if psshPR else None,
kid=kid,
# extra
extra=(rep, adaptation_set)
@@ -336,8 +341,9 @@ def parse(*, url=None, data=None, source, session=None, downloader=None):
descriptor=Track.Descriptor.MPD,
# decryption
encrypted=encrypted,
- pssh=pssh,
- kid=kid,
+ psshWV=psshWV if psshWV else None,
+ psshPR=psshPR if psshPR else None,
+ kid=kid if kid else None,
# extra
extra=(rep, adaptation_set)
))
diff --git a/vinetrimmer/services/amazon.py b/vinetrimmer/services/amazon.py
index c5819ef..dcfe9c3 100644
--- a/vinetrimmer/services/amazon.py
+++ b/vinetrimmer/services/amazon.py
@@ -622,120 +622,83 @@ class Amazon(BaseService):
return self.config["certificate"]
def license(self, challenge: Union[bytes, str], title: Title, **_):
+ lic_list = []
lic_challenge = base64.b64encode(challenge).decode("utf-8") if isinstance(challenge, bytes) else base64.b64encode(challenge.encode("utf-8")).decode("utf-8")
self.log.debug(f"Challenge - {lic_challenge}")
-
- try:
- lic = self.session.post(
- url=self.endpoints["licence"],
- params={
- "asin": title.id,
- "consumptionType": "Streaming",
- "desiredResources": "PlayReadyLicense",
- "deviceTypeID": self.device["device_type"],
- "deviceID": self.device_id,
- "firmware": 1,
- "gascEnabled": str(self.pv).lower(),
- "marketplaceID": self.region["marketplace_id"],
- "resourceUsage": "ImmediateConsumption",
- "videoMaterialType": "Feature",
- "operatingSystemName": "Linux" if self.vquality == "SD" else "Windows",
- "operatingSystemVersion": "unknown" if self.vquality == "SD" else "10.0",
- "customerID": self.customer_id,
- "deviceDrmOverride": "Playready",
- "deviceStreamingTechnologyOverride": "SmoothStreaming",
- "deviceVideoQualityOverride": self.vquality,
- "deviceHdrFormatsOverride": self.VIDEO_RANGE_MAP.get(self.range, "None"),
- },
- headers={
- "Accept": "application/json",
- "Content-Type": "application/x-www-form-urlencoded",
- "Authorization": f"Bearer {self.device_token}"
- },
- data={
- "playReadyChallenge": lic_challenge, # expects base64
- "includeHdcpTestKeyInLicense": "true"
- }
- ).json()
- if "errorsByResource" in lic:
-
- error_code = lic["errorsByResource"]["PlayReadyLicense"]
- self.log.debug(error_code)
- if "errorCode" in error_code:
- error_code = error_code["errorCode"]
- elif "type" in error_code:
- error_code = error_code["type"]
- if error_code == "PRS.NoRights.AnonymizerIP":
- raise self.log.exit(" - Amazon detected a Proxy/VPN and refused to return a license!")
- message = lic["errorsByResource"]["PlayReadyLicense"]["message"]
- raise self.log.exit(f" - Amazon reported an error during the License request: {message} [{error_code}]")
- if "error" in lic:
- error_code = lic["error"]
- if "errorCode" in error_code:
- error_code = error_code["errorCode"]
- elif "type" in error_code:
- error_code = error_code["type"]
- if error_code == "PRS.NoRights.AnonymizerIP":
- raise self.log.exit(" - Amazon detected a Proxy/VPN and refused to return a license!")
- message = lic["error"]["message"]
- raise self.log.exit(f" - Amazon reported an error during the License request: {message} [{error_code}]")
- except:
- lic = self.session.post(
- url=self.endpoints["licence"],
- params={
- "asin": title.id,
- "consumptionType": "Streaming",
- "desiredResources": "PlayReadyLicense",
- "deviceTypeID": self.device["device_type"],
- "deviceID": self.device_id,
- "firmware": 1,
- "gascEnabled": str(self.pv).lower(),
- "marketplaceID": self.region["marketplace_id"],
- "resourceUsage": "ImmediateConsumption",
- "videoMaterialType": "Feature",
- "operatingSystemName": "Linux" if self.vquality == "SD" else "Windows",
- "operatingSystemVersion": "unknown" if self.vquality == "SD" else "10.0",
- "customerID": self.customer_id,
- "deviceDrmOverride": "Playready", #CENC or Playready
- "deviceStreamingTechnologyOverride": "DASH",
- "deviceVideoQualityOverride": self.vquality,
- "deviceHdrFormatsOverride": self.VIDEO_RANGE_MAP.get(self.range, "None"),
- },
- headers={
- "Accept": "application/json",
- "Content-Type": "application/x-www-form-urlencoded",
- "Authorization": f"Bearer {self.device_token}"
- },
- data={
- "playReadyChallenge": lic_challenge, # expects base64
- "includeHdcpTestKeyInLicense": "true"
- }
- ).json()
- if "errorsByResource" in lic:
-
- error_code = lic["errorsByResource"]["PlayReadyLicense"]
- self.log.debug(error_code)
- if "errorCode" in error_code:
- error_code = error_code["errorCode"]
- elif "type" in error_code:
- error_code = error_code["type"]
- if error_code == "PRS.NoRights.AnonymizerIP":
- raise self.log.exit(" - Amazon detected a Proxy/VPN and refused to return a license!")
- message = lic["errorsByResource"]["PlayReadyLicense"]["message"]
- raise self.log.exit(f" - Amazon reported an error during the License request: {message} [{error_code}]")
- if "error" in lic:
- error_code = lic["error"]
- if "errorCode" in error_code:
- error_code = error_code["errorCode"]
- elif "type" in error_code:
- error_code = error_code["type"]
- if error_code == "PRS.NoRights.AnonymizerIP":
- raise self.log.exit(" - Amazon detected a Proxy/VPN and refused to return a license!")
- message = lic["error"]["message"]
- raise self.log.exit(f" - Amazon reported an error during the License request: {message} [{error_code}]")
- #self.log.debug(lic["playReadyLicense"]["encodedLicenseResponse"])
+ params = {
+ "asin": title.id,
+ "consumptionType": "Streaming", # Streaming or Download both work
+ "desiredResources": "PlayReadyLicense",
+ "deviceTypeID": self.device["device_type"],
+ "deviceID": self.device_id,
+ "firmware": 1,
+ "gascEnabled": str(self.pv).lower(),
+ "marketplaceID": self.region["marketplace_id"],
+ "resourceUsage": "ImmediateConsumption",
+ "videoMaterialType": "Feature",
+ "operatingSystemName": "Linux" if self.vquality == "SD" else "Windows",
+ "operatingSystemVersion": "unknown" if self.vquality == "SD" else "10.0",
+ "customerID": self.customer_id,
+ "deviceDrmOverride": "Playready", #CENC or Playready both work
+ "deviceStreamingTechnologyOverride": "DASH", # or SmoothStreaming
+ "deviceVideoQualityOverride": self.vquality,
+ "deviceHdrFormatsOverride": self.VIDEO_RANGE_MAP.get(self.range, "None"),
+ }
+ headers = {
+ "Accept": "application/json",
+ "Content-Type": "application/x-www-form-urlencoded",
+ "Authorization": f"Bearer {self.device_token}"
+ }
+ data = {
+ "playReadyChallenge": lic_challenge, # expects base64
+ "includeHdcpTestKeyInLicense": "true"
+ }
+ lic = self.session.post(
+ url=self.endpoints["licence"],
+ params=params,
+ headers=headers,
+ data=data
+ ).json()
+ lic_list.append(lic)
+ params["deviceStreamingTechnologyOverride"] = "SmoothStreaming"
+ lic = self.session.post(
+ url=self.endpoints["licence"],
+ params=params,
+ headers=headers,
+ data=data
+ ).json()
+ lic_list.append(lic)
- return base64.b64decode(lic["playReadyLicense"]["encodedLicenseResponse"].encode("utf-8")).decode("utf-8") # Return Xml licence
+ for lic in lic_list:
+ if "errorsByResource" in lic:
+ error_code = lic["errorsByResource"]["PlayReadyLicense"]
+ self.log.debug(error_code)
+ if "errorCode" in error_code:
+ error_code = error_code["errorCode"]
+ elif "type" in error_code:
+ error_code = error_code["type"]
+ if error_code == "PRS.NoRights.AnonymizerIP":
+ self.log.warn(" - Amazon detected a Proxy/VPN and refused to return a license!")
+ continue
+ message = lic["errorsByResource"]["PlayReadyLicense"]["message"]
+ self.log.warn(f" - Amazon reported an error during the License request: {message} [{error_code}]")
+ continue
+ elif "error" in lic:
+ error_code = lic["error"]
+ if "errorCode" in error_code:
+ error_code = error_code["errorCode"]
+ elif "type" in error_code:
+ error_code = error_code["type"]
+ if error_code == "PRS.NoRights.AnonymizerIP":
+ self.log.warn(" - Amazon detected a Proxy/VPN and refused to return a license!")
+ continue
+ message = lic["error"]["message"]
+ self.log.warn(f" - Amazon reported an error during the License request: {message} [{error_code}]")
+ continue
+ else:
+ xmrlic = base64.b64decode(lic["playReadyLicense"]["encodedLicenseResponse"].encode("utf-8")).decode("utf-8")
+ self.log.debug(xmrlic)
+ return xmrlic # Return Xml licence
# Service specific functions
@@ -893,11 +856,11 @@ class Amazon(BaseService):
"operatingSystemName": "Linux" if quality == "SD" else "Windows",
"operatingSystemVersion": "unknown" if quality == "SD" else "10.0",
} if not self.device_token else {}),
- "deviceDrmOverride": "Playready" if manifest_type and (manifest_type == "SmoothStreaming") else "CENC",
+ "deviceDrmOverride": "Playready", # if manifest_type and (manifest_type == "SmoothStreaming") else "CENC",
"deviceStreamingTechnologyOverride": manifest_type if manifest_type else "DASH",
"deviceProtocolOverride": "Https",
"deviceVideoCodecOverride": video_codec,
- "deviceBitrateAdaptationsOverride": bitrate_mode.replace("+", ","),
+ #"deviceBitrateAdaptationsOverride": bitrate_mode.replace("+", ","),
"deviceVideoQualityOverride": quality,
"deviceHdrFormatsOverride": self.VIDEO_RANGE_MAP.get(hdr, "None"),
"supportedDRMKeyScheme": "DUAL_KEY", # ?
@@ -1118,20 +1081,21 @@ class Amazon(BaseService):
with open(self.cache_path, encoding="utf-8") as fd:
cache = jsonpickle.decode(fd.read())
#self.device["device_serial"] = cache["device_serial"]
- #if cache.get("expires_in", 0) > int(time.time()):
- # # not expired, lets use
- # self.log.info(" + Using cached device bearer")
- # self.bearer = cache["access_token"]
- #else:
+ if cache.get("expires_in", 0) > int(time.time()):
+ # not expired, lets use
+ self.log.info(" + Using cached device bearer")
+ self.bearer = cache["access_token"]
+ else:
# expired, refresh
- self.log.info("Refreshing cached device bearer...")
- refreshed_tokens = self.refresh(self.device, cache["refresh_token"], cache["access_token"])
- refreshed_tokens["refresh_token"] = cache["refresh_token"]
- # expires_in seems to be in minutes, create a unix timestamp and add the minutes in seconds
- refreshed_tokens["expires_in"] = int(time.time()) + int(refreshed_tokens["expires_in"])
- with open(self.cache_path, "w", encoding="utf-8") as fd:
- fd.write(jsonpickle.encode(refreshed_tokens))
- self.bearer = refreshed_tokens["access_token"]
+ self.log.info("Refreshing cached device bearer...")
+
+ refreshed_tokens = self.refresh(self.device, cache["refresh_token"], cache["access_token"])
+ refreshed_tokens["refresh_token"] = cache["refresh_token"]
+ # expires_in seems to be in minutes, create a unix timestamp and add the minutes in seconds
+ refreshed_tokens["expires_in"] = int(time.time()) + int(refreshed_tokens["expires_in"])
+ with open(self.cache_path, "w", encoding="utf-8") as fd:
+ fd.write(jsonpickle.encode(refreshed_tokens))
+ self.bearer = refreshed_tokens["access_token"]
else:
self.log.info(" + Registering new device bearer")
self.bearer = self.register(self.device)
@@ -1259,7 +1223,6 @@ class Amazon(BaseService):
raw_cookies = response['response']['tokens']['cookies']['.amazon.com']
except:
error = response['response']["error"]
- self.cache_path.unlink(missing_ok=True)
raise self.log.exit(f"Error when refreshing cookies: {error['message']} [{error['code']}]")
# Create a new cookies object to be used with requsts.
@@ -1315,7 +1278,7 @@ class Amazon(BaseService):
prop = prop.get("props", {}).get("codeEntry", {}).get("token")
if prop:
return prop
- raise self.log.exit("Unable to get ontv CSRF token \n Navigate to /region/eu/ontv/code?ref_=atv_auth_red_aft, login and save cookies from that page to default.txt")
+ raise self.log.exit(f"Unable to get ontv CSRF token - Navigate to {self.endpoints['ontv']}, login and save cookies from that page to default.txt")
def get_code_pair(self, device: dict) -> dict:
"""
diff --git a/vinetrimmer/services/hotstar.py b/vinetrimmer/services/hotstar.py
index 0678286..f3005e2 100644
--- a/vinetrimmer/services/hotstar.py
+++ b/vinetrimmer/services/hotstar.py
@@ -8,6 +8,8 @@ import uuid
import click
import m3u8
+from sys import platform
+
from urllib.request import urlopen, Request
from vinetrimmer.config import directories
@@ -55,6 +57,12 @@ class Hotstar(BaseService):
self.range = ctx.parent.params["range_"]
self.hdrdv = None
+ if "linux" in platform:
+ import yaml
+ #Read YAML file
+ with open("./vinetrimmer/config/Services/hotstar.yml", 'r') as stream:
+ self.config = yaml.safe_load(stream)
+
#self.log.info(self.title)
#self.log.info(self.range)
#self.log.info(self.vcodec)
diff --git a/vinetrimmer/utils/io.py b/vinetrimmer/utils/io.py
index 10cea0f..be5ea1b 100644
--- a/vinetrimmer/utils/io.py
+++ b/vinetrimmer/utils/io.py
@@ -284,7 +284,7 @@ async def m3u8dl(uri, out, track, headers=None, proxy=None):
elif track.__class__.__name__ == "AudioTrack":
if track.language:
arguments.extend([
- "-sa", f"lang='{track.language}':for=best"
+ "-sa", f"lang={track.language}:for=best"
])
else:
arguments.extend([