aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomasz Kramkowski <tomasz@kramkow.ski>2026-11-03 20:01:42 +0000
committerTomasz Kramkowski <tomasz@kramkow.ski>2026-11-03 20:01:42 +0000
commit098bd37f355a77abbb5751fc55a91f7ebe74cc6b (patch)
treed3bc93cb427276a4936d79903b62e156d5c538e7
parent64c349eb1dc0fde65fd4e6ddf525faa0a4c64a91 (diff)
downloadsteamvr-patches-098bd37f355a77abbb5751fc55a91f7ebe74cc6b.tar.gz
steamvr-patches-098bd37f355a77abbb5751fc55a91f7ebe74cc6b.tar.xz
steamvr-patches-098bd37f355a77abbb5751fc55a91f7ebe74cc6b.zip
use patch generator to build patchersHEADmaster
-rw-r--r--.clang-format77
-rw-r--r--.gitignore6
-rw-r--r--LICENSE131
-rw-r--r--LICENSES/CC-BY-SA-4.0.text428
-rw-r--r--LICENSES/CC0-1.0.text121
-rw-r--r--LICENSES/GPL-3.0-or-later.md675
-rw-r--r--README.md5
-rwxr-xr-xframe-steamos/install2
-rw-r--r--pyproject.toml26
-rw-r--r--src/steamvr_patches/__init__.py125
-rw-r--r--src/steamvr_patches/elf.py232
-rw-r--r--src/steamvr_patches/template.py169
-rw-r--r--steamvr/image-wait/image_wait.c148
-rw-r--r--steamvr/image-wait/trampoline.S26
-rwxr-xr-xsteamvr/patch-steamvr216
-rw-r--r--steamvr/patches.toml82
-rw-r--r--uv.lock182
17 files changed, 2303 insertions, 348 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..1616256
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,77 @@
+# SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
+# SPDX-License-Identifier: CC0-1.0
+
+---
+AlignAfterOpenBracket: BlockIndent
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignOperands: Align
+AlignTrailingComments: false
+AllowShortBlocksOnASingleLine: Always
+AllowShortCaseLabelsOnASingleLine: true
+AllowShortEnumsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: WithoutElse
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakBeforeMultilineStrings: false
+BinPackArguments: true
+BinPackParameters: true
+BitFieldColonSpacing: Both
+AlwaysBreakAfterReturnType: None
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Linux
+BreakBeforeTernaryOperators: true
+BreakStringLiterals: false
+ColumnLimit: 85
+ContinuationIndentWidth: 8
+DerivePointerAlignment: false
+DisableFormat: false
+# Preserve for now, to avoid problems, will be changed later.
+IncludeBlocks: Regroup
+IncludeCategories:
+ - Regex: '^<Python\.h>$'
+ Priority: -1
+ - Regex: '^"'
+ Priority: 1
+ - Regex: '^<'
+ Priority: 2
+IncludeIsMainRegex: '$'
+IndentCaseBlocks: true
+IndentCaseLabels: false
+IndentGotoLabels: false # maybe ...
+IndentPPDirectives: None
+IndentWidth: 8
+IndentWrappedFunctionNames: false
+InsertBraces: false
+KeepEmptyLinesAtTheStartOfBlocks: false
+Language: Cpp
+MaxEmptyLinesToKeep: 1
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakOpenParenthesis: 10
+PenaltyBreakString: 10
+PenaltyExcessCharacter: 60
+PenaltyReturnTypeOnItsOwnLine: 40
+PointerAlignment: Right
+QualifierAlignment: Leave
+ReflowComments: false
+# Never for now, to avoid problems, will be changed later.
+SortIncludes: CaseInsensitive
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAroundPointerQualifiers: Both
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeParens: ControlStatementsExceptForEachMacros
+SpaceBeforeSquareBrackets: false
+SpaceInEmptyBlock: false
+SpacesBeforeTrailingComments: 1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+TabWidth: 8
+UseCRLF: false
+UseTab: ForContinuationAndIndentation
+...
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cd2f5d4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
+# SPDX-License-Identifier: CC0-1.0
+
+dist/
+__pycache__/
+.venv
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 6f73ac4..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,131 +0,0 @@
-# Creative Commons CC0 1.0 Universal
-
-CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL
-SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT
-RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS"
-BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS
-DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS
-LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE
-INFORMATION OR WORKS PROVIDED HEREUNDER.
-
-## Statement of Purpose
-
-The laws of most jurisdictions throughout the world automatically confer
-exclusive Copyright and Related Rights (defined below) upon the creator and
-subsequent owner(s) (each and all, an "owner") of an original work of
-authorship and/or a database (each, a "Work").
-
-Certain owners wish to permanently relinquish those rights to a Work for
-the purpose of contributing to a commons of creative, cultural and
-scientific works ("Commons") that the public can reliably and without fear
-of later claims of infringement build upon, modify, incorporate in other
-works, reuse and redistribute as freely as possible in any form whatsoever
-and for any purposes, including without limitation commercial purposes.
-These owners may contribute to the Commons to promote the ideal of a free
-culture and the further production of creative, cultural and scientific
-works, or to gain reputation or greater distribution for their Work in part
-through the use and efforts of others.
-
-For these and/or other purposes and motivations, and without any
-expectation of additional consideration or compensation, the person
-associating CC0 with a Work (the "Affirmer"), to the extent that he or she
-is an owner of Copyright and Related Rights in the Work, voluntarily elects
-to apply CC0 to the Work and publicly distribute the Work under its terms,
-with knowledge of his or her Copyright and Related Rights in the Work and
-the meaning and intended legal effect of CC0 on those rights.
-
-1. __Copyright and Related Rights.__ A Work made available under CC0 may be
- protected by copyright and related or neighboring rights ("Copyright and
- Related Rights"). Copyright and Related Rights include, but are not
- limited to, the following:
-
- i. the right to reproduce, adapt, distribute, perform, display,
- communicate, and translate a Work;
-
- ii. moral rights retained by the original author(s) and/or
- performer(s);
-
- iii. publicity and privacy rights pertaining to a person's image or
- likeness depicted in a Work;
-
- iv. rights protecting against unfair competition in regards to a Work,
- subject to the limitations in paragraph 4(a), below;
-
- v. rights protecting the extraction, dissemination, use and reuse of
- data in a Work;
-
- vi. database rights (such as those arising under Directive 96/9/EC of
- the European Parliament and of the Council of 11 March 1996 on the
- legal protection of databases, and under any national
- implementation thereof, including any amended or successor version
- of such directive); and
-
- vii. other similar, equivalent or corresponding rights throughout the
- world based on applicable law or treaty, and any national
- implementations thereof.
-
-2. __Waiver.__ To the greatest extent permitted by, but not in
- contravention of, applicable law, Affirmer hereby overtly, fully,
- permanently, irrevocably and unconditionally waives, abandons, and
- surrenders all of Affirmer's Copyright and Related Rights and associated
- claims and causes of action, whether now known or unknown (including
- existing as well as future claims and causes of action), in the Work (i)
- in all territories worldwide, (ii) for the maximum duration provided by
- applicable law or treaty (including future time extensions), (iii) in
- any current or future medium and for any number of copies, and (iv) for
- any purpose whatsoever, including without limitation commercial,
- advertising or promotional purposes (the "Waiver"). Affirmer makes the
- Waiver for the benefit of each member of the public at large and to the
- detriment of Affirmer's heirs and successors, fully intending that such
- Waiver shall not be subject to revocation, rescission, cancellation,
- termination, or any other legal or equitable action to disrupt the quiet
- enjoyment of the Work by the public as contemplated by Affirmer's
- express Statement of Purpose.
-
-3. __Public License Fallback.__ Should any part of the Waiver for any
- reason be judged legally invalid or ineffective under applicable law,
- then the Waiver shall be preserved to the maximum extent permitted
- taking into account Affirmer's express Statement of Purpose. In
- addition, to the extent the Waiver is so judged Affirmer hereby grants
- to each affected person a royalty-free, non transferable, non
- sublicensable, non exclusive, irrevocable and unconditional license to
- exercise Affirmer's Copyright and Related Rights in the Work (i) in all
- territories worldwide, (ii) for the maximum duration provided by
- applicable law or treaty (including future time extensions), (iii) in
- any current or future medium and for any number of copies, and (iv) for
- any purpose whatsoever, including without limitation commercial,
- advertising or promotional purposes (the "License"). The License shall
- be deemed effective as of the date CC0 was applied by Affirmer to the
- Work. Should any part of the License for any reason be judged legally
- invalid or ineffective under applicable law, such partial invalidity or
- ineffectiveness shall not invalidate the remainder of the License, and
- in such case Affirmer hereby affirms that he or she will not (i)
- exercise any of his or her remaining Copyright and Related Rights in the
- Work or (ii) assert any associated claims and causes of action with
- respect to the Work, in either case contrary to Affirmer's express
- Statement of Purpose.
-
-4. __Limitations and Disclaimers.__
-
- a. No trademark or patent rights held by Affirmer are waived,
- abandoned, surrendered, licensed or otherwise affected by this
- document.
-
- b. Affirmer offers the Work as-is and makes no representations or
- warranties of any kind concerning the Work, express, implied,
- statutory or otherwise, including without limitation warranties of
- title, merchantability, fitness for a particular purpose, non
- infringement, or the absence of latent or other defects, accuracy,
- or the present or absence of errors, whether or not discoverable,
- all to the greatest extent permissible under applicable law.
-
- c. Affirmer disclaims responsibility for clearing rights of other
- persons that may apply to the Work or any use thereof, including
- without limitation any person's Copyright and Related Rights in the
- Work. Further, Affirmer disclaims responsibility for obtaining any
- necessary consents, permissions or other rights required for any use
- of the Work.
-
- d. Affirmer understands and acknowledges that Creative Commons is not a
- party to this document and has no duty or obligation with respect to
- this CC0 or use of the Work.
diff --git a/LICENSES/CC-BY-SA-4.0.text b/LICENSES/CC-BY-SA-4.0.text
new file mode 100644
index 0000000..2d58298
--- /dev/null
+++ b/LICENSES/CC-BY-SA-4.0.text
@@ -0,0 +1,428 @@
+Attribution-ShareAlike 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-ShareAlike 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-ShareAlike 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. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. 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.
+
+ e. 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.
+
+ f. 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.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. 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.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. 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.
+
+ l. 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.
+
+ m. 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; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 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. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. 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.
+
+
+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 (including in modified
+ form), 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.
+
+ 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.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+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;
+
+ 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,
+ including for purposes of Section 3(b); 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/LICENSES/CC0-1.0.text b/LICENSES/CC0-1.0.text
new file mode 100644
index 0000000..0e259d4
--- /dev/null
+++ b/LICENSES/CC0-1.0.text
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
diff --git a/LICENSES/GPL-3.0-or-later.md b/LICENSES/GPL-3.0-or-later.md
new file mode 100644
index 0000000..496acdb
--- /dev/null
+++ b/LICENSES/GPL-3.0-or-later.md
@@ -0,0 +1,675 @@
+# GNU GENERAL PUBLIC LICENSE
+
+Version 3, 29 June 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc.
+<https://fsf.org/>
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+## Preamble
+
+The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom
+to share and change all versions of a program--to make sure it remains
+free software for all its users. We, the Free Software Foundation, use
+the GNU General Public License for most of our software; it applies
+also to any other work released this way by its authors. You can apply
+it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you
+have certain responsibilities if you distribute copies of the
+software, or if you modify it: responsibilities to respect the freedom
+of others.
+
+For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the
+manufacturer can do so. This is fundamentally incompatible with the
+aim of protecting users' freedom to change the software. The
+systematic pattern of such abuse occurs in the area of products for
+individuals to use, which is precisely where it is most unacceptable.
+Therefore, we have designed this version of the GPL to prohibit the
+practice for those products. If such problems arise substantially in
+other domains, we stand ready to extend this provision to those
+domains in future versions of the GPL, as needed to protect the
+freedom of users.
+
+Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish
+to avoid the special danger that patents applied to a free program
+could make it effectively proprietary. To prevent this, the GPL
+assures that patents cannot be used to render the program non-free.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+## TERMS AND CONDITIONS
+
+### 0. Definitions.
+
+"This License" refers to version 3 of the GNU General Public License.
+
+"Copyright" also means copyright-like laws that apply to other kinds
+of works, such as semiconductor masks.
+
+"The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of
+an exact copy. The resulting work is called a "modified version" of
+the earlier work or a work "based on" the earlier work.
+
+A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user
+through a computer network, with no transfer of a copy, is not
+conveying.
+
+An interactive user interface displays "Appropriate Legal Notices" to
+the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+### 1. Source Code.
+
+The "source code" for a work means the preferred form of the work for
+making modifications to it. "Object code" means any non-source form of
+a work.
+
+A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+The Corresponding Source need not include anything that users can
+regenerate automatically from other parts of the Corresponding Source.
+
+The Corresponding Source for a work in source code form is that same
+work.
+
+### 2. Basic Permissions.
+
+All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+You may make, run and propagate covered works that you do not convey,
+without conditions so long as your license otherwise remains in force.
+You may convey covered works to others for the sole purpose of having
+them make modifications exclusively for you, or provide you with
+facilities for running those works, provided that you comply with the
+terms of this License in conveying all material for which you do not
+control copyright. Those thus making or running the covered works for
+you must do so exclusively on your behalf, under your direction and
+control, on terms that prohibit them from making any copies of your
+copyrighted material outside their relationship with you.
+
+Conveying under any other circumstances is permitted solely under the
+conditions stated below. Sublicensing is not allowed; section 10 makes
+it unnecessary.
+
+### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such
+circumvention is effected by exercising rights under this License with
+respect to the covered work, and you disclaim any intention to limit
+operation or modification of the work as a means of enforcing, against
+the work's users, your or third parties' legal rights to forbid
+circumvention of technological measures.
+
+### 4. Conveying Verbatim Copies.
+
+You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+### 5. Conveying Modified Source Versions.
+
+You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these
+conditions:
+
+- a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+- b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under
+ section 7. This requirement modifies the requirement in section 4
+ to "keep intact all notices".
+- c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+- d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+### 6. Conveying Non-Source Forms.
+
+You may convey a covered work in object code form under the terms of
+sections 4 and 5, provided that you also convey the machine-readable
+Corresponding Source under the terms of this License, in one of these
+ways:
+
+- a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+- b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the Corresponding
+ Source from a network server at no charge.
+- c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+- d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+- e) Convey the object code using peer-to-peer transmission,
+ provided you inform other peers where the object code and
+ Corresponding Source of the work are being offered to the general
+ public at no charge under subsection 6d.
+
+A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal,
+family, or household purposes, or (2) anything designed or sold for
+incorporation into a dwelling. In determining whether a product is a
+consumer product, doubtful cases shall be resolved in favor of
+coverage. For a particular product received by a particular user,
+"normally used" refers to a typical or common use of that class of
+product, regardless of the status of the particular user or of the way
+in which the particular user actually uses, or expects or is expected
+to use, the product. A product is a consumer product regardless of
+whether the product has substantial commercial, industrial or
+non-consumer uses, unless such uses represent the only significant
+mode of use of the product.
+
+"Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to
+install and execute modified versions of a covered work in that User
+Product from a modified version of its Corresponding Source. The
+information must suffice to ensure that the continued functioning of
+the modified object code is in no case prevented or interfered with
+solely because modification has been made.
+
+If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or
+updates for a work that has been modified or installed by the
+recipient, or for the User Product in which it has been modified or
+installed. Access to a network may be denied when the modification
+itself materially and adversely affects the operation of the network
+or violates the rules and protocols for communication across the
+network.
+
+Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+### 7. Additional Terms.
+
+"Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders
+of that material) supplement the terms of this License with terms:
+
+- a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+- b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+- c) Prohibiting misrepresentation of the origin of that material,
+ or requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+- d) Limiting the use for publicity purposes of names of licensors
+ or authors of the material; or
+- e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+- f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions
+ of it) with contractual assumptions of liability to the recipient,
+ for any liability that these contractual assumptions directly
+ impose on those licensors and authors.
+
+All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions; the
+above requirements apply either way.
+
+### 8. Termination.
+
+You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+However, if you cease all violation of this License, then your license
+from a particular copyright holder is reinstated (a) provisionally,
+unless and until the copyright holder explicitly and finally
+terminates your license, and (b) permanently, if the copyright holder
+fails to notify you of the violation by some reasonable means prior to
+60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+### 9. Acceptance Not Required for Having Copies.
+
+You are not required to accept this License in order to receive or run
+a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+### 10. Automatic Licensing of Downstream Recipients.
+
+Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+### 11. Patents.
+
+A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+A contributor's "essential patent claims" are all patent claims owned
+or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+A patent license is "discriminatory" if it does not include within the
+scope of its coverage, prohibits the exercise of, or is conditioned on
+the non-exercise of one or more of the rights that are specifically
+granted under this License. You may not convey a covered work if you
+are a party to an arrangement with a third party that is in the
+business of distributing software, under which you make payment to the
+third party based on the extent of your activity of conveying the
+work, and under which the third party grants, to any of the parties
+who would receive the covered work from you, a discriminatory patent
+license (a) in connection with copies of the covered work conveyed by
+you (or copies made from those copies), or (b) primarily for and in
+connection with specific products or compilations that contain the
+covered work, unless you entered into that arrangement, or that patent
+license was granted, prior to 28 March 2007.
+
+Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+### 12. No Surrender of Others' Freedom.
+
+If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under
+this License and any other pertinent obligations, then as a
+consequence you may not convey it at all. For example, if you agree to
+terms that obligate you to collect a royalty for further conveying
+from those to whom you convey the Program, the only way you could
+satisfy both those terms and this License would be to refrain entirely
+from conveying the Program.
+
+### 13. Use with the GNU Affero General Public License.
+
+Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+### 14. Revised Versions of this License.
+
+The Free Software Foundation may publish revised and/or new versions
+of the GNU General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in
+detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies that a certain numbered version of the GNU General Public
+License "or any later version" applies to it, you have the option of
+following the terms and conditions either of that numbered version or
+of any later version published by the Free Software Foundation. If the
+Program does not specify a version number of the GNU General Public
+License, you may choose any version ever published by the Free
+Software Foundation.
+
+If the Program specifies that a proxy can decide which future versions
+of the GNU General Public License can be used, that proxy's public
+statement of acceptance of a version permanently authorizes you to
+choose that version for the Program.
+
+Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+### 15. Disclaimer of Warranty.
+
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
+WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
+PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
+DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
+CORRECTION.
+
+### 16. Limitation of Liability.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
+CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
+NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
+LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
+TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
+PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+### 17. Interpretation of Sections 15 and 16.
+
+If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+END OF TERMS AND CONDITIONS
+
+## How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these
+terms.
+
+To do so, attach the following notices to the program. It is safest to
+attach them to the start of each source file to most effectively state
+the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper
+mail.
+
+If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands \`show w' and \`show c' should show the
+appropriate parts of the General Public License. Of course, your
+program's commands might be different; for a GUI interface, you would
+use an "about box".
+
+You should also get your employer (if you work as a programmer) or
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. For more information on this, and how to apply and follow
+the GNU GPL, see <https://www.gnu.org/licenses/>.
+
+The GNU General Public License does not permit incorporating your
+program into proprietary programs. If your program is a subroutine
+library, you may consider it more useful to permit linking proprietary
+applications with the library. If this is what you want to do, use the
+GNU Lesser General Public License instead of this License. But first,
+please read <https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/README.md b/README.md
index eff86b6..326b357 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,8 @@
+<!--
+SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
+SPDX-License-Identifier: CC-BY-SA-4.0
+-->
+
# Patches for SteamVR on Linux
Use at your own risk!
diff --git a/frame-steamos/install b/frame-steamos/install
index d36b0df..e03a621 100755
--- a/frame-steamos/install
+++ b/frame-steamos/install
@@ -1,6 +1,6 @@
#!/bin/sh
# SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
-# SPDX-License-Identifier: CC0-1.0
+# SPDX-License-Identifier: GPL-3.0-or-later
set -e
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..e2ffb1d
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,26 @@
+# SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
+# SPDX-License-Identifier: CC0-1.0
+
+[project]
+name = "steamvr-patches"
+version = "0.1.0"
+description = "SteamVR patching utilities"
+readme = "README.md"
+license = "GPL-3.0-or-later"
+license-files = ["LICENSES/*"]
+requires-python = ">=3.13"
+dependencies = [
+ "capstone>=5,<6",
+ "pydantic>=2.12,<3",
+ "pyelftools>=0.32,<1",
+]
+
+[project.scripts]
+generate-patcher = "steamvr_patches:main"
+
+[build-system]
+requires = ["uv_build>=0.13,<0.14"]
+build-backend = "uv_build"
+
+[tool.ruff.lint]
+select = ["E", "F", "I", "UP"]
diff --git a/src/steamvr_patches/__init__.py b/src/steamvr_patches/__init__.py
new file mode 100644
index 0000000..be3b696
--- /dev/null
+++ b/src/steamvr_patches/__init__.py
@@ -0,0 +1,125 @@
+# SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+import argparse
+import hashlib
+import sys
+import tempfile
+import tomllib
+from base64 import b85encode
+from importlib.resources import files
+from io import BytesIO
+from json import dumps
+from pathlib import Path
+from typing import Annotated, BinaryIO, Self
+from zlib import compress
+
+from pydantic import BaseModel, ConfigDict, Field, field_validator
+
+from .elf import Hook, compile_hooks
+
+NonnegativeInt = Annotated[int, Field(strict=True, ge=0)]
+
+
+class Compilation(BaseModel):
+ model_config = ConfigDict(extra="forbid")
+
+ sources: list[Path] = Field(min_length=1)
+ imports: dict[str, NonnegativeInt] = Field(default_factory=dict)
+ hooks: list[Hook] = Field(min_length=1)
+
+
+class FilePatch(BaseModel):
+ model_config = ConfigDict(val_json_bytes="hex", extra="forbid")
+
+ path: Path
+ sha256: bytes = Field(min_length=32, max_length=32)
+ edits: list[tuple[NonnegativeInt, bytes]]
+ compiled: Compilation | None = None
+
+
+class PatchSet(BaseModel):
+ model_config = ConfigDict(extra="forbid")
+
+ target: str
+ targets: list[FilePatch]
+
+ @field_validator("targets")
+ @classmethod
+ def unique_paths(cls, targets: list[FilePatch]) -> list[FilePatch]:
+ paths = [target.path for target in targets]
+ if len(set(paths)) != len(paths):
+ raise ValueError("duplicate patch target")
+ return targets
+
+ @classmethod
+ def parse(cls, config: dict, directory: Path) -> Self:
+ result = cls.model_validate(config)
+ for target in result.targets:
+ if target.compiled is not None:
+ target.compiled.sources = [
+ directory / source for source in target.compiled.sources
+ ]
+ return result
+
+
+def compile_patch(config: FilePatch, source: BinaryIO, output: Path) -> dict:
+ source.seek(0)
+ data = source.read()
+ if hashlib.sha256(data).digest() != config.sha256:
+ raise ValueError(f"input SHA-256 mismatch: {config.path}")
+ edits = list(config.edits)
+ if config.compiled is not None:
+ compiled = config.compiled
+ edits += compile_hooks(
+ source, compiled.sources, compiled.imports, compiled.hooks, output
+ )
+ end = 0
+ for offset, replacement in sorted(edits):
+ if offset < end:
+ raise ValueError("overlapping edits")
+ end = offset + len(replacement)
+ patched = BytesIO(data)
+ for offset, replacement in edits:
+ patched.seek(offset)
+ patched.write(replacement)
+ return {
+ "original_hash": config.sha256,
+ "patched_hash": hashlib.sha256(patched.getbuffer()).digest(),
+ "edits": edits,
+ }
+
+
+def render(patches: dict[str, dict]) -> str:
+ template = files("steamvr_patches").joinpath("template.py").read_text()
+ placeholder = "PATCH_DATA = {}"
+ if template.count(placeholder) != 1:
+ raise ValueError("missing or duplicate runtime patch-table placeholder")
+ data = dumps(patches, default=bytes.hex, separators=(",", ":")).encode()
+ encoded = b85encode(compress(data, level=9))
+ return template.replace(placeholder, f"PATCH_DATA = decode_patch_data({encoded!r})")
+
+
+def generate(patch_data: Path, input_root: Path) -> str:
+ with (patch_data / "patches.toml").open("rb") as source:
+ definition = PatchSet.parse(tomllib.load(source), patch_data)
+ patches = {}
+ with tempfile.TemporaryDirectory(prefix="generate-patcher-") as temporary:
+ for index, config in enumerate(definition.targets):
+ with (input_root / config.path).open("rb") as source:
+ patches[config.path.as_posix()] = compile_patch(
+ config, source, Path(temporary) / str(index)
+ )
+ return render(patches)
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "patch_data", type=Path, help="directory containing patches.toml etc"
+ )
+ parser.add_argument(
+ "input_root", type=Path, help="directory containing the unpatched target files"
+ )
+ args = parser.parse_args()
+ sys.stdout.write(generate(args.patch_data, args.input_root))
diff --git a/src/steamvr_patches/elf.py b/src/steamvr_patches/elf.py
new file mode 100644
index 0000000..e69c567
--- /dev/null
+++ b/src/steamvr_patches/elf.py
@@ -0,0 +1,232 @@
+# SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+"""Compile C/assembly hooks into one Linux x86-64 ELF patch payload."""
+
+import struct
+import subprocess
+from pathlib import Path
+from typing import BinaryIO
+
+from capstone import CS_ARCH_X86, CS_MODE_64, Cs
+from elftools.construct import Container
+from elftools.elf.constants import P_FLAGS
+from elftools.elf.elffile import ELFFile
+from elftools.elf.relocation import RelocationSection
+from elftools.elf.sections import SymbolTableSection
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class Hook(BaseModel):
+ model_config = ConfigDict(strict=True, frozen=True, extra="forbid")
+
+ address: int = Field(ge=0)
+ length: int = Field(ge=5)
+ target: str = Field(min_length=1)
+
+
+class CompiledPayload:
+ """Position-independent code with resolved entry addresses inside it."""
+
+ code: bytes
+ entries: dict[str, int]
+
+ def __init__(self, source: BinaryIO, targets: list[str]):
+ elf = ELFFile(source)
+ for section in elf.iter_sections():
+ if (
+ section["sh_flags"] & 2
+ and section["sh_size"]
+ and section.name != ".payload"
+ ):
+ raise ValueError(f"unsupported allocated section: {section.name}")
+ # PC32 (2) PLT32 (4) (PC-relative)
+ if isinstance(section, RelocationSection):
+ if any(
+ r["r_info_type"] not in (2, 4) for r in section.iter_relocations()
+ ):
+ raise ValueError("helper contains a non-relative relocation")
+ payload = elf.get_section_by_name(".payload")
+ if payload is None:
+ raise ValueError("missing compiled payload")
+ self.code = payload.data()
+ symbols = elf.get_section_by_name(".symtab")
+ if not isinstance(symbols, SymbolTableSection):
+ raise ValueError("missing compiled symbol table")
+ payload_index = elf.get_section_index(".payload")
+ address = payload["sh_addr"]
+ self.entries = {}
+ for target in targets:
+ matches = symbols.get_symbol_by_name(target) or []
+ if len(matches) != 1 or matches[0]["st_shndx"] != payload_index:
+ raise ValueError(
+ f"hook target is not a unique payload symbol: {target}"
+ )
+ entry = matches[0]["st_value"]
+ if not address <= entry < address + len(self.code):
+ raise ValueError(
+ f"hook target is outside the compiled payload: {target}"
+ )
+ self.entries[target] = entry
+
+
+def align(value: int, boundary: int) -> int:
+ return (value + boundary - 1) & -boundary
+
+
+def run(*args: str) -> str:
+ return subprocess.check_output(args, text=True)
+
+
+def resolve_hook_sites(elf: ELFFile, hooks: list[Hook]) -> list[tuple[Hook, int]]:
+ """Find file offsets and verify that each hook covers whole instructions."""
+ source = elf.stream
+ sites = []
+ disassembler = Cs(CS_ARCH_X86, CS_MODE_64)
+ for hook in hooks:
+ offset = next(elf.address_offsets(hook.address, hook.length))
+ source.seek(offset)
+ instructions = disassembler.disasm(source.read(hook.length), hook.address)
+ if sum(i.size for i in instructions) != hook.length:
+ raise ValueError("hook length cuts an instruction or contains invalid code")
+ sites.append((hook, offset))
+ return sites
+
+
+def compile_code(
+ sources: list[Path],
+ imports: dict[str, int],
+ targets: list[str],
+ code_va: int,
+ output: Path,
+) -> CompiledPayload:
+ """Compile and link the patch sources at their destination address."""
+ output.mkdir(parents=True, exist_ok=False)
+ objects = []
+ flags = [
+ "-Os",
+ "-ffreestanding",
+ "-fPIE",
+ "-fvisibility=hidden",
+ "-fno-stack-protector",
+ "-fno-strict-aliasing",
+ "-mno-red-zone",
+ "-fno-asynchronous-unwind-tables",
+ "-fno-unwind-tables",
+ "-fcf-protection=none",
+ "-Wall",
+ "-Wextra",
+ ]
+ for number, source_path in enumerate(sources):
+ obj = output / f"{number}.o"
+ source_flags = ["-std=c17"] if source_path.suffix == ".c" else []
+ run("cc", *flags, *source_flags, "-c", str(source_path), "-o", str(obj))
+ objects.append(str(obj))
+
+ linker_script = output / "layout.ld"
+ linker_script.write_text(
+ "PHDRS { code PT_LOAD FLAGS(5); }\n" # PF_R | PF_X: readable/executable.
+ f"SECTIONS {{ . = {code_va:#x};\n"
+ " .payload : { *(.text.entry) *(.text*) *(.rodata*) } :code\n"
+ " /DISCARD/ : { *(.comment) *(.note.GNU-stack) }\n}\n"
+ )
+ import_args = [f"--defsym={name}={value:#x}" for name, value in imports.items()]
+ linked = output / "helper.elf"
+ run(
+ "ld",
+ "--build-id=none",
+ "--emit-relocs",
+ f"--script={linker_script}",
+ *import_args,
+ f"--output={linked}",
+ *objects,
+ )
+ with linked.open("rb") as compiled:
+ return CompiledPayload(compiled, targets)
+
+
+def make_hook_edits(
+ sites: list[tuple[Hook, int]], entries: dict[str, int]
+) -> list[tuple[int, bytes]]:
+ """Redirect each hook site to its compiled entry with a relative jump."""
+ edits = []
+ for hook, offset in sites:
+ displacement = entries[hook.target] - hook.address - 5
+ if not -(1 << 31) <= displacement < 1 << 31:
+ raise ValueError(
+ f"hook target is outside relative jump range: {hook.target}"
+ )
+ jump = b"\xe9" + struct.pack("<i", displacement) + b"\x90" * (hook.length - 5)
+ edits.append((offset, jump))
+ return edits
+
+
+def compile_segment(
+ elf: ELFFile,
+ sources: list[Path],
+ imports: dict[str, int],
+ targets: list[str],
+ output: Path,
+) -> tuple[CompiledPayload, list[tuple[int, bytes]]]:
+ """Place the compiled code and enlarged program-header table in a new segment."""
+ phdr = elf.structs.Elf_Phdr
+ headers = [segment.header for segment in elf.iter_segments()]
+ # Place the new segment after all existing loadable mappings.
+ load_end = max(
+ header["p_vaddr"] + header["p_memsz"]
+ for header in headers
+ if header["p_type"] == "PT_LOAD"
+ )
+ segment_va = align(load_end, 4096)
+ segment_offset = align(elf.stream_len, 4096)
+ table_size = (len(headers) + 1) * phdr.sizeof()
+ code_delta = align(table_size, 16)
+ code_va = segment_va + code_delta
+ compiled = compile_code(sources, imports, targets, code_va, output)
+
+ # Relocate the enlarged program-header table into the new segment.
+ table_header = next(header for header in headers if header["p_type"] == "PT_PHDR")
+ table_header.update(
+ p_flags=P_FLAGS.PF_R,
+ p_offset=segment_offset,
+ p_vaddr=segment_va,
+ p_paddr=segment_va,
+ p_filesz=table_size,
+ p_memsz=table_size,
+ p_align=8,
+ )
+ segment_size = code_delta + len(compiled.code)
+ headers.append(
+ Container(
+ p_type="PT_LOAD",
+ p_flags=P_FLAGS.PF_R | P_FLAGS.PF_X,
+ p_offset=segment_offset,
+ p_vaddr=segment_va,
+ p_paddr=segment_va,
+ p_filesz=segment_size,
+ p_memsz=segment_size,
+ p_align=4096,
+ )
+ )
+ edits = [
+ (0x20, struct.pack("<Q", segment_offset)), # Elf64_Ehdr.e_phoff
+ (0x38, struct.pack("<H", len(headers))), # Elf64_Ehdr.e_phnum
+ (segment_offset, b"".join(phdr.build(header) for header in headers)),
+ (segment_offset + code_delta, compiled.code),
+ ]
+ return compiled, edits
+
+
+def compile_hooks(
+ source: BinaryIO,
+ sources: list[Path],
+ imports: dict[str, int],
+ hooks: list[Hook],
+ output: Path,
+) -> list[tuple[int, bytes]]:
+ elf = ELFFile(source)
+ sites = resolve_hook_sites(elf, hooks)
+ compiled, segment_edits = compile_segment(
+ elf, sources, imports, [hook.target for hook in hooks], output
+ )
+ return make_hook_edits(sites, compiled.entries) + segment_edits
diff --git a/src/steamvr_patches/template.py b/src/steamvr_patches/template.py
new file mode 100644
index 0000000..4f88b10
--- /dev/null
+++ b/src/steamvr_patches/template.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python3
+# SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
+# SPDX-License-Identifier: CC0-1.0
+
+"""Patching wrapper script.
+
+This file is generated.
+
+See https://kramkow.ski/project/steamvr-patches.html and
+https://the-tk.com/cgit/steamvr-patches/about/ for more information.
+
+Use at your own risk!
+
+
+Installation (where <thing> is the thing this patcher targets, e.g. steamvr):
+
+1. Put this `patch-<thing>` script somewhere, e.g. `~/.local/libexec/patch-<thing>`
+2. Mark it executable
+3. Change the launch options of the specified <thing> to:
+ `/absolute/path/to/patch-<thing> %command%`.
+ If you already have something in there, I'm sure you can figure it out.
+4. Launch <thing> normally. You should see a desktop notification with some
+ diagnostics which specify if the script actually did/tried to do something.
+"""
+
+import hashlib
+import os
+import stat
+import subprocess
+import sys
+import tempfile
+from base64 import b85decode
+from dataclasses import dataclass
+from io import BytesIO
+from json import loads as json_loads
+from pathlib import Path
+from zlib import decompress
+
+NAME = Path(__file__).name
+MARKER = b"Valve, please fix."
+
+
+@dataclass(frozen=True)
+class Edit:
+ offset: int
+ replacement: bytes
+
+
+@dataclass(frozen=True)
+class Patch:
+ original_hash: bytes
+ patched_hash: bytes
+ edits: tuple[Edit, ...]
+
+ @classmethod
+ def from_dict(cls, data: dict) -> "Patch":
+ return cls(
+ bytes.fromhex(data["original_hash"]),
+ bytes.fromhex(data["patched_hash"]),
+ tuple(
+ Edit(offset, bytes.fromhex(replacement))
+ for offset, replacement in data["edits"]
+ ),
+ )
+
+ def apply_to(self, data: bytes) -> bytes:
+ payload = data.removesuffix(MARKER)
+ digest = sha256(payload)
+ if digest == self.patched_hash:
+ return payload + MARKER
+ if digest != self.original_hash:
+ if payload != data:
+ raise LookupError(
+ "already patched with another set of patches: "
+ "verify file integrity and restart"
+ )
+ raise LookupError("unrecognized file hash: this wrapper may need updating")
+ result = BytesIO(payload)
+ for edit in self.edits:
+ result.seek(edit.offset)
+ result.write(edit.replacement)
+ patched = result.getvalue()
+ if sha256(patched) != self.patched_hash:
+ raise ValueError("patched file checksum mismatch")
+ return patched + MARKER
+
+
+def decode_patch_data(data: bytes) -> dict:
+ return json_loads(decompress(b85decode(data)))
+
+
+# Just to keep the file small, not for obfuscation.
+PATCH_DATA = {}
+PATCHES: dict[str, Patch] = {
+ path: Patch.from_dict(data) for path, data in PATCH_DATA.items()
+}
+
+
+def sha256(data):
+ return hashlib.sha256(data).digest()
+
+
+def notify(message):
+ print(f"{NAME}: {message}", file=sys.stderr)
+ try:
+ subprocess.run(
+ ["notify-send", NAME, message],
+ check=False,
+ timeout=3,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ )
+ except (OSError, subprocess.TimeoutExpired):
+ pass
+
+
+def replace(path, data):
+ mode = stat.S_IMODE(path.stat().st_mode)
+ with tempfile.NamedTemporaryFile(
+ dir=path.parent, prefix=path.name + ".", delete=False
+ ) as f:
+ temporary = Path(f.name)
+ try:
+ f.write(data)
+ f.flush()
+ os.fchmod(f.fileno(), mode)
+ os.fsync(f.fileno())
+ os.replace(temporary, path)
+ finally:
+ temporary.unlink(missing_ok=True)
+
+
+def main(command):
+ diagnostics = []
+ if not command:
+ print(f"Usage: {NAME} command [args...]", file=sys.stderr)
+ return 2
+ try:
+ root = Path.cwd()
+ pending = []
+ for relative, patch in PATCHES.items():
+ path = (root / relative).resolve()
+ try:
+ original = path.read_bytes()
+ except FileNotFoundError as e:
+ diagnostics.append(f"Skipped `{relative}`: {e}")
+ continue
+ try:
+ updated = patch.apply_to(original)
+ except ValueError as e:
+ raise ValueError(f"Patching `{relative}` failed: {e}") from e
+ except LookupError as e:
+ diagnostics.append(f"Skipped `{relative}`: {e}")
+ continue
+ if updated != original:
+ pending.append((path, updated))
+ for path, updated in pending:
+ replace(path, updated)
+ diagnostics.append(f"Patched {path}")
+ if diagnostics:
+ notify("\n".join(diagnostics))
+ except (OSError, ValueError) as e:
+ notify("\n".join([f"Encountered an error: {e}", *diagnostics]))
+ return 1
+ os.execvp(command[0], command)
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv[1:]))
diff --git a/steamvr/image-wait/image_wait.c b/steamvr/image-wait/image_wait.c
new file mode 100644
index 0000000..e8112c3
--- /dev/null
+++ b/steamvr/image-wait/image_wait.c
@@ -0,0 +1,148 @@
+// SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
+// SPDX-License-Identifier: CC0-1.0
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#define ASSERT_SIZE(type, expected) \
+ static_assert(sizeof (type) == (expected), #type " size must be " #expected)
+#define ASSERT_OFFSET(type, field, expected) \
+ static_assert( \
+ offsetof(type, field) == (expected), \
+ #type "." #field " offset must be " #expected \
+ )
+
+ASSERT_SIZE(void *, 8);
+
+enum { VK_PIPELINE_STAGE_ALL_COMMANDS_BIT = 0x00010000 };
+
+typedef struct {
+ uintptr_t semaphore;
+ uint64_t value;
+} SemaphoreEntry;
+ASSERT_SIZE(SemaphoreEntry, 16);
+ASSERT_OFFSET(SemaphoreEntry, value, 8);
+
+// vr::IVRSharedResource
+struct SharedResource {
+ const struct SharedResourceVtable *vtable;
+};
+
+typedef struct Semaphore *GetSemaphore(struct SharedResource *);
+
+struct SharedResourceVtable {
+ unsigned char unknown_000[0x30];
+ GetSemaphore *get_semaphore;
+};
+ASSERT_OFFSET(struct SharedResourceVtable, get_semaphore, 0x30);
+
+typedef uint64_t SemaphoreGetU64(struct Semaphore *);
+
+struct SemaphoreVtable {
+ unsigned char unknown_000[0x40];
+ SemaphoreGetU64 *get_handle;
+ unsigned char unknown_048[0x70 - 0x48];
+ SemaphoreGetU64 *get_value;
+};
+ASSERT_OFFSET(struct SemaphoreVtable, get_handle, 0x40);
+ASSERT_OFFSET(struct SemaphoreVtable, get_value, 0x70);
+
+// vr::IVRSharedSemaphore
+struct Semaphore {
+ const struct SemaphoreVtable *vtable;
+};
+
+// vr::IVRSharedImage
+struct Image {
+ const void *vtable;
+};
+
+struct Texture {
+ unsigned char unknown_000[0x48];
+ struct Image *image;
+};
+ASSERT_OFFSET(struct Texture, image, 0x48);
+
+struct Batch {
+ unsigned char unknown_000[0xf0];
+ SemaphoreEntry *waits;
+ SemaphoreEntry *waits_end;
+ unsigned char unknown_100[0x140 - 0x100];
+ uint32_t *wait_stages;
+ unsigned char unknown_148[0x398 - 0x148];
+ SemaphoreEntry *signals;
+ SemaphoreEntry *signals_end;
+};
+ASSERT_OFFSET(struct Batch, waits, 0xf0);
+ASSERT_OFFSET(struct Batch, waits_end, 0xf8);
+ASSERT_OFFSET(struct Batch, wait_stages, 0x140);
+ASSERT_OFFSET(struct Batch, signals, 0x398);
+ASSERT_OFFSET(struct Batch, signals_end, 0x3a0);
+
+struct RendererContext {
+ unsigned char unknown_000[8];
+ struct Batch *batch;
+};
+ASSERT_OFFSET(struct RendererContext, batch, 8);
+
+// CFacetVRRenderer
+struct Renderer {
+ unsigned char unknown_000[0x12b0];
+ struct RendererContext *context;
+};
+ASSERT_OFFSET(struct Renderer, context, 0x12b0);
+
+extern void append_wait(
+ struct Batch *batch, uintptr_t semaphore, uint32_t stages, uint64_t value
+) __attribute__((visibility("hidden")));
+
+// The patch calls this function at the beginning of a method in the "Facet"
+// `renderer` which is called, among other things, on `texture`s that the current
+// submission batch will use as shader inputs (e.g. eye textures).
+void register_image_wait(struct Renderer *renderer, struct Texture *texture)
+{
+ // First we need the completion semaphore for the texture.
+ struct Image *image = texture->image;
+ if (!image) return;
+
+ // The vbase offset entry for converting `vr::IVRSharedImage *` to
+ // `vr::IVRSharedResource *` is located at `image->vtable - 0x50`.
+ ptrdiff_t resource_offset =
+ *(const ptrdiff_t *)((const char *)image->vtable - 0x50);
+ struct SharedResource *resource = (void *)((char *)image + resource_offset);
+
+ struct Semaphore *semaphore = resource->vtable->get_semaphore(resource);
+ if (!semaphore) return;
+
+ uintptr_t handle = semaphore->vtable->get_handle(semaphore);
+ if (!handle) return;
+
+ // The current batch that is being prepared, and that may need the image
+ // semaphore as a dependency.
+ struct Batch *batch = renderer->context->batch;
+
+ // If the texture we got called on has the same completion semaphore as one
+ // of the completion signals of this batch, then making the batch depend on
+ // the semaphore could create a loop, and that would be bad.
+ for (SemaphoreEntry *p = batch->signals; p != batch->signals_end; p++) {
+ if (p->semaphore == handle) return;
+ }
+
+ // If there's already a wait on this texture's semaphore, ensure it will
+ // block all work in this batch and that the value is at least as high as the
+ // semaphore value required for this texture to be ready.
+ uint64_t value = semaphore->vtable->get_value(semaphore);
+ uint32_t *stages = batch->wait_stages;
+ for (SemaphoreEntry *p = batch->waits; p != batch->waits_end;
+ p++, stages++) {
+ if (p->semaphore != handle) continue;
+
+ if (p->value < value) p->value = value;
+ *stages |= VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
+ return;
+ }
+
+ // Otherwise append a wait on this texture's completion semaphore.
+ append_wait(batch, handle, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, value);
+}
diff --git a/steamvr/image-wait/trampoline.S b/steamvr/image-wait/trampoline.S
new file mode 100644
index 0000000..6c7dbc4
--- /dev/null
+++ b/steamvr/image-wait/trampoline.S
@@ -0,0 +1,26 @@
+/* SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
+ * SPDX-License-Identifier: CC0-1.0
+ */
+
+.intel_syntax noprefix
+.section .text.entry,"ax",@progbits
+.global input_wait
+input_wait:
+ push rdi
+ push rsi
+ push rdx
+ push rcx
+ sub rsp, 8
+ call register_image_wait
+ add rsp, 8
+ pop rcx
+ pop rdx
+ pop rsi
+ pop rdi
+
+ # Replay the three displaced instructions, then resume the original function.
+ push rbp
+ mov rax, rsi
+ mov r9d, edx
+ jmp resume_transition
+.section .note.GNU-stack,"",@progbits
diff --git a/steamvr/patch-steamvr b/steamvr/patch-steamvr
deleted file mode 100755
index f176b0a..0000000
--- a/steamvr/patch-steamvr
+++ /dev/null
@@ -1,216 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
-# SPDX-License-Identifier: CC0-1.0
-
-"""A steamvr launch command wrapper that patches various parts of steamvr
-to fix bugs.
-
-For SteamVR Beta 2.19.1, build 26389675.
-
-Use at your own risk!
-"""
-
-import hashlib
-import os
-import stat
-import subprocess
-import sys
-import tempfile
-from dataclasses import dataclass
-from pathlib import Path
-
-NAME = Path(__file__).name
-MARKER = b"Valve, please fix."
-
-
-@dataclass(frozen=True)
-class Edit:
- offset: int
- replacement: bytes
-
-
-@dataclass(frozen=True)
-class Patch:
- original_hash: bytes
- patched_hash: bytes
- edits: tuple[Edit, ...]
-
- def apply_to(self, data: bytes) -> bytes:
- payload = data.removesuffix(MARKER)
- digest = sha256(payload)
- if digest == self.patched_hash:
- return payload + MARKER
- if digest != self.original_hash:
- if payload != data:
- raise LookupError(
- "already patched with a another set of patches: verify file integrity and restart"
- )
- raise LookupError("unrecognized build: this wrapper may need updating")
- result = bytearray(payload)
- for edit in self.edits:
- end = edit.offset + len(edit.replacement)
- if edit.offset < 0 or end > len(result):
- raise ValueError("patch extends outside the file")
- result[edit.offset : end] = edit.replacement
- if sha256(result) != self.patched_hash:
- raise ValueError("patched file checksum mismatch")
- return bytes(result) + MARKER
-
-
-def patch(
- original_hash: str, patched_hash: str, edits: tuple[tuple[int, str], ...]
-) -> Patch:
- return Patch(
- bytes.fromhex(original_hash),
- bytes.fromhex(patched_hash),
- tuple(
- Edit(offset, bytes.fromhex(replacement)) for offset, replacement in edits
- ),
- )
-
-
-PATCHES = {
- "bin/linux64/vrcompositor": patch(
- "4c380adc27832db9afaf83e446080354338bcb4c96f0f0cc561c90cd4d79168f",
- "a6d073c17489919e838c3d2ac4941a923bc18172a372f3c32bae3433e95f9dbc",
- (
- # Wait for GPU to finish using its resources (see #952)
- (0x404941, "84c00f842f570000eb06"),
- (0x404951, "488dbbe00d0000e90a570000"),
- (0x40A05B, "e830db0600"),
- (0x40A060, "e9dca8ffff9090"),
- # Stop reusing GPU work identifier across frames
- # (allows above patch to work reliably)
- (0x41D224, "31c09090909090"),
- (0x41DB39, "31c09090909090"),
- (0x41DB74, "909090909090"),
- # Fix shader descriptor texture array counts (should be 3, not 1)
- # for bindings 9, 17, and 20. Affected shaders:
- # - distort_none_ps
- # - distort_none_ps_layered
- # - distort_panorama_ps
- # - distort_panorama_solid_color_ps
- # - distort_ps_achromatic_nd
- (0x404961, "498b5500b80300000089424ceb22"),
- (0x404991, "89828c0000008982a4000000eb12"),
- (0x4049B1, "4c8bbd18feffffe902200100"),
- (0x4169B8, "e9a4dffeff9090"),
- ),
- ),
- "bin/linux64/vrserver": patch(
- "a7c9246f8fe36749caa0bfe3984efbb5fa7dbbb28e5263a5c1744f5ba6b0c10b",
- "b14314f1fbfe8acb164583853e32c9a6870317bb9cbb278d9cea21862aaebd8f",
- (
- # Remove logger calls in forked processes (can cause a deadlock and
- # make the process linger preventing SteamVR from starting again)
- (0x2C1386, "9090909090"),
- (0x2C1577, "9090909090"),
- (0x2C160C, "9090909090"),
- (0x2C1726, "9090909090"),
- (0x2C17FF, "9090909090"),
- (0x2C1699, "9090909090"),
- ),
- ),
- "drivers/vrlink/bin/linux64/driver_vrlink.so": patch(
- "a33d1da2d1c9851a61e19b87ffc89bbbb6e8b1e503e189e3e19bccd2e6cd3b7f",
- "c7d8adbbcd44d577f7891c15e53d6000eaf0b257fde642b09ff286bd00be9468",
- (
- # Keep increasing (mod(2^16)) the ACK cutoff instead of clearing it
- # (#965)
- (0xD91DD, "89836c0d000090909090"),
- # Wait for encoder to finish before reconfiguring it
- # (ulVideoTimeline == ulTimelineSequence) (#965)
- (0x13C2D2, "83bb4c090000007458e9702c0000"),
- (0x13C333, "488b8328090000e9ec210000"),
- (0x13E524, "e9a9ddffff9090"),
- # Clean up failed encoded frames so the above fix doesn't wait
- # forever when these trigger a reset
- (0x13D735, "e927000000"),
- (0x13D761, "488b9d70ffffff4c01f3e9d6feffff"),
- # Reset tracking and encoding when starting a new streaming session
- (0x1129F1, "57488b7f184885ff740feb04"),
- (0x112A01, "488b076a015eff50285fe91013feff"),
- (0x11A251, "4c89a30804000031c0894308c3"),
- (0x11A297, "e8b5ffffff6690"),
- (0x11A2AA, "e84287ffff"),
- # Ignore stale tracking IDs and repeated older attempts
- (0x11B301, "89d02b47084189c6c1e004"),
- (0x11B30C, "0f8853050000"),
- (0x11B614, "0f841719fdff"),
- (0xECF31, "410fb644240988430ee926e90200"),
- ),
- ),
-}
-
-
-def sha256(data):
- return hashlib.sha256(data).digest()
-
-
-def notify(message):
- print(f"{NAME}: {message}", file=sys.stderr)
- try:
- subprocess.run(
- ["notify-send", NAME, message],
- check=False,
- timeout=3,
- stdout=subprocess.DEVNULL,
- stderr=subprocess.DEVNULL,
- )
- except (OSError, subprocess.TimeoutExpired):
- pass
-
-
-def replace(path, data):
- mode = stat.S_IMODE(path.stat().st_mode)
- with tempfile.NamedTemporaryFile(
- dir=path.parent, prefix=path.name + ".", delete=False
- ) as f:
- temporary = Path(f.name)
- try:
- f.write(data)
- f.flush()
- os.fchmod(f.fileno(), mode)
- os.fsync(f.fileno())
- os.replace(temporary, path)
- finally:
- temporary.unlink(missing_ok=True)
-
-
-def main(command):
- diagnostics = []
- if not command:
- print(f"Usage: {NAME} command [args...]", file=sys.stderr)
- return 2
- try:
- root = Path.cwd()
- pending = []
- for relative, patch in PATCHES.items():
- path = (root / relative).resolve()
- try:
- original = path.read_bytes()
- except FileNotFoundError as e:
- diagnostics.append(f"Skipped `{relative}`: {e}")
- continue
- try:
- updated = patch.apply_to(original)
- except ValueError as e:
- raise ValueError(f"Patching `{relative}` failed: {e}") from e
- except LookupError as e:
- diagnostics.append(f"Skipped `{relative}`: {e}")
- continue
- if updated != original:
- pending.append((path, updated))
- for path, updated in pending:
- replace(path, updated)
- diagnostics.append(f"Patched {path}")
- if diagnostics:
- notify("\n".join(diagnostics))
- except (OSError, ValueError) as e:
- notify("\n".join([f"Encountered an error: {e}", *diagnostics]))
- return 1
- os.execvp(command[0], command)
-
-
-if __name__ == "__main__":
- sys.exit(main(sys.argv[1:]))
diff --git a/steamvr/patches.toml b/steamvr/patches.toml
new file mode 100644
index 0000000..a4e618e
--- /dev/null
+++ b/steamvr/patches.toml
@@ -0,0 +1,82 @@
+# SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
+# SPDX-License-Identifier: CC0-1.0
+
+target = "Beta 2.19.1, build 26389675"
+
+[[targets]]
+path = "bin/linux64/vrcompositor"
+sha256 = "4c380adc27832db9afaf83e446080354338bcb4c96f0f0cc561c90cd4d79168f"
+edits = [
+ # Wait for GPU to finish using its resources (see #952)
+ [0x404941, "84c00f842f570000eb06"],
+ [0x404951, "488dbbe00d0000e90a570000"],
+ [0x40a05b, "e830db0600"],
+ [0x40a060, "e9dca8ffff9090"],
+ # Stop reusing GPU work identifier across frames (allows above patch to
+ # work reliably)
+ [0x41d224, "31c09090909090"],
+ [0x41db39, "31c09090909090"],
+ [0x41db74, "909090909090"],
+ # Fix shader descriptor texture array counts (should be 3, not 1) for
+ # bindings 9, 17, and 20. Affected shaders:
+ # - distort_none_ps
+ # - distort_none_ps_layered
+ # - distort_panorama_ps
+ # - distort_panorama_solid_color_ps
+ # - distort_ps_achromatic_nd
+ [0x404961, "498b5500b80300000089424ceb22"],
+ [0x404991, "89828c0000008982a4000000eb12"],
+ [0x4049b1, "4c8bbd18feffffe902200100"],
+ [0x4169b8, "e9a4dffeff9090"],
+]
+[targets.compiled]
+sources = ["image-wait/trampoline.S", "image-wait/image_wait.c"]
+imports = { append_wait = 0x465dd0, resume_transition = 0x40e9d7 }
+hooks = [
+ # Make the compositor wait for frames submitted by the application to
+ # complete rendering before trying to encode them etc. (#1046)
+ { address = 0x40e9d0, length = 7, target = "input_wait" },
+]
+
+[[targets]]
+path = "bin/linux64/vrserver"
+sha256 = "a7c9246f8fe36749caa0bfe3984efbb5fa7dbbb28e5263a5c1744f5ba6b0c10b"
+edits = [
+ # Remove logger calls in forked processes (can cause a deadlock and make
+ # the process linger preventing SteamVR from starting again)
+ [0x2c1386, "9090909090"],
+ [0x2c1577, "9090909090"],
+ [0x2c160c, "9090909090"],
+ [0x2c1726, "9090909090"],
+ [0x2c17ff, "9090909090"],
+ [0x2c1699, "9090909090"],
+]
+
+[[targets]]
+path = "drivers/vrlink/bin/linux64/driver_vrlink.so"
+sha256 = "a33d1da2d1c9851a61e19b87ffc89bbbb6e8b1e503e189e3e19bccd2e6cd3b7f"
+edits = [
+ # Keep increasing (mod(2^16)) the ACK cutoff instead of clearing it (#965)
+ [0xd91dd, "89836c0d000090909090"],
+ # Wait for encoder to finish before reconfiguring it
+ # (ulVideoTimeline == ulTimelineSequence) (#965)
+ [0x13c2d2, "83bb4c090000007458e9702c0000"],
+ [0x13c333, "488b8328090000e9ec210000"],
+ [0x13e524, "e9a9ddffff9090"],
+ # Clean up failed encoded frames so the above fix doesn't wait forever when
+ # these trigger a reset
+ [0x13d735, "e927000000"],
+ [0x13d761, "488b9d70ffffff4c01f3e9d6feffff"],
+ # Reset tracking and encoding when starting a new streaming session
+ [0x1129f1, "57488b7f184885ff740feb04"],
+ [0x112a01, "488b076a015eff50285fe91013feff"],
+ [0x11a251, "4c89a30804000031c0894308c3"],
+ [0x11a297, "e8b5ffffff6690"],
+ [0x11a2aa, "e84287ffff"],
+ # Ignore stale tracking IDs and repeated older attempts
+ [0x11b301, "89d02b47084189c6c1e004"],
+ [0x11b30c, "0f8853050000"],
+ [0x11b614, "0f841719fdff"],
+ [0xecf31, "410fb644240988430ee926e90200"],
+]
+
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000..45312bc
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,182 @@
+# SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
+# SPDX-License-Identifier: CC0-1.0
+
+version = 1
+revision = 5
+requires-python = ">=3.13"
+
+[[package]]
+name = "annotated-types"
+version = "0.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" },
+]
+
+[[package]]
+name = "capstone"
+version = "5.0.9"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/39/01/6516910f546fbb996068207b9dd0229b14bc8dae223114d5e0e27d3cad11/capstone-5.0.9.tar.gz", hash = "sha256:0429af292ddc604d3c9344696807e281d5e728db029f00e6a4ea9e3bff1aac9e", size = 2947300, upload-time = "2026-05-28T16:05:57.968Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/da/f3/43f4dbfc36c7740d512621a9d42b276ac21bb6ca7cb974d4c8a19060d387/capstone-5.0.9-py3-none-macosx_10_9_universal2.whl", hash = "sha256:58a874d9a6cb15122135b1385a782e5b54c8a4d9396161b6498465046d2e0442", size = 2195391, upload-time = "2026-05-28T16:05:45.515Z" },
+ { url = "https://files.pythonhosted.org/packages/70/74/d00de02c62ce864c6b43796c524516ea064e3e6fc1327d452f78270e8323/capstone-5.0.9-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:8c4a1d7134d7d5290e5d77066500385f2c7a4bf66835ccbfde5180042d508b3c", size = 1190392, upload-time = "2026-05-28T16:05:46.953Z" },
+ { url = "https://files.pythonhosted.org/packages/42/9a/4eecfbc94961cda70301fd13de69042be1943bee456e37bc1369840601b1/capstone-5.0.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fc269c5082e17d7f9f265cd6980e2fdc6ab572df913951d76db0357e06b8001d", size = 1202361, upload-time = "2026-05-28T16:05:48.1Z" },
+ { url = "https://files.pythonhosted.org/packages/45/d9/dbeadb9fcd461e608ce79b1cb647fed6baa812fa6e1c68b0ba5ef81291af/capstone-5.0.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41772ad0c71aa5d3e97d541e802593c2e71fb94ac7e20d4202f97ce2a3eb8ed5", size = 1461016, upload-time = "2026-05-28T16:05:49.554Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/39/17747862222bb062e86b501f1f148d5ff589b77908b080d30f7f085cbfb7/capstone-5.0.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:273fd8d747d2e35c88f91450be51a603ecfaafb00d96d9f315dcb8689c86193e", size = 1485316, upload-time = "2026-05-28T16:05:50.721Z" },
+ { url = "https://files.pythonhosted.org/packages/12/56/9a694f60f5b26612039d975c1c84d2ad8fd0ad839b0b1058dbe68dd32b0b/capstone-5.0.9-py3-none-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f5871b57e987d5bf23b531fbe868211b74ad750fa9b830a3d3cdc4956bc0ae3", size = 1483221, upload-time = "2026-05-28T16:05:51.925Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/74/c178ac241892bb3668b633065bdbe6dc719c2aa54668d68a8e23a6753525/capstone-5.0.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:69064219c814ad64af35469a7c4ddd19b730cacb96a1a796435ccde0e1567d05", size = 1459140, upload-time = "2026-05-28T16:05:53.182Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/f3/3865a0371603994f8bf521595b3072080bfe1b715f8bd080988d026dd2a1/capstone-5.0.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0cc3b1ec319ab0530efc6b181491b77dd3373c81b6dbcf2f05a80e3d8dd61d5e", size = 1488205, upload-time = "2026-05-28T16:05:54.31Z" },
+ { url = "https://files.pythonhosted.org/packages/09/a1/39a575aa27a35b43b0ad93065a1803dba39038060eccfb29031d82e3fb2c/capstone-5.0.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cfec34e6e01472fe60850c87c5bea9918c274fa2a605b510e6489bf87c0c9960", size = 1488865, upload-time = "2026-05-28T16:05:55.603Z" },
+ { url = "https://files.pythonhosted.org/packages/50/e6/6f06fdb6a9ed32b2f7cd9c036b92d5324112c3ef7080f2c71efc367d40dd/capstone-5.0.9-py3-none-win_amd64.whl", hash = "sha256:732cedbbb56d42e723f14d7af6387f1454194a820b4b96b56d1e53f865ef85d0", size = 1273459, upload-time = "2026-05-28T16:05:56.73Z" },
+]
+
+[[package]]
+name = "pydantic"
+version = "2.14.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-types" },
+ { name = "pydantic-core" },
+ { name = "typing-extensions" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9f/d5/b4867fa7c7f04c34997d7d8aad744f64d210bb99d67cf18bf3fb1eea1aed/pydantic-2.14.2.tar.gz", hash = "sha256:fb7b2db6cefe8414dd612edeede49c4c433acb0432b0f92b95024e45f66c505d", size = 911596, upload-time = "2026-10-29T16:37:12.476Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/97/01/c71a71258b58659c72504e5fd65b451f6e36e34dc931d0b237ded213c814/pydantic-2.14.2-py3-none-any.whl", hash = "sha256:7efbf7507b937931cd32a5755acb00ea1cf5ccb1d4e8067a5850d5069e382118", size = 490403, upload-time = "2026-10-29T16:37:10.181Z" },
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.50.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/af/8e/23ec1021834e35041fbb2f10946141e6acdfd39379c45cc4d65a4e8a1d98/pydantic_core-2.50.2.tar.gz", hash = "sha256:1b0e8dc3932cf4e9ed5727570325cacc716b9409ffc794a221c0ccc72e8eb775", size = 518711, upload-time = "2026-10-29T16:34:57.465Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c2/fc/ac3ae9d934932ce3c380e7fe53e01372b49ef5dcb49db56f1a1f005b4d8c/pydantic_core-2.50.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:93eb41d626d9a2749a678823c70bba8c25033cef474ae8001fd7621adbbd8437", size = 2222463, upload-time = "2026-10-29T16:32:32.125Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/0b/7605223654ed4e777d05668e914d32b476b1fa2bb756bff9820fe270456b/pydantic_core-2.50.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c539d452cba22f250103c7d1c00cbe9038eb705e657476e701c43374efd9f2e", size = 2093995, upload-time = "2026-10-29T16:32:33.496Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/29/4a1a653cfedd3f5e1ccd3b1e919fe324fb45607d12c30fdd7ca5af16ba8d/pydantic_core-2.50.2-cp313-cp313-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:53bd25e8ab54c02bb4c55d4848c1d36f75f3b9a7356e83179fe27794d9e9e6c4", size = 2253178, upload-time = "2026-10-29T16:32:34.77Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/f5/03ce5ed01c98b60272cdee8ac1abb819b64ef347717c11d96215e32d9952/pydantic_core-2.50.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f142cd663d1148ed32993ed9a58e5c67ea312f446a01a6c13a956945aafc9f23", size = 2113894, upload-time = "2026-10-29T16:32:39.241Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/bc/f7644474ac0faad9a7fdf5179239893b21339169b8b4f89e3075c4be16b2/pydantic_core-2.50.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:126f158474346f457c0759c29c1a6334acd5f7f99dd272bb27d19314f3d81b4d", size = 2198248, upload-time = "2026-10-29T16:32:42.357Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/8f/3d3a1c1d313e02774f6c034eb1910eb6e795922e66e81f0f053f98afe866/pydantic_core-2.50.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0b94a515f44f1ebcb25395bcdba506c4cdaf6d3fcb0466b9658521b41e5ff057", size = 2368505, upload-time = "2026-10-29T16:32:43.755Z" },
+ { url = "https://files.pythonhosted.org/packages/03/33/38fb4545c00ce2bfebc2a0ffd99f7f580f1e1efb8db3b2faf88def640435/pydantic_core-2.50.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a55c1163e829a08707b4a92c8ee02a5a3a3847ced74aa4da94e3e975ffe1ec63", size = 2451704, upload-time = "2026-10-29T16:32:45.075Z" },
+ { url = "https://files.pythonhosted.org/packages/79/be/b559a1ceaff8b1d4581733d47d2ffa785635ac05716eae4c964466aac6e8/pydantic_core-2.50.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:31a1fb3ffbd1195b24490f671d12623dd4c9bfa56d6af0a299fa43b516134f5b", size = 2241760, upload-time = "2026-10-29T16:32:46.382Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/7c/3c8ab7cb45e4dc7687a4f16a953fee14d2eca18dd90d6be8bd470ef06962/pydantic_core-2.50.2-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:83d6f42ae6eba44548a1a3d4f900a5b1500fdd14f09f04754a8800ab0d544432", size = 2254115, upload-time = "2026-10-29T16:32:47.818Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/ad/3f3471206224cdac2af3492a3f7c724c47a82f5f8850ff364c2e36023497/pydantic_core-2.50.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9fcde11462e0faab4c2a2ae8cd4516f7dd8239eec583bd88c5524d8064ad2d0c", size = 2288598, upload-time = "2026-10-29T16:32:49.404Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/1e/2070b388b1d8633cfb72e3964ac3c95aaeb195c240209894ff386847b494/pydantic_core-2.50.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:848984e231190ae49bc8b5229e64d2a6fba4231dc6046e1b6b28ddc060bc7601", size = 2475116, upload-time = "2026-10-29T16:32:51.414Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/96/e96ff19527c73b9aaddce2a4424006dc15439fd89ef2d39641048c5ee85a/pydantic_core-2.50.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8772c2c0091e6fc2d0499151793d31bf811826986b0727052935a95104845544", size = 2478926, upload-time = "2026-10-29T16:32:52.856Z" },
+ { url = "https://files.pythonhosted.org/packages/35/10/2fefc082eb46f959e80735e859161305c0c2fbb3d5d20ac5547c49a84a3b/pydantic_core-2.50.2-cp313-cp313-win32.whl", hash = "sha256:bd6ecae08c0d871cf93b6f0d4ee76b06c65be44ea22311981a022b2f1948cf9d", size = 2108354, upload-time = "2026-10-29T16:32:54.276Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/dd/14eeec394ec1422f9c4e9743cd746f4502232a7c65eb235d1a03729d94ab/pydantic_core-2.50.2-cp313-cp313-win_amd64.whl", hash = "sha256:e5c801bdf3cc92274b8fc2c7023c8751e5ae81260c009ac558ff55ce712a2d45", size = 2250556, upload-time = "2026-10-29T16:32:55.396Z" },
+ { url = "https://files.pythonhosted.org/packages/75/b7/a014acf1ae4b4aa7d32ff957e2250698637daa3a29448444180360b92284/pydantic_core-2.50.2-cp313-cp313-win_arm64.whl", hash = "sha256:fd9aa71a800c87c0f7ab53fa38009169a23a82c685989626ca74a4bd483e1b67", size = 2162633, upload-time = "2026-10-29T16:32:56.792Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/64/fc525bb981a9f287cfa34346e7ed1d5ef7c043c2b9b517f54c6136032d10/pydantic_core-2.50.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:efad0bdc67d3b119c3bab6b94d341a27333c2bb89ed75dfa182a4a06cd0ea329", size = 2226414, upload-time = "2026-10-29T16:32:58.559Z" },
+ { url = "https://files.pythonhosted.org/packages/db/68/315701095f25687938ab79d263a6d939337456c2840fe69038fd38000338/pydantic_core-2.50.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6ffb4dce3ecb2cd563aa30f9eb5088a1cf1f4f688b90cbb374fa96e49fc50b02", size = 2095041, upload-time = "2026-10-29T16:32:59.873Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/4b/efd3abf31aa24bc97b311735f418886f947b4f193df02a1977643963b03e/pydantic_core-2.50.2-cp314-cp314-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:150f6e101f575c21a4ecfed1be1c05bb89a0ad49cb9c846ed377583bd71b32c2", size = 2257635, upload-time = "2026-10-29T16:33:01.096Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/6c/146b36eed4de8115a0fda76840703e75f5f8b36aeb3af86976bf094d1a17/pydantic_core-2.50.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1db9cab6197e797824f0479dd2ae5fd2e272aa61c61b7faa5a6e450ca7a0d3b7", size = 2116807, upload-time = "2026-10-29T16:33:02.692Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/94/db006ea79bbe405cc38b25899319c7d461ff1813cbdfe6aa90a65ed1dfff/pydantic_core-2.50.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:1b9f652e17a13bbc09b45f74c3c326cca72059ce4dcdef687fcb04389db413a9", size = 2201037, upload-time = "2026-10-29T16:33:03.942Z" },
+ { url = "https://files.pythonhosted.org/packages/41/f9/ea2bc1ec13a67af4a63c9a887fa0aa6ef4ade9018e2bb412d4fc80110e19/pydantic_core-2.50.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:823b222c4fc861a82c650998a6117ecbd150cc71bf8b78f9a31bf3f48c456108", size = 2374904, upload-time = "2026-10-29T16:33:05.458Z" },
+ { url = "https://files.pythonhosted.org/packages/44/8e/26a6a075301f0e563646037ea69bef8c63911d38fbd257f94cec69cdf985/pydantic_core-2.50.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:783abdfac21247d33bec40aa683d26bba30d61655c40de38b2decc4797aabf92", size = 2457626, upload-time = "2026-10-29T16:33:07.271Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/a2/8eeabaf1d394f0d3b9a27129ac21556eabee6f2ddcb418069ef4037e00b0/pydantic_core-2.50.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b29b7c5adf4b2be7c941f7f76432aa3269cd5052ff8610376b8ece24d7b02c51", size = 2245505, upload-time = "2026-10-29T16:33:08.824Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/54/449778cd397e2682b30d1bcfab0872f6ac36702b59cc326296b7f4f14928/pydantic_core-2.50.2-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:4ac996e42a93d80122a23edce44e02c6b9c4913f5c6198a077da31475341a39a", size = 2259563, upload-time = "2026-10-29T16:33:10.107Z" },
+ { url = "https://files.pythonhosted.org/packages/26/c2/2f65691d25488ee0f3c17861d079e8ccb7817955522d07cd3c3970d30af5/pydantic_core-2.50.2-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:99e1d482138a71902415ba375bb4627679a5fefd3bd07803c3a8f37632f4981d", size = 2290470, upload-time = "2026-10-29T16:33:11.273Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/32/aa7c40234cd24536afa0895ff1d26f9f15638004e25242c5193ce053b96b/pydantic_core-2.50.2-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:a53d5f11cd6d15569ba3e53f3498077f0d5e633f6ec604d2ea613e895017ebee", size = 2477272, upload-time = "2026-10-29T16:33:12.771Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/43/c347c68e040cf93d8487a8f4fc28f2768312834c7d21c301538f416f9955/pydantic_core-2.50.2-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23385c4626f8b5a7792aa381ea573d9432cfbf4c21375a0a5eed2a4fb64e0e58", size = 2484352, upload-time = "2026-10-29T16:33:14.485Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/8e/72556126c8b6154fe7acaf757b96b2daa37aeffc3a517deb76e272f5ad09/pydantic_core-2.50.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:9a8a62281fd9c6a373b3fd3d473685e6838d43341eecde5f47729748e2c11ee3", size = 1491226, upload-time = "2026-10-29T16:33:15.603Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/66/004c85511812964a36b3535af022c5c0dfcb760a31e4905b96310e8dd6c8/pydantic_core-2.50.2-cp314-cp314-win32.whl", hash = "sha256:18f008ef58cd30782f81b6e0119e20b7e884edcefbc2d370162ebe033f6e5c99", size = 2113134, upload-time = "2026-10-29T16:33:16.87Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/5f/dcd3df20ab2a7279d42a8415fc184e8e77bcf9aa3c9ac3e938c1300a242c/pydantic_core-2.50.2-cp314-cp314-win_amd64.whl", hash = "sha256:b62c89545d21d930c333208fd548e7b4f262228ba3a322fd75c987a75ab11f31", size = 2253407, upload-time = "2026-10-29T16:33:18.47Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/fb/0685895f5d199b215011dc4eb8ccea032a64ec23dfa88eeb5acca939c2d2/pydantic_core-2.50.2-cp314-cp314-win_arm64.whl", hash = "sha256:6e731bf5a5e8e25398544d7709e39068ec4230b0177e4db7fa955692fd415ba3", size = 2160456, upload-time = "2026-10-29T16:33:19.782Z" },
+ { url = "https://files.pythonhosted.org/packages/84/2f/4c8f67efe62837a508b74cf910904f73029cb211371351c5ceb36d56e85e/pydantic_core-2.50.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:33c6800ab9563adaa0d7893e9cd8f5b972ef1e6a23df70498e05c24f3fcec5e1", size = 2217558, upload-time = "2026-10-29T16:33:21.323Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/73/e0ef88cfbcca5409f57751289bc20be63a58af596a36e94b8cd795e59747/pydantic_core-2.50.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f81df206b937d0dca5efb38c25fc99037d82dbcd3740e34c3f81997d2322ac1c", size = 2074335, upload-time = "2026-10-29T16:33:22.658Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/cc/47844a3179ebce27064e13619594b3fc1a9b4509d8dec1e3960c5f87f5bb/pydantic_core-2.50.2-cp314-cp314t-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:21ea3b00f1e87c66f3f9e92f9f6fec20495ac0e60d7d2e68ba1fc49739d7cf40", size = 2264201, upload-time = "2026-10-29T16:33:24.953Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/d1/30e9d3c86d81884ee3199560ddfa2c1a66a8dc0736441748eae43064abbc/pydantic_core-2.50.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9eff570402175c7d5fff8497d5a9c05580664a79bb66c07b8f205d17f69e580b", size = 2094353, upload-time = "2026-10-29T16:33:26.739Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/8f/392a5999480d0bcab4539db0dafa93265152396fd63e87be4d24e13d6d82/pydantic_core-2.50.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:240fec32a74af0979f7253532859b3fd209ad5eea822416bb010929dba445c3d", size = 2203119, upload-time = "2026-10-29T16:33:28.154Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/80/6e4e276a3fde5500224a39150e06a4f134f136165908be69402720718a36/pydantic_core-2.50.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92acf02c770b9d5c4e9a3e8d64e9d95ae26456a86ebb7b7cf64a97f899e5871b", size = 2392881, upload-time = "2026-10-29T16:33:29.305Z" },
+ { url = "https://files.pythonhosted.org/packages/70/d5/5eb56c6317ab4abf33ef9519a50bfac0c96f7f408bad1bc36da6f27d43e4/pydantic_core-2.50.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1ac5b99bc7b0a68ec64f5f75a6828a7bab5535c8df0cdc07901deb50e0815910", size = 2457290, upload-time = "2026-10-29T16:33:30.587Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/9e/f1fe9384bb37e4e728c51c79151808deacf7b2502e245f50b57c85eeb199/pydantic_core-2.50.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:713d7c33c0e231e3786eb0ec95885814dd65d5f3a09754399b6acbf5e9365f11", size = 2241568, upload-time = "2026-10-29T16:33:32.04Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/f1/cffc4f8456c35690bcb65a8f2304afbf2f519766345a28aba553d5a55cc8/pydantic_core-2.50.2-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:61e13100b30d8d25fc0d1f0297014b32941bf0975a3584bd08f3adebb68d1ead", size = 2257185, upload-time = "2026-10-29T16:33:33.36Z" },
+ { url = "https://files.pythonhosted.org/packages/84/34/ccd4df1cc39cb1e42fb7ec86a0dc0a23e8479de04d3012ca6b0bf2279a03/pydantic_core-2.50.2-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:523fe6574aea50f456cbc301c1cdddcbf237939f7657f9af99b4f0f71942b59c", size = 2299384, upload-time = "2026-10-29T16:33:34.799Z" },
+ { url = "https://files.pythonhosted.org/packages/83/60/404b4b0c416a61867ee642cdc1450bca4d8a9a10e5f5f21e1ecaad05fdc8/pydantic_core-2.50.2-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:6c25b8826be85714b27dafe908d3461e1b09e3e31c0279a7029040c832c781ef", size = 2479384, upload-time = "2026-10-29T16:33:36.106Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/0d/57c81418a2fdfde0578c71396df773965adebd4421d9985a0f8dcbe9ec4d/pydantic_core-2.50.2-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a59db03b80a156a6fbeae41096866d1cb89cdf31031ba44c768a804538880636", size = 2504616, upload-time = "2026-10-29T16:33:37.423Z" },
+ { url = "https://files.pythonhosted.org/packages/08/66/dec78e28a604f3f4985f5a795d4da3e5fef78b0921aeb7f614ed7c780a32/pydantic_core-2.50.2-cp314-cp314t-win32.whl", hash = "sha256:0d090ae76d5b09c003600c7779b3adcf3d1b49268f616c5c51d844e54c8e318d", size = 2093179, upload-time = "2026-10-29T16:33:38.705Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/ea/79d9ee60648463804145d44b08f1e7e2474427d9704e3331d814483908b3/pydantic_core-2.50.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9b01a092955c1c7530196ad526755cff4f2d11f37ca9a6fc9ebe37ca49a48977", size = 2220575, upload-time = "2026-10-29T16:33:39.929Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/01/aa6a7bd017c3228a41942da4fd788b86fb4aad9677212a4737a0c157fe74/pydantic_core-2.50.2-cp314-cp314t-win_arm64.whl", hash = "sha256:4ea01f30e119f1ec7a2034e85c40f491d469896aef02cd6436fd9292fe1be3fb", size = 2160866, upload-time = "2026-10-29T16:33:41.237Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/7f/ec2974015b4be5e50266ce732b8942d3d0b3bff751ace8900999c6577bce/pydantic_core-2.50.2-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:a3317630e2791f56071b82f89bb71624146125e4311e6643f708c6ba744b5ac5", size = 2230936, upload-time = "2026-10-29T16:33:43.656Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/ff/af99a09eddc348c1d0744c0df28dfcd4839ca497067f0fa2168208b41b1b/pydantic_core-2.50.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:f73930dacad5e611fe019e18df6f53c30629b1843d038d303b8dec86a32493bd", size = 2100409, upload-time = "2026-10-29T16:33:44.946Z" },
+ { url = "https://files.pythonhosted.org/packages/84/75/8db3c46f02d6005277e5c2ff5933c85e7e1eb8798aa334c676c828650c3f/pydantic_core-2.50.2-cp315-cp315-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:e2e6240a0a5ad847017b3d65187877fdb7a11d5a58d43c30736f9f523478913b", size = 2259728, upload-time = "2026-10-29T16:33:46.461Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/9d/98c8d524921ca24e5c8a81a4b8f12209ac6ab05d0dd58b374e357da0ffc5/pydantic_core-2.50.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1fdf5b52ed22f1404c2433d0b46df2d86d9befb742d0b9b4042a2325c43cdb97", size = 2121771, upload-time = "2026-10-29T16:33:47.952Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/ea/68c3b1fa919946012c940dea88e94e33e7dde46abe3e3d57053793c9bab8/pydantic_core-2.50.2-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:4e9392ddf67fe97ab9889f796d468c9908a8754b8b6c7520eb03a65012b9a1e7", size = 2196328, upload-time = "2026-10-29T16:33:49.131Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/9b/cfb6312a5b65b02eab6bc00fb0d258d0e2e49468dab29b430e7cb49b2ceb/pydantic_core-2.50.2-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:48e944091b7bff233920bed8a60b0ed6c96c73d3c5836bf51c4b9a4bfb58e4ca", size = 2380152, upload-time = "2026-10-29T16:33:50.503Z" },
+ { url = "https://files.pythonhosted.org/packages/41/5a/7fcced1d84ea294200abfa93e488f36d00732101d17bf5e1f1389e66de26/pydantic_core-2.50.2-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:91c92856b846aa690ea6239ffd6c5003c5e3e296a69095570bf4efee5bfa0bdc", size = 2462299, upload-time = "2026-10-29T16:33:51.884Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/d0/dd0e355694094f0045922f4da60ac9bc7a314babc494e46089fb00640fd3/pydantic_core-2.50.2-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5299296ff5cafcb69851fd652de8f9268a0bb036bcbedbfcaa94cf743baf3986", size = 2249999, upload-time = "2026-10-29T16:33:53.333Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/79/f5f4b36434604d22aacb7152667f87a470e68847c89b136c68a278cf409d/pydantic_core-2.50.2-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:d3d049b557875f9d15230eec8a731749a7a873d8ed6a051903b05bdf46e0e65f", size = 2262745, upload-time = "2026-10-29T16:33:54.79Z" },
+ { url = "https://files.pythonhosted.org/packages/14/21/a4c47a286745857758a4bb8d4d5e260c736a7bd6fab5da618f5711d1a3f1/pydantic_core-2.50.2-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b0b25d647036972c2951059046cb498f72b7af5a2def27bf8e5f44c3067ab7dd", size = 2293663, upload-time = "2026-10-29T16:33:56.132Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/db/25330e609fd219ce16d0a089af1b4b62095f01595dbe6972f7a7dfc313b9/pydantic_core-2.50.2-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:8835566b3e94026da3ce2249f9ee35c9d2ebe4cd131f6a39bfc82708d3010a46", size = 2473247, upload-time = "2026-10-29T16:33:57.607Z" },
+ { url = "https://files.pythonhosted.org/packages/53/16/704ca88b82156dc485da2dfb11d5d52a10b9ef15a294639c0aa5ebd1643e/pydantic_core-2.50.2-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:3fbcd402e4b607d4b8de5d4591215f31f042af03f9d09e58f693a6221e32833f", size = 2490536, upload-time = "2026-10-29T16:33:59.297Z" },
+ { url = "https://files.pythonhosted.org/packages/50/34/1fdd0eebee9f8b6c4ec3c2490f49dc59a652c8c5a7fa0cbcaada0d91cda9/pydantic_core-2.50.2-cp315-cp315-win32.whl", hash = "sha256:8431ecb131489cbdaf6f51f7ade47475f73aff4f1912552f75ba19a6cfe233a8", size = 2117204, upload-time = "2026-10-29T16:34:00.514Z" },
+ { url = "https://files.pythonhosted.org/packages/24/6b/702adc91486fb96ba55675d58ee15ddc38fb48553fb35e26fd005e303206/pydantic_core-2.50.2-cp315-cp315-win_amd64.whl", hash = "sha256:04cf672960b8c4fc28bb159c8cdd101fbd311c520237a5c0360448b851adfb41", size = 2261064, upload-time = "2026-10-29T16:34:01.773Z" },
+ { url = "https://files.pythonhosted.org/packages/36/0d/7f117432b8614526aa0d96fadd2b7fb7393156ddf2caf26a6fe606e6cfb6/pydantic_core-2.50.2-cp315-cp315-win_arm64.whl", hash = "sha256:36917e29c0b815d6b940bc91560fcf453b5b85d55c579796373f0ea11d1c111a", size = 2161783, upload-time = "2026-10-29T16:34:02.966Z" },
+ { url = "https://files.pythonhosted.org/packages/45/1c/2ba7a34fe8da4c83ec200fb9f85ab4d54b961be08367922743623f987849/pydantic_core-2.50.2-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:bd1fe69056a19c377a6c6de2c1240fb8676ba0512a969965efb486510c5e0f1f", size = 2223184, upload-time = "2026-10-29T16:34:04.214Z" },
+ { url = "https://files.pythonhosted.org/packages/47/01/f71a8da1f53a5be1787d2a97ba6fd9adba115305c7f15ea0bf733ed8336c/pydantic_core-2.50.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:9b2383933b1dd7a8c93e02f99ebfd305f1286057172483b8e6898bed3fef69d3", size = 2078174, upload-time = "2026-10-29T16:34:05.426Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/0a/847e1f5427372692f6eb5c38261fed6b8cedf379e37d9f6134d48b9c8ee9/pydantic_core-2.50.2-cp315-cp315t-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:a1b4d60bad57d6e0e7cd30cc4d1dc646d258dd6dfb7935ce76c7dd72dd1e98b0", size = 2268813, upload-time = "2026-10-29T16:34:06.733Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/bb/12966dc82b5e9c347981658b52e24349b6242902b644725c00ee7bdf4267/pydantic_core-2.50.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:93bedc68c4138088ea3c042189c01d2945f530f0dcc36e1ea02df8418bd4f1cd", size = 2098331, upload-time = "2026-10-29T16:34:08.69Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/fe/340416b7ebe664524e7ada16ad2e3e069888b168010d75bda6a502bf2bf8/pydantic_core-2.50.2-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:d684ff87d00de2f6abcbbfa9b0fdce8b513dd799a900809dcfa7eb62ad69832a", size = 2197432, upload-time = "2026-10-29T16:34:10.091Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/4d/425162c3d88af0d190c4dc4f940402cc6dd44d934359a37f50da6b7e4672/pydantic_core-2.50.2-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bb6d49f00e2babd40af4889d6b9f4bf50879f522dd4e2e22cb705cac5e0c5e8e", size = 2399300, upload-time = "2026-10-29T16:34:11.462Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/fe/2882bddf2e2038c8f41fa69f16ddeb6eeb1c002cc0cc0eb2157f73bb34e9/pydantic_core-2.50.2-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6d47b2d0e00e030e0ebdb08115a37da7294e7a6d196d134a77ed7ee6c142284b", size = 2462672, upload-time = "2026-10-29T16:34:12.79Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/ac/d7e6bfc6a95573647d1a699e19228dd99914ae6aab79a2bec293a038e980/pydantic_core-2.50.2-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:95838be125133fa314753f8cc5168f7c41e3a9dd5eef5383dbbbeed511bd4853", size = 2246293, upload-time = "2026-10-29T16:34:14.065Z" },
+ { url = "https://files.pythonhosted.org/packages/74/64/97473ccaeb4ae5eb338162bd8479af121484e67f7078499730e3ee0a5592/pydantic_core-2.50.2-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:2d4a043c820363359e40ac96270e1915e5376e116be1f59cee0701775de3e376", size = 2259507, upload-time = "2026-10-29T16:34:15.391Z" },
+ { url = "https://files.pythonhosted.org/packages/48/15/78a9b7a6a0d0679ce1e46cdd38ff95afd18dd2d6a01ed87c80a01bc003f7/pydantic_core-2.50.2-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:57bccd3e1128736c792094da1feac6da46ec3feb1957ef649a9b3e4f8de6fa04", size = 2303550, upload-time = "2026-10-29T16:34:17.162Z" },
+ { url = "https://files.pythonhosted.org/packages/39/ba/b697173f82e99cdcf5ec6654b2edb66a00f2f92ef9d9a168861fca3fcded/pydantic_core-2.50.2-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:24120c644322a35b5eb765416ac8442214c45689e255f5fc6e761e7e7c743c8b", size = 2474919, upload-time = "2026-10-29T16:34:18.841Z" },
+ { url = "https://files.pythonhosted.org/packages/71/10/986be76723784ae87f9664658fae39df340eaa21681cea95b6339d2e1b3e/pydantic_core-2.50.2-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:b1adf2f37521b578c438f6ec61d1c1d0c20b076863ddd5d8b6f3ab658857db3f", size = 2509095, upload-time = "2026-10-29T16:34:20.16Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/0b/38dbd38c67de7642f87348da70122b89ca4c5fe8227cfdd5cc00c1cdb454/pydantic_core-2.50.2-cp315-cp315t-win32.whl", hash = "sha256:d79aaa468193c1bed6b0560d3c5c9f9e369e3efb0b128b38a2537e2d51d42773", size = 2096972, upload-time = "2026-10-29T16:34:21.715Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/df/154baf8413b08d6daf2f5dec211c303ec6bed50c032717f354f8dd85c994/pydantic_core-2.50.2-cp315-cp315t-win_amd64.whl", hash = "sha256:cc488daac19e6f5292180fac5e0ef7efffabeef4e096af9a17a75a7d3d6781f6", size = 2226659, upload-time = "2026-10-29T16:34:23.177Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/17/eea7a1e7d98a79ef11b6d31748b70c58f8723f87d7ee42edbb6a101f6245/pydantic_core-2.50.2-cp315-cp315t-win_arm64.whl", hash = "sha256:91315aea7adb51821d4a3378ede953bfdd76e3bf71551de4a82d7638cef7dc02", size = 2166387, upload-time = "2026-10-29T16:34:24.433Z" },
+]
+
+[[package]]
+name = "pyelftools"
+version = "0.33"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a3/11/767522582afab1b884d277de0e6e011640cb9d7292a38694b4b1a1df1ae8/pyelftools-0.33.tar.gz", hash = "sha256:660d82dcbeb8e83d1702bd97f223f761625da06111c0cc988eac6b8ab0c1b61f", size = 15068655, upload-time = "2026-05-29T12:56:22.553Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/46/2a/f9697576603dae937727827505a6126a066affb227034e77e6f9068910da/pyelftools-0.33-py3-none-any.whl", hash = "sha256:f215ad5f47d3f1373a21496a6c9e0707c622840d0622f23ff7ce08678b020036", size = 201178, upload-time = "2026-05-29T12:56:20.587Z" },
+]
+
+[[package]]
+name = "steamvr-patches"
+version = "0.1.0"
+source = { editable = "." }
+dependencies = [
+ { name = "capstone" },
+ { name = "pydantic" },
+ { name = "pyelftools" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "capstone", specifier = ">=5,<6" },
+ { name = "pydantic", specifier = ">=2.12,<3" },
+ { name = "pyelftools", specifier = ">=0.32,<1" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" },
+]