mirror of
https://github.com/ovrport/app.git
synced 2026-10-26 19:19:56 -04:00
feat: Initial commit
feat: Main screen feat: Patcher beginnings chore: Rename project feat: Bottom navigation bar chore: Adaptive icon feat: Shaping patching feat: Integrate aapt2 feat: Integrating ARSCLib feat: JSON manifest processing feat: Migrating to KMP feat: Generate ovrport config feat: Remove localized app names feat: Drag and drop refactor: Improve reliability chore: Prepare for release feat: Update detection chore: README and LICENSE feat: Replace smali compiler chore: Code cleanup
This commit is contained in:
commit
5fc0e8fef4
52 files changed
+3344
No files matched your search
+22
@@ -0,0 +1,22 @@
|
|||||||
|
*.iml
|
||||||
|
.kotlin
|
||||||
|
.gradle
|
||||||
|
**/build/
|
||||||
|
xcuserdata
|
||||||
|
!src/**/build/
|
||||||
|
local.properties
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
*.xcodeproj/*
|
||||||
|
!*.xcodeproj/project.pbxproj
|
||||||
|
!*.xcodeproj/xcshareddata/
|
||||||
|
!*.xcodeproj/project.xcworkspace/
|
||||||
|
!*.xcworkspace/contents.xcworkspacedata
|
||||||
|
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||||
|
*.dm
|
||||||
|
*.apk
|
||||||
|
composeApp/release/output-metadata.json
|
||||||
|
*.salive
|
||||||
@@ -0,0 +1,674 @@
|
|||||||
|
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>.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<p align="center"><img src="assets/overport.svg" alt="overport logo"></p>
|
||||||
|
|
||||||
|
# ovrport/app
|
||||||
|
|
||||||
|
overport (aka ovrport) app is used to patch Android games, that are targeting Quest headsets. Functionality will be expanded in the future.
|
||||||
|
|
||||||
|
Powered by [Compose Multiplatform](https://www.jetbrains.com/compose-multiplatform/) and [ARSCLib](https://github.com/REAndroid/ARSCLib). [Builds](https://github.com/ovrport/app/releases) available for Windows and Android.
|
||||||
|
|
||||||
|
# Issues
|
||||||
|
|
||||||
|
Please, report issues related only to app itself. If you have any issues regarding game compatibility, see [ovrport/tracker](https://github.com/ovrport/tracker).
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
overport app is licensed under the GPLv3 license. Please see the [`LICENSE`](https://github.com/ovrport/app/blob/master/LICENSE) file for more information.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg width="77.095mm" height="16.497mm" version="1.1" viewBox="0 0 77.095 16.497" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m9.8487 7.9437q0 1.1811-0.34289 2.1717-0.34289 0.97153-0.99058 1.6764-0.64769 0.68579-1.5621 1.0668-0.89534 0.38099-2.0193 0.38099-1.1049 0-2.0193-0.38099-0.89534-0.38099-1.543-1.0668-0.64769-0.70484-1.0096-1.6764-0.36194-0.99058-0.36194-2.1717t0.36194-2.1526q0.38099-0.97153 1.0287-1.6573 0.66674-0.68579 1.5621-1.0668 0.91439-0.38099 1.9812-0.38099 1.0858 0 1.9812 0.38099 0.91439 0.38099 1.5621 1.0668t1.0096 1.6573 0.36194 2.1526zm-2.8956 0q0-1.3144-0.53339-2.0574-0.51434-0.76199-1.4859-0.76199t-1.5049 0.76199q-0.53339 0.74294-0.53339 2.0574 0 1.3144 0.53339 2.0955 0.53339 0.76199 1.5049 0.76199t1.4859-0.76199q0.53339-0.78104 0.53339-2.0955zm7.5437 5.0291q-0.97154-1.905-1.9812-4.4576-0.99058-2.5527-1.8859-5.5625h3.0099q0.1905 0.74294 0.43814 1.6192 0.2667 0.85724 0.53339 1.7526 0.28575 0.87629 0.55244 1.7145 0.28575 0.83819 0.55244 1.524 0.24765-0.68579 0.53339-1.524 0.28575-0.83819 0.55244-1.7145 0.28575-0.89534 0.53339-1.7526 0.2667-0.87629 0.45719-1.6192h2.9337q-0.89534 3.0099-1.905 5.5625-0.99058 2.5527-1.9621 4.4576zm22.936-7.5056q-0.38099-0.095248-0.89534-0.1905-0.51434-0.1143-1.1049-0.1143-0.2667 0-0.64769 0.057149-0.36194 0.038099-0.55244 0.095249v7.658h-2.8384v-9.4868q0.76199-0.2667 1.7907-0.49529 1.0477-0.24765 2.3241-0.24765 0.2286 0 0.55244 0.038099 0.32384 0.01905 0.64769 0.076199 0.32384 0.038099 0.64769 0.1143 0.32384 0.057149 0.55244 0.1524zm8.42 2.5717q0-1.3144-0.59054-2.0955-0.59054-0.80009-1.7526-0.80009-0.38099 0-0.70484 0.038099-0.32384 0.01905-0.53339 0.057149v5.1244q0.2667 0.17145 0.68579 0.28574 0.43814 0.1143 0.87629 0.1143 2.0193 0 2.0193-2.7241zm2.8956-0.076199q0 1.162-0.28574 2.1145-0.28575 0.95248-0.83819 1.6383-0.55244 0.68579-1.3716 1.0668-0.81914 0.38099-1.8859 0.38099-0.59054 0-1.1049-0.1143-0.51434-0.1143-0.99058-0.32384v3.7718h-2.8384v-13.201q0.38099-0.1143 0.87629-0.20955 0.49529-0.1143 1.0287-0.1905 0.55244-0.076199 1.1049-0.1143 0.57149-0.057149 1.0858-0.057149 1.2382 0 2.2098 0.38099 0.97153 0.36194 1.6383 1.0477 0.66674 0.66674 1.0096 1.6383 0.36194 0.97153 0.36194 2.1717zm11.563-0.01905q0 1.1811-0.3429 2.1717-0.34289 0.97153-0.99058 1.6764-0.64769 0.68579-1.5621 1.0668-0.89534 0.38099-2.0193 0.38099-1.1049 0-2.0193-0.38099-0.89534-0.38099-1.543-1.0668-0.64769-0.70484-1.0096-1.6764-0.36194-0.99058-0.36194-2.1717t0.36194-2.1526q0.38099-0.97153 1.0287-1.6573 0.66674-0.68579 1.5621-1.0668 0.91439-0.38099 1.9812-0.38099 1.0858 0 1.9812 0.38099 0.91439 0.38099 1.5621 1.0668 0.64769 0.68579 1.0096 1.6573 0.36194 0.97153 0.36194 2.1526zm-2.8956 0q0-1.3144-0.53339-2.0574-0.51434-0.76199-1.4859-0.76199-0.97153 0-1.5049 0.76199-0.53339 0.74294-0.53339 2.0574 0 1.3144 0.53339 2.0955 0.53339 0.76199 1.5049 0.76199 0.97154 0 1.4859-0.76199 0.53339-0.78104 0.53339-2.0955zm11.125-2.4765q-0.38099-0.095248-0.89534-0.1905-0.51434-0.1143-1.1049-0.1143-0.2667 0-0.64769 0.057149-0.36194 0.038099-0.55244 0.095249v7.658h-2.8384v-9.4868q0.76199-0.2667 1.7907-0.49529 1.0477-0.24765 2.3241-0.24765 0.2286 0 0.55244 0.038099 0.32384 0.01905 0.64769 0.076199 0.32384 0.038099 0.64769 0.1143 0.32384 0.057149 0.55244 0.1524zm1.905-5.0101 2.8384-0.45719v2.9527h3.4099v2.3622h-3.4099v3.5242q0 0.89534 0.3048 1.4287 0.32384 0.53339 1.2763 0.53339 0.45719 0 0.93344-0.0762 0.49529-0.09525 0.89534-0.24765l0.40004 2.2098q-0.51434 0.20955-1.143 0.36194-0.62864 0.1524-1.543 0.1524-1.162 0-1.924-0.3048-0.76199-0.32384-1.2192-0.87628-0.45719-0.57149-0.64769-1.3716-0.17145-0.80009-0.17145-1.7716z" stroke-width=".26458" aria-label="ov rport"/><path d="m29.849 2.4418c0.23377 0.13497 0.29456 0.44614 0.1262 0.66062l-3.046 3.8875 2.3377 1.3497c0.58776 0.33934-0.06881 0.62819-0.09373 0.64052-1.7015 1.0036-4.2364 2.4789-7.6029 4.4536-0.13619 0.08166-0.31221 0.078-0.44579 8.86e-4 -0.23377-0.13497-0.29456-0.44614-0.1262-0.66063l3.046-3.8875-2.3444-1.3535c-0.26717-0.15425-0.34084-0.36598-0.01266-0.59507 4.4109-2.5651 6.9784-4.0661 7.716-4.4952 0.13619-0.08166 0.30552-0.08186 0.44579-8.85e-4z" stroke-width=".77121"/></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,10 @@
|
|||||||
|
plugins {
|
||||||
|
// this is necessary to avoid the plugins to be loaded multiple times
|
||||||
|
// in each subproject's classloader
|
||||||
|
alias(libs.plugins.androidApplication) apply false
|
||||||
|
alias(libs.plugins.androidLibrary) apply false
|
||||||
|
alias(libs.plugins.composeMultiplatform) apply false
|
||||||
|
alias(libs.plugins.composeCompiler) apply false
|
||||||
|
alias(libs.plugins.kotlinMultiplatform) apply false
|
||||||
|
alias(libs.plugins.kotlinSerialization) apply false
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
|
val appVersion: String by project
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.kotlinMultiplatform)
|
||||||
|
alias(libs.plugins.androidApplication)
|
||||||
|
alias(libs.plugins.composeMultiplatform)
|
||||||
|
alias(libs.plugins.composeCompiler)
|
||||||
|
alias(libs.plugins.kotlinSerialization)
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
androidTarget {
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget.set(JvmTarget.JVM_11)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jvm("desktop")
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
val desktopMain by getting
|
||||||
|
|
||||||
|
androidMain.dependencies {
|
||||||
|
implementation(compose.preview)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
|
}
|
||||||
|
commonMain.dependencies {
|
||||||
|
implementation(compose.runtime)
|
||||||
|
implementation(compose.foundation)
|
||||||
|
implementation(compose.material3)
|
||||||
|
implementation(compose.ui)
|
||||||
|
implementation(compose.components.resources)
|
||||||
|
implementation(compose.components.uiToolingPreview)
|
||||||
|
implementation(libs.androidx.lifecycle.viewmodel)
|
||||||
|
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||||
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
|
implementation(compose.materialIconsExtended)
|
||||||
|
implementation(libs.android.tools.build)
|
||||||
|
implementation(libs.bouncycastle.pkix)
|
||||||
|
implementation(libs.kotlinx.serialization.json)
|
||||||
|
implementation(fileTree("libs"))
|
||||||
|
}
|
||||||
|
desktopMain.dependencies {
|
||||||
|
implementation(compose.desktop.currentOs)
|
||||||
|
implementation(libs.kotlinx.coroutines.swing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "moe.crx.ovrport"
|
||||||
|
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "moe.crx.ovrport"
|
||||||
|
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||||
|
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
||||||
|
versionCode = appVersion.split('.').run { get(0).toInt() * 1000000 + get(1).toInt() * 1000 + get(2).toInt() }
|
||||||
|
versionName = appVersion
|
||||||
|
}
|
||||||
|
packaging {
|
||||||
|
resources {
|
||||||
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||||
|
excludes += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
isShrinkResources = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
debugImplementation(compose.uiTooling)
|
||||||
|
}
|
||||||
|
|
||||||
|
compose.desktop {
|
||||||
|
application {
|
||||||
|
mainClass = "moe.crx.ovrport.MainKt"
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
proguard {
|
||||||
|
configurationFiles.from("proguard-rules.pro")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeDistributions {
|
||||||
|
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||||
|
packageName = "overport"
|
||||||
|
packageVersion = appVersion
|
||||||
|
|
||||||
|
windows {
|
||||||
|
shortcut = true
|
||||||
|
perUserInstall = true
|
||||||
|
iconFile.set(file("icons/app-icon.ico"))
|
||||||
|
}
|
||||||
|
linux {
|
||||||
|
shortcut = true
|
||||||
|
iconFile.set(file("icons/app-icon.png"))
|
||||||
|
}
|
||||||
|
macOS {
|
||||||
|
dockName = "overport"
|
||||||
|
iconFile.set(file("icons/app-icon.icns"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
Binary file not shown.
Vendored
Whitespace-only changes.
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:extractNativeLibs="true"
|
||||||
|
android:theme="@style/Theme.Overport"
|
||||||
|
tools:targetApi="31">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/Theme.Overport">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data android:name="pico.vr.position" android:value="near"/>
|
||||||
|
</activity>
|
||||||
|
<meta-data android:name="isPUI" android:value="1"/>
|
||||||
|
<meta-data android:name="pvr.vrshell.mode" android:value="1"/>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package moe.crx.ovrport
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import moe.crx.ovrport.patch.CantDecodeApkException
|
||||||
|
import moe.crx.ovrport.patch.CantUpdateOverportException
|
||||||
|
import moe.crx.ovrport.patch.Constants
|
||||||
|
import moe.crx.ovrport.ui.AppContent
|
||||||
|
import moe.crx.ovrport.ui.MainViewModel
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import overportapp.composeapp.generated.resources.*
|
||||||
|
import overportapp.composeapp.generated.resources.Res
|
||||||
|
import overportapp.composeapp.generated.resources.apk_file_exported
|
||||||
|
import overportapp.composeapp.generated.resources.cant_read_apk
|
||||||
|
import overportapp.composeapp.generated.resources.cant_update_overport
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
setContent {
|
||||||
|
ActivityContent(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ActivityContent(activity: MainActivity) {
|
||||||
|
val viewModel: MainViewModel = viewModel { MainViewModel() }
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
val cantReadApkMessage = stringResource(Res.string.cant_read_apk)
|
||||||
|
val apkFileExportedMessage = stringResource(Res.string.apk_file_exported)
|
||||||
|
val cantUpdateOverportMessage = stringResource(Res.string.cant_update_overport)
|
||||||
|
val unknownErrorMessage = stringResource(Res.string.unknown_error)
|
||||||
|
|
||||||
|
val openApkFile = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
if (it != null) {
|
||||||
|
val stream = activity.contentResolver.openInputStream(it) ?: return@launch
|
||||||
|
val fileName = it.lastPathSegment?.substringAfter(':') ?: "game"
|
||||||
|
|
||||||
|
stream.use { s ->
|
||||||
|
try {
|
||||||
|
viewModel.import(activity.cacheDir, fileName, s)
|
||||||
|
} catch (_: CantDecodeApkException) {
|
||||||
|
viewModel.cancel()
|
||||||
|
snackbarHostState.showSnackbar(cantReadApkMessage)
|
||||||
|
} catch (_: CantUpdateOverportException) {
|
||||||
|
viewModel.cancel()
|
||||||
|
snackbarHostState.showSnackbar(cantUpdateOverportMessage)
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
viewModel.cancel()
|
||||||
|
snackbarHostState.showSnackbar(unknownErrorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val saveApkFile =
|
||||||
|
rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument(Constants.APK_MIMETYPE)) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
if (it != null) {
|
||||||
|
val stream = activity.contentResolver.openOutputStream(it) ?: return@launch
|
||||||
|
|
||||||
|
stream.use { s ->
|
||||||
|
viewModel.export(s)
|
||||||
|
launch {
|
||||||
|
snackbarHostState.showSnackbar(apkFileExportedMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppContent(
|
||||||
|
viewModel = viewModel.apply {
|
||||||
|
setAppIconConverter {
|
||||||
|
FileInputStream(it).use { stream ->
|
||||||
|
BitmapFactory.decodeStream(stream).asImageBitmap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
snackbarHostState = snackbarHostState,
|
||||||
|
onOpen = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
openApkFile.launch(arrayOf(Constants.APK_MIMETYPE))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onConfirm = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.process(it)
|
||||||
|
saveApkFile.launch(viewModel.patchedName())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.cancel()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package moe.crx.ovrport
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
|
||||||
|
actual fun getPlatform(): Platform = AndroidPlatform("Android ${Build.VERSION.SDK_INT}")
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<group android:scaleX="0.6"
|
||||||
|
android:scaleY="0.6"
|
||||||
|
android:translateX="4.8"
|
||||||
|
android:translateY="4.8">
|
||||||
|
<path
|
||||||
|
android:pathData="m17.66,4.873l0,-0C17.963,5.048 18.042,5.452 17.824,5.73L13.874,10.77L16.906,12.52C17.668,12.96 16.816,13.335 16.784,13.351 14.578,14.652 11.291,16.565 6.926,19.125 6.749,19.231 6.521,19.226 6.348,19.126l0,-0c-0.303,-0.175 -0.382,-0.578 -0.164,-0.857L10.134,13.229l-3.04,-1.755c-0.346,-0.2 -0.442,-0.475 -0.016,-0.772 5.719,-3.326 9.048,-5.272 10.005,-5.829 0.177,-0.106 0.396,-0.106 0.578,-0.001z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#000000</color>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">overport</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="Theme.Overport" parent="android:Theme.Material.Light.NoActionBar">
|
||||||
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="77.095dp"
|
||||||
|
android:height="16.497dp"
|
||||||
|
android:viewportWidth="77.095"
|
||||||
|
android:viewportHeight="16.497">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="m9.849,7.944q0,1.181 -0.343,2.172 -0.343,0.972 -0.991,1.676 -0.648,0.686 -1.562,1.067 -0.895,0.381 -2.019,0.381 -1.105,0 -2.019,-0.381 -0.895,-0.381 -1.543,-1.067 -0.648,-0.705 -1.01,-1.676 -0.362,-0.991 -0.362,-2.172t0.362,-2.153q0.381,-0.972 1.029,-1.657 0.667,-0.686 1.562,-1.067 0.914,-0.381 1.981,-0.381 1.086,0 1.981,0.381 0.914,0.381 1.562,1.067t1.01,1.657 0.362,2.153zM6.953,7.944q0,-1.314 -0.533,-2.057 -0.514,-0.762 -1.486,-0.762t-1.505,0.762q-0.533,0.743 -0.533,2.057 0,1.314 0.533,2.095 0.533,0.762 1.505,0.762t1.486,-0.762q0.533,-0.781 0.533,-2.095zM14.497,12.973q-0.972,-1.905 -1.981,-4.458 -0.991,-2.553 -1.886,-5.563h3.01q0.191,0.743 0.438,1.619 0.267,0.857 0.533,1.753 0.286,0.876 0.552,1.714 0.286,0.838 0.552,1.524 0.248,-0.686 0.533,-1.524 0.286,-0.838 0.552,-1.714 0.286,-0.895 0.533,-1.753 0.267,-0.876 0.457,-1.619h2.934q-0.895,3.01 -1.905,5.563 -0.991,2.553 -1.962,4.458zM37.433,5.467q-0.381,-0.095 -0.895,-0.191 -0.514,-0.114 -1.105,-0.114 -0.267,0 -0.648,0.057 -0.362,0.038 -0.552,0.095v7.658h-2.838v-9.487q0.762,-0.267 1.791,-0.495 1.048,-0.248 2.324,-0.248 0.229,0 0.552,0.038 0.324,0.019 0.648,0.076 0.324,0.038 0.648,0.114 0.324,0.057 0.552,0.152zM45.853,8.039q0,-1.314 -0.591,-2.095 -0.591,-0.8 -1.753,-0.8 -0.381,0 -0.705,0.038 -0.324,0.019 -0.533,0.057v5.124q0.267,0.171 0.686,0.286 0.438,0.114 0.876,0.114 2.019,0 2.019,-2.724zM48.748,7.963q0,1.162 -0.286,2.115 -0.286,0.952 -0.838,1.638 -0.552,0.686 -1.372,1.067 -0.819,0.381 -1.886,0.381 -0.591,0 -1.105,-0.114 -0.514,-0.114 -0.991,-0.324v3.772h-2.838v-13.201q0.381,-0.114 0.876,-0.21 0.495,-0.114 1.029,-0.191 0.552,-0.076 1.105,-0.114 0.571,-0.057 1.086,-0.057 1.238,0 2.21,0.381 0.972,0.362 1.638,1.048 0.667,0.667 1.01,1.638 0.362,0.972 0.362,2.172zM60.311,7.944q0,1.181 -0.343,2.172 -0.343,0.972 -0.991,1.676 -0.648,0.686 -1.562,1.067 -0.895,0.381 -2.019,0.381 -1.105,0 -2.019,-0.381 -0.895,-0.381 -1.543,-1.067 -0.648,-0.705 -1.01,-1.676 -0.362,-0.991 -0.362,-2.172t0.362,-2.153q0.381,-0.972 1.029,-1.657 0.667,-0.686 1.562,-1.067 0.914,-0.381 1.981,-0.381 1.086,0 1.981,0.381 0.914,0.381 1.562,1.067 0.648,0.686 1.01,1.657 0.362,0.972 0.362,2.153zM57.416,7.944q0,-1.314 -0.533,-2.057 -0.514,-0.762 -1.486,-0.762 -0.972,0 -1.505,0.762 -0.533,0.743 -0.533,2.057 0,1.314 0.533,2.095 0.533,0.762 1.505,0.762 0.972,0 1.486,-0.762 0.533,-0.781 0.533,-2.095zM68.541,5.467q-0.381,-0.095 -0.895,-0.191 -0.514,-0.114 -1.105,-0.114 -0.267,0 -0.648,0.057 -0.362,0.038 -0.552,0.095v7.658h-2.838v-9.487q0.762,-0.267 1.791,-0.495 1.048,-0.248 2.324,-0.248 0.229,0 0.552,0.038 0.324,0.019 0.648,0.076 0.324,0.038 0.648,0.114 0.324,0.057 0.552,0.152zM70.446,0.457 L73.284,-0v2.953h3.41v2.362h-3.41v3.524q0,0.895 0.305,1.429 0.324,0.533 1.276,0.533 0.457,0 0.933,-0.076 0.495,-0.095 0.895,-0.248l0.4,2.21q-0.514,0.21 -1.143,0.362 -0.629,0.152 -1.543,0.152 -1.162,0 -1.924,-0.305 -0.762,-0.324 -1.219,-0.876 -0.457,-0.571 -0.648,-1.372 -0.171,-0.8 -0.171,-1.772z"
|
||||||
|
android:strokeWidth=".26458"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="m29.849,2.442c0.234,0.135 0.295,0.446 0.126,0.661l-3.046,3.888 2.338,1.35c0.588,0.339 -0.069,0.628 -0.094,0.641 -1.702,1.004 -4.236,2.479 -7.603,4.454 -0.136,0.082 -0.312,0.078 -0.446,0.001 -0.234,-0.135 -0.295,-0.446 -0.126,-0.661l3.046,-3.888 -2.344,-1.354c-0.267,-0.154 -0.341,-0.366 -0.013,-0.595 4.411,-2.565 6.978,-4.066 7.716,-4.495 0.136,-0.082 0.306,-0.082 0.446,-0.001z"
|
||||||
|
android:strokeWidth=".77121"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="m21.421,0.131c0.505,0.291 0.636,0.964 0.273,1.427l-6.578,8.396l5.049,2.915c1.269,0.733 -0.149,1.357 -0.202,1.383 -3.675,2.167 -9.149,5.354 -16.42,9.618 -0.294,0.176 -0.674,0.168 -0.963,0.002 -0.505,-0.291 -0.636,-0.964 -0.273,-1.427l6.578,-8.396l-5.063,-2.923c-0.577,-0.333 -0.736,-0.79 -0.027,-1.285 9.526,-5.54 15.072,-8.782 16.664,-9.708 0.294,-0.176 0.66,-0.177 0.963,-0.002z"
|
||||||
|
android:fillColor="#fff"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="select_apk_to_begin">Select an APK to begin</string>
|
||||||
|
<string name="or_drag_and_drop_it">(or drag and drop it)</string>
|
||||||
|
<string name="decoding_apk">Processing APK…</string>
|
||||||
|
<string name="unknown_package"><unknown package></string>
|
||||||
|
<string name="cant_read_apk">Selected APK file cannot be parsed.</string>
|
||||||
|
<string name="apk_file_exported">APK file has been successfully exported.</string>
|
||||||
|
<string name="cant_update_overport">overport libraries cannot be downloaded.</string>
|
||||||
|
<string name="unknown_error">Unknown error has been occurred.</string>
|
||||||
|
<string name="update_available">Update available!</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package moe.crx.ovrport
|
||||||
|
|
||||||
|
interface Platform {
|
||||||
|
val name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
class JVMPlatform(override val name: String): Platform
|
||||||
|
|
||||||
|
class AndroidPlatform(override val name: String) : Platform
|
||||||
|
|
||||||
|
expect fun getPlatform(): Platform
|
||||||
|
|
||||||
|
// TODO This should be controlled by Gradle.
|
||||||
|
fun getCurrentVersion(): String = "1.0.0"
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package moe.crx.ovrport.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FrdaAsset(
|
||||||
|
@SerialName("ID")
|
||||||
|
val id: Long,
|
||||||
|
val path: String,
|
||||||
|
val type: String = "store"
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FrdaParameters(
|
||||||
|
val assets: List<FrdaAsset>,
|
||||||
|
val skus: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FrdaInteraction(
|
||||||
|
val parameters: FrdaParameters
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class FrdaConfig(
|
||||||
|
val interaction: FrdaInteraction
|
||||||
|
)
|
||||||
|
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
fun generateConfig(jsonString: String): String {
|
||||||
|
return json.decodeFromString<FrdaConfig>(jsonString).let { config ->
|
||||||
|
json.encodeToString(
|
||||||
|
OverportConfigV1(
|
||||||
|
database = listOf(
|
||||||
|
OverportConfigApplication(
|
||||||
|
"__current__",
|
||||||
|
config.interaction.parameters.skus,
|
||||||
|
config.interaction.parameters.assets.map { OverportConfigAsset(it.id, it.path, it.type) })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package moe.crx.ovrport.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GithubRelease(
|
||||||
|
@SerialName("html_url")
|
||||||
|
var htmlUrl: String,
|
||||||
|
@SerialName("name")
|
||||||
|
val name: String,
|
||||||
|
@SerialName("published_at")
|
||||||
|
val publishedAt: String,
|
||||||
|
)
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package moe.crx.ovrport.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.EncodeDefault
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class OverportConfigLanguage(
|
||||||
|
@SerialName("english_name")
|
||||||
|
var englishName: String = "",
|
||||||
|
@SerialName("native_name")
|
||||||
|
var nativeName: String = "",
|
||||||
|
@SerialName("tag")
|
||||||
|
var tag: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class OverportConfigAsset(
|
||||||
|
@SerialName("asset_id")
|
||||||
|
var assetId: Long = 0,
|
||||||
|
@SerialName("file_path")
|
||||||
|
var filePath: String,
|
||||||
|
@SerialName("asset_type")
|
||||||
|
var assetType: String = "store",
|
||||||
|
@SerialName("language")
|
||||||
|
var language: OverportConfigLanguage = OverportConfigLanguage(),
|
||||||
|
@SerialName("download_status")
|
||||||
|
var downloadStatus: String = "installed",
|
||||||
|
@SerialName("iap_status")
|
||||||
|
var iapStatus: String = "entitled",
|
||||||
|
@SerialName("metadata")
|
||||||
|
var metadata: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class OverportConfigApplication(
|
||||||
|
@SerialName("app_id")
|
||||||
|
var appId: String,
|
||||||
|
@SerialName("products")
|
||||||
|
var products: List<String>,
|
||||||
|
@SerialName("assets")
|
||||||
|
var assets: List<OverportConfigAsset>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@Serializable
|
||||||
|
data class OverportConfigV1(
|
||||||
|
@SerialName("version")
|
||||||
|
@EncodeDefault
|
||||||
|
var version: Int = 1,
|
||||||
|
@SerialName("suggested_texture_width")
|
||||||
|
var suggestedTextureWidth: Int = 0,
|
||||||
|
@SerialName("suggested_texture_height")
|
||||||
|
var suggestedTextureHeight: Int = 0,
|
||||||
|
@SerialName("suggested_cpu_level")
|
||||||
|
var suggestedCpuLevel: Int = 0,
|
||||||
|
@SerialName("suggested_gpu_level")
|
||||||
|
var suggestedGpuLevel: Int = 0,
|
||||||
|
@SerialName("vibration_multiplier")
|
||||||
|
var vibrationMultiplier: Double = 1.0,
|
||||||
|
@SerialName("vibration_frequency_multiplier")
|
||||||
|
var vibrationFrequencyMultiplier: Double = 1.0,
|
||||||
|
@SerialName("database")
|
||||||
|
var database: List<OverportConfigApplication>,
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package moe.crx.ovrport.patch
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
const val APK_MIMETYPE = "application/vnd.android.package-archive"
|
||||||
|
const val OVRPORT_VERSION = "3.2.2"
|
||||||
|
const val LIBRARIES_URL = "https://files.crx.moe/ovrport/tracker/releases/download/$OVRPORT_VERSION/libraries.zip"
|
||||||
|
const val LIBRARIES_DIR = "libraries/$OVRPORT_VERSION"
|
||||||
|
const val RELEASES_URL = "https://api.github.com/repos/ovrport/app/releases"
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package moe.crx.ovrport.patch
|
||||||
|
|
||||||
|
class CantUpdateOverportException : RuntimeException()
|
||||||
|
|
||||||
|
class CantDecodeApkException : RuntimeException()
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package moe.crx.ovrport.patch
|
||||||
|
|
||||||
|
import com.reandroid.json.JSONObject
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
data class Patch(
|
||||||
|
val name: String = "",
|
||||||
|
val desc: String? = null,
|
||||||
|
val executor: PatchExecutor.() -> Unit
|
||||||
|
)
|
||||||
|
|
||||||
|
class FileTransformer(val file: File) {
|
||||||
|
fun replace(input: String, output: String) {
|
||||||
|
file.writeText(Regex(input, RegexOption.DOT_MATCHES_ALL).replace(file.readText(), output))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun find(input: String): Boolean {
|
||||||
|
return Regex(input, RegexOption.DOT_MATCHES_ALL).find(file.readText()) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun append(line: String) {
|
||||||
|
file.appendText(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readJson(): JSONObject? {
|
||||||
|
return runCatching { JSONObject(file.readText()) }.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeJson(json: JSONObject) {
|
||||||
|
file.writeText(json.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replaceHex(input: String, output: String) {
|
||||||
|
val bytes = file.readBytes()
|
||||||
|
val inputBytes = input.split(' ')
|
||||||
|
|
||||||
|
for (i in 0..<bytes.size-inputBytes.size+1) {
|
||||||
|
var matches = true
|
||||||
|
|
||||||
|
for (j in i..<bytes.size) {
|
||||||
|
if (inputBytes[j] != "??" && bytes[j] != inputBytes[j].toByte(16)) {
|
||||||
|
matches = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches) {
|
||||||
|
val replaced = bytes.slice(0..<i) +
|
||||||
|
output.split(' ').map { it.toByte(16) } +
|
||||||
|
bytes.slice(i+inputBytes.size..<bytes.size)
|
||||||
|
file.writeBytes(replaced.toByteArray())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PatchExecutor(private val workingDir: File) {
|
||||||
|
fun selectWorkspace(block: FileTransformer.() -> Unit) {
|
||||||
|
workingDir.run {
|
||||||
|
if (exists()) {
|
||||||
|
FileTransformer(this).block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectFile(fileName: String, block: FileTransformer.() -> Unit) {
|
||||||
|
workingDir.resolve(fileName).run {
|
||||||
|
if (exists()) {
|
||||||
|
FileTransformer(this).block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectResources(block: FileTransformer.() -> Unit) {
|
||||||
|
selectFile("resources/resources.arsc.json", block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectManifest(block: FileTransformer.() -> Unit) {
|
||||||
|
selectFile("AndroidManifest.xml.json", block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectLibrary(fileName: String, block: FileTransformer.() -> Unit) {
|
||||||
|
listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64").forEach { arch ->
|
||||||
|
val archDirectory = workingDir.resolve("root/lib/$arch")
|
||||||
|
if (archDirectory.exists()) {
|
||||||
|
archDirectory.resolve(fileName).run {
|
||||||
|
if (exists()) {
|
||||||
|
FileTransformer(this).block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createLibrary(fileName: String, block: FileTransformer.() -> Unit) {
|
||||||
|
listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64").forEach { arch ->
|
||||||
|
val archDirectory = workingDir.resolve("root/lib/$arch")
|
||||||
|
if (archDirectory.exists()) {
|
||||||
|
archDirectory.resolve(fileName).run {
|
||||||
|
createNewFile()
|
||||||
|
FileTransformer(this).block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectSmali(vararg fileNames: String, block: FileTransformer.() -> Unit) {
|
||||||
|
workingDir.resolve("smali").listFiles()?.forEach { dir ->
|
||||||
|
if (fileNames.isEmpty()) {
|
||||||
|
dir.walk().forEach { file ->
|
||||||
|
if (file.isFile && file.name.endsWith(".smali")) {
|
||||||
|
FileTransformer(file).block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fileNames.forEach { fileName ->
|
||||||
|
dir.resolve("${fileName}.1.smali").run {
|
||||||
|
if (exists()) {
|
||||||
|
FileTransformer(this).block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dir.resolve("${fileName}.smali").run {
|
||||||
|
if (exists()) {
|
||||||
|
FileTransformer(this).block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createSmali(fileName: String, block: FileTransformer.() -> Unit) {
|
||||||
|
workingDir.resolve("smali").listFiles()?.forEach { dir ->
|
||||||
|
dir.resolve("${fileName}.1.smali").run {
|
||||||
|
if (exists()) {
|
||||||
|
FileTransformer(this).block()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dir.resolve("${fileName}.smali").run {
|
||||||
|
if (exists()) {
|
||||||
|
FileTransformer(this).block()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
workingDir
|
||||||
|
.resolve("smali")
|
||||||
|
.listFiles()
|
||||||
|
?.reduceOrNull { left, right ->
|
||||||
|
val leftValue = left.name.substringAfter("classes").toIntOrNull() ?: 0
|
||||||
|
val rightValue = right.name.substringAfter("classes").toIntOrNull() ?: 0
|
||||||
|
if (rightValue > leftValue) right else left
|
||||||
|
}
|
||||||
|
.let {
|
||||||
|
it ?: workingDir.resolve("smali/classes").apply { mkdirs() }
|
||||||
|
}
|
||||||
|
.resolve("${fileName}.smali")
|
||||||
|
.apply { createNewFile() }
|
||||||
|
.run {
|
||||||
|
if (exists()) {
|
||||||
|
FileTransformer(this).block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package moe.crx.ovrport.patch
|
||||||
|
|
||||||
|
import com.reandroid.json.JSONArray
|
||||||
|
import com.reandroid.json.JSONObject
|
||||||
|
import moe.crx.ovrport.model.generateConfig
|
||||||
|
import moe.crx.ovrport.utils.*
|
||||||
|
|
||||||
|
object PatchStore {
|
||||||
|
val PATCHES = mutableListOf<Patch>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
PATCHES += Patch("Copy ovrport libraries") {
|
||||||
|
selectWorkspace {
|
||||||
|
file.resolve("root/lib")
|
||||||
|
.listFiles()
|
||||||
|
?.forEach {
|
||||||
|
val arch = file.parentFile
|
||||||
|
?.parentFile
|
||||||
|
?.resolve(Constants.LIBRARIES_DIR)
|
||||||
|
?.resolve("lib")
|
||||||
|
?.resolve(it.name)
|
||||||
|
|
||||||
|
if (arch?.exists() == true) {
|
||||||
|
arch.copyRecursively(it, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PATCHES += Patch("Fix minimal Android SDK") {
|
||||||
|
selectManifest {
|
||||||
|
readJson().takeNodesEach({ named("manifest") }) {
|
||||||
|
takeNodesEach({ named("uses-sdk") }) {
|
||||||
|
takeAttributesEach({ named("minSdkVersion") }) {
|
||||||
|
take<Int>("data") { 29 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?.let {
|
||||||
|
writeJson(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectSmali {
|
||||||
|
replace(
|
||||||
|
"invoke-virtual \\{(\\w+), (\\w+)\\}, Landroid/view/Window;->setDecorFitsSystemWindows\\(Z\\)V",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
selectSmali("androidx/core/view/WindowCompat\$Api30Impl") {
|
||||||
|
replace(
|
||||||
|
"invoke-virtual \\{(\\w+), (\\w+)\\}, Landroid/view/Window;->setDecorFitsSystemWindows\\(Z\\)V",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
selectSmali("androidx/core/view/WindowInsetsCompat\$TypeImpl30") {
|
||||||
|
replace(
|
||||||
|
"(toPlatformType\\(I\\)I.*?)\\.locals (\\w+)",
|
||||||
|
"$0\nconst v0, 0x0\nreturn v0"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
selectSmali("androidx/core/view/WindowInsetsCompat\$Impl30") {
|
||||||
|
replace(
|
||||||
|
"(getInsets\\(I\\)Landroidx/core/graphics/Insets;.*?)\\.locals (\\w+)",
|
||||||
|
"$0\nsget-object p0, Landroidx/core/graphics/Insets;->NONE:Landroidx/core/graphics/Insets;\nreturn-object p0"
|
||||||
|
)
|
||||||
|
replace("(isVisible\\(I\\)Z.*?)\\.locals (\\w+)", "$0\nconst/4 p0, 0\nreturn p0")
|
||||||
|
}
|
||||||
|
selectSmali("com/epicgames/unreal/GameActivity") {
|
||||||
|
replace(
|
||||||
|
"const-string \\w+?, \\\"vibrator_manager\\\".*?iput-object \\w+?, \\w+?, Lcom/epicgames/unreal/GameActivity;->\\w+?:Landroid/os/Vibrator;",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PATCHES += Patch("Generate ovrport config") {
|
||||||
|
selectLibrary("libfrda.config.so") {
|
||||||
|
readJson()?.let { generateConfig(it.toString()) }?.let {
|
||||||
|
createLibrary("liboverport.config.so") {
|
||||||
|
writeJson(JSONObject(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PATCHES += Patch("Remove localized app names") {
|
||||||
|
selectResources {
|
||||||
|
readJson().take<JSONArray>("packages") {
|
||||||
|
takeEach<JSONObject> {
|
||||||
|
take<JSONArray>("specs") {
|
||||||
|
takeEach<JSONObject> {
|
||||||
|
take<JSONArray>("types") {
|
||||||
|
takeEach<JSONObject> {
|
||||||
|
val configLanguage = elem<JSONObject>("config").elem<String>("language")
|
||||||
|
|
||||||
|
take<JSONArray>("entries") {
|
||||||
|
takeEach<JSONObject> {
|
||||||
|
this.takeIf {
|
||||||
|
elem<String>("entry_name") != "app_name" || configLanguage == null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?.let {
|
||||||
|
writeJson(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PATCHES += Patch("Clean up Frida") {
|
||||||
|
selectLibrary("libfrda.so") {
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
selectLibrary("libfrda.config.so") {
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
selectLibrary("libscript.so") {
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
selectManifest {
|
||||||
|
readJson().takeNodesEach(({ named("manifest") })) {
|
||||||
|
takeNodesEach({ named("application") }) {
|
||||||
|
takeNodesEach({ named("activity") }) {
|
||||||
|
attributeName()?.replace('.', '/')?.let {
|
||||||
|
selectSmali(it) {
|
||||||
|
replace(
|
||||||
|
"const-string (\\w+), \\\"frda\\\"\\s+?invoke-static \\{\\1\\}, Ljava/lang/System;->loadLibrary\\(Ljava/lang/String;\\)V",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PATCHES += Patch("Patch Oculus detection for Unity") {
|
||||||
|
selectSmali("com/unity/oculus/OculusUnity") {
|
||||||
|
replace(
|
||||||
|
"(getIsOnOculusHardware\\(\\)Z.*?)return (\\w+)",
|
||||||
|
"$1\nconst/4 $2, 0x1\nreturn $2"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PATCHES += Patch("Patch Oculus detection for Unreal") {
|
||||||
|
selectSmali(
|
||||||
|
"com/epicgames/ue4/GameActivity",
|
||||||
|
"com/epicgames/unreal/GameActivity",
|
||||||
|
) {
|
||||||
|
replace(
|
||||||
|
"sget-object (\\w+), Landroid/os/Build;->MANUFACTURER:Ljava/lang/String;",
|
||||||
|
"const-string $1, \"Oculus\""
|
||||||
|
)
|
||||||
|
replace(
|
||||||
|
"sget-object (\\w+), Landroid/os/Build;->MODEL:Ljava/lang/String;",
|
||||||
|
"const-string $1, \"Quest 2\""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PATCHES += Patch("Pico/YVR/Quest metadata") {
|
||||||
|
selectManifest {
|
||||||
|
readJson().takeNodesEach(({ named("manifest") })) {
|
||||||
|
takeNodesEach({ named("application") }) {
|
||||||
|
takeNodesEach({ named("meta-data") }) {
|
||||||
|
if (attributeName() == "com.oculus.supportedDevices") null else this
|
||||||
|
}
|
||||||
|
takeNodes {
|
||||||
|
this
|
||||||
|
?.put(createMetadata("pvr.app.type", "vr"))
|
||||||
|
?.put(createMetadata("com.yvr.intent.category.VR", "vr_only"))
|
||||||
|
?.put(createMetadata("com.oculus.supportedDevices", "all"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?.let {
|
||||||
|
writeJson(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PATCHES += Patch("Fix launcher icon entry") {
|
||||||
|
selectManifest {
|
||||||
|
readJson().takeNodesEach(({ named("manifest") })) {
|
||||||
|
takeNodesEach({ named("application") }) {
|
||||||
|
takeNodesEach({ named("activity") }) {
|
||||||
|
takeNodesEach({ named("intent-filter") }) {
|
||||||
|
takeNodesEach({ named("category") }) {
|
||||||
|
when (attributeName()) {
|
||||||
|
"android.intent.category.INFO" -> null
|
||||||
|
"android.intent.category.LAUNCHER" -> null
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
takeNodes {
|
||||||
|
this?.put(createCategory("android.intent.category.LAUNCHER"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?.let {
|
||||||
|
writeJson(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PATCHES += Patch("Remove uses-library") {
|
||||||
|
selectManifest {
|
||||||
|
readJson().takeNodesEach({ named("manifest") }) {
|
||||||
|
takeNodesEach({ named("application") }) {
|
||||||
|
takeNodesEach({ named("uses-library") }) { null }
|
||||||
|
takeNodesEach({ named("uses-native-library") }) { null }
|
||||||
|
}
|
||||||
|
}?.let {
|
||||||
|
writeJson(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PATCHES += Patch("Fix Unreal Engine 4 crash") {
|
||||||
|
createSmali("com/unity3d/player/UnityPlayer") {
|
||||||
|
if (!find(".*")) {
|
||||||
|
append(".class public Lcom/unity3d/player/UnityPlayer;")
|
||||||
|
append(".super Ljava/lang/Object;")
|
||||||
|
append(".field public static currentActivity:Landroid/app/Activity;")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectSmali("com/epicgames/ue4/GameActivity") {
|
||||||
|
replace(
|
||||||
|
"sput-object (\\w+), Lcom\\/epicgames\\/ue4\\/GameActivity;->\\w+:Lcom\\/epicgames\\/ue4\\/GameActivity;",
|
||||||
|
"$0\nsput-object $1, Lcom/unity3d/player/UnityPlayer;->currentActivity:Landroid/app/Activity;"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PATCHES += Patch("Patch MetaXRAudioWwise") {
|
||||||
|
selectLibrary("liblibMetaXRAudioWwise.so") {
|
||||||
|
replaceHex(
|
||||||
|
"57 D0 3B D5 40 EB FF F0 00 0C 2D 91 E8 16 40 F9 A8 83 1F F8 DC 7F 0B 94 00 01 00 B4 E3 03 00 AA 21 EB FF B0",
|
||||||
|
"57 D0 3B D5 20 00 80 D2 1F 20 03 D5 E8 16 40 F9 A8 83 1F F8 1F 20 03 D5 00 01 00 B4 03 00 80 D2 21 EB FF B0"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
package moe.crx.ovrport.patch
|
||||||
|
|
||||||
|
import com.android.apksig.ApkSigner
|
||||||
|
import com.reandroid.apk.*
|
||||||
|
import com.reandroid.archive.FileInputSource
|
||||||
|
import moe.crx.ovrport.utils.HttpUtil
|
||||||
|
import org.jf.baksmali.Baksmali
|
||||||
|
import org.jf.baksmali.BaksmaliOptions
|
||||||
|
import org.jf.dexlib2.Opcodes
|
||||||
|
import org.jf.dexlib2.VersionMap
|
||||||
|
import org.jf.dexlib2.dexbacked.DexBackedDexFile
|
||||||
|
import org.jf.dexlib2.dexbacked.raw.HeaderItem
|
||||||
|
import org.jf.smali.Smali
|
||||||
|
import org.jf.smali.SmaliOptions
|
||||||
|
import java.io.*
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
|
data class PatcherContext(
|
||||||
|
val patcherDirectory: File,
|
||||||
|
val dataDir: File = patcherDirectory.resolve("dec"),
|
||||||
|
|
||||||
|
val fileName: String = "working",
|
||||||
|
val inputApk: File = dataDir.resolve("$fileName.apk"),
|
||||||
|
val outputApk: File = dataDir.resolve("$fileName.output.apk"),
|
||||||
|
|
||||||
|
val workingDir: File = dataDir.resolve(fileName),
|
||||||
|
var workingApk: ApkModule? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun PatcherContext.prepare(stream: InputStream) {
|
||||||
|
val librariesDir = patcherDirectory.resolve(Constants.LIBRARIES_DIR)
|
||||||
|
|
||||||
|
if (!librariesDir.exists()) {
|
||||||
|
val downloadedArchive = runCatching { HttpUtil.download(Constants.LIBRARIES_URL) }.getOrElse {
|
||||||
|
librariesDir.deleteRecursively()
|
||||||
|
throw CantUpdateOverportException()
|
||||||
|
}
|
||||||
|
|
||||||
|
librariesDir.mkdirs()
|
||||||
|
|
||||||
|
ZipInputStream(downloadedArchive.inputStream()).use { zip ->
|
||||||
|
generateSequence { zip.nextEntry }
|
||||||
|
.forEach {
|
||||||
|
if (it.isDirectory) {
|
||||||
|
librariesDir.resolve(it.name).mkdirs()
|
||||||
|
} else {
|
||||||
|
zip.copyTo(FileOutputStream(librariesDir.resolve(it.name)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataDir.mkdirs()
|
||||||
|
|
||||||
|
workingDir.deleteRecursively()
|
||||||
|
inputApk.delete()
|
||||||
|
outputApk.delete()
|
||||||
|
|
||||||
|
Files.copy(stream, inputApk.toPath(), REPLACE_EXISTING)
|
||||||
|
|
||||||
|
val apk = runCatching { ApkModule.loadApkFile(inputApk) }.getOrElse {
|
||||||
|
throw CantDecodeApkException()
|
||||||
|
}
|
||||||
|
|
||||||
|
val decoder = ApkModuleJsonDecoder(apk).apply {
|
||||||
|
dexDecoder = DexDecoder { dexFile, dir ->
|
||||||
|
val bytes = ByteArrayOutputStream().apply {
|
||||||
|
dexFile.write(this)
|
||||||
|
}.toByteArray()
|
||||||
|
|
||||||
|
val version = HeaderItem.getVersion(bytes, 0)
|
||||||
|
val api = VersionMap.mapDexVersionToApi(version)
|
||||||
|
val opcodes = Opcodes.forApi(api)
|
||||||
|
|
||||||
|
val classesName = dexFile.dexNumber.let { if (it != 0) "classes$it" else "classes" }
|
||||||
|
val smaliDir = dir.resolve("smali").resolve(classesName)
|
||||||
|
|
||||||
|
val options = BaksmaliOptions().apply {
|
||||||
|
localsDirective = true
|
||||||
|
sequentialLabels = true
|
||||||
|
skipDuplicateLineNumbers = true
|
||||||
|
apiLevel = api
|
||||||
|
}
|
||||||
|
Baksmali.disassembleDexFile(DexBackedDexFile(opcodes, bytes), smaliDir, 4, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder.sanitizeFilePaths()
|
||||||
|
decoder.decode(workingDir)
|
||||||
|
|
||||||
|
workingApk = apk
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PatcherContext.patch(patches: List<Patch>) {
|
||||||
|
patches.forEach {
|
||||||
|
it.executor(PatchExecutor(workingDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
val encoder = ApkModuleJsonEncoder().apply {
|
||||||
|
dexEncoder = DexEncoder { _, dir ->
|
||||||
|
dir
|
||||||
|
.resolve("smali")
|
||||||
|
.listFiles()
|
||||||
|
?.filter { file -> file.isDirectory }
|
||||||
|
?.map {
|
||||||
|
dir.resolve(".cache").mkdirs()
|
||||||
|
val file = dir.resolve(".cache").resolve("${it.name}.dex")
|
||||||
|
|
||||||
|
val smaliOptions = SmaliOptions().apply {
|
||||||
|
outputDexFile = file.absolutePath
|
||||||
|
jobs = 4
|
||||||
|
apiLevel = 29
|
||||||
|
}
|
||||||
|
|
||||||
|
Smali.assemble(smaliOptions, it.absolutePath)
|
||||||
|
FileInputSource(file, file.name)
|
||||||
|
}
|
||||||
|
?.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.scanDirectory(workingDir)
|
||||||
|
encoder.apkModule.run {
|
||||||
|
zipEntryMap.autoSortApkFiles()
|
||||||
|
writeApk(outputApk)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
val signatureDir = patcherDirectory.resolve("signatures")
|
||||||
|
signatureDir.mkdirs()
|
||||||
|
val signature = (appPackage() ?: "default")
|
||||||
|
.plus(".keystore")
|
||||||
|
.let { signatureDir.resolve(it) }
|
||||||
|
.let { SignatureStore.getSignature(it) }
|
||||||
|
|
||||||
|
val tempFile = outputApk
|
||||||
|
.parentFile
|
||||||
|
?.resolve(outputApk.name + ".temp")
|
||||||
|
?.let {
|
||||||
|
outputApk.copyTo(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ApkSigner
|
||||||
|
.Builder(listOf(signature))
|
||||||
|
.setAlignFileSize(true)
|
||||||
|
.setMinSdkVersion(29)
|
||||||
|
.setInputApk(tempFile)
|
||||||
|
.setOutputApk(outputApk)
|
||||||
|
.build()
|
||||||
|
.sign()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFile?.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PatcherContext.export(stream: OutputStream) {
|
||||||
|
Files.copy(outputApk.toPath(), stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PatcherContext.cleanup() {
|
||||||
|
workingDir.deleteRecursively()
|
||||||
|
workingApk?.close()
|
||||||
|
workingApk = null
|
||||||
|
inputApk.delete()
|
||||||
|
outputApk.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PatcherContext.appPackage(): String? {
|
||||||
|
return workingApk?.androidManifest?.packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PatcherContext.appVersion(): String? {
|
||||||
|
return workingApk?.androidManifest?.versionName
|
||||||
|
?: workingApk?.androidManifest?.versionCode?.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PatcherContext.appName(): String? {
|
||||||
|
return workingApk?.androidManifest?.applicationLabelString
|
||||||
|
?: workingApk
|
||||||
|
?.tableBlock
|
||||||
|
?.getEntries(workingApk?.androidManifest?.applicationLabelReference ?: 0)
|
||||||
|
?.asSequence()
|
||||||
|
?.firstOrNull()
|
||||||
|
?.valueAsString
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PatcherContext.appIcon(): File? {
|
||||||
|
val iconId = workingApk?.androidManifest?.iconResourceId?.takeIf { it != 0 }
|
||||||
|
?: workingApk?.androidManifest?.roundIconResourceId?.takeIf { it != 0 }
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
val result = workingApk?.listResFiles(iconId, null)
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
// TODO layered adaptive icons support
|
||||||
|
|
||||||
|
arrayOf(
|
||||||
|
"-xxxhdpi",
|
||||||
|
"-xxhdpi",
|
||||||
|
"-xhdpi",
|
||||||
|
"-hdpi",
|
||||||
|
"-mdpi",
|
||||||
|
"-ldpi",
|
||||||
|
"-anydpi",
|
||||||
|
""
|
||||||
|
).forEach { dpi ->
|
||||||
|
arrayOf(".webp", ".png").forEach { ext ->
|
||||||
|
result.firstOrNull { it.filePath.contains(dpi) && it.filePath.endsWith(ext) }?.let {
|
||||||
|
workingDir.resolve("root").resolve(it.filePath).run {
|
||||||
|
if (exists()) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package moe.crx.ovrport.patch
|
||||||
|
|
||||||
|
import com.android.apksig.ApkSigner.SignerConfig
|
||||||
|
import com.android.apksig.KeyConfig
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.KeyPairGenerator
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.security.cert.Certificate
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
object SignatureStore {
|
||||||
|
private const val KS_PASSWORD = "password"
|
||||||
|
private const val KS_ALIAS = "key"
|
||||||
|
|
||||||
|
private fun readSignature(file: File): SignerConfig {
|
||||||
|
val ks = KeyStore.getInstance(KeyStore.getDefaultType())
|
||||||
|
|
||||||
|
FileInputStream(file).use { stream ->
|
||||||
|
ks.load(stream, KS_PASSWORD.toCharArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
val alias = ks.aliases().nextElement()
|
||||||
|
|
||||||
|
val key = ks.getKey(alias, KS_PASSWORD.toCharArray()) as PrivateKey
|
||||||
|
val cert = ks.getCertificate(alias) as X509Certificate
|
||||||
|
|
||||||
|
return SignerConfig.Builder(cert.issuerX500Principal.name, KeyConfig.Jca(key), listOf(cert)).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateCertificate(keyPair: KeyPair): Certificate {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val startDate = Date(now)
|
||||||
|
val expireDate = Calendar.getInstance().run {
|
||||||
|
time = startDate
|
||||||
|
add(Calendar.YEAR, 25)
|
||||||
|
time
|
||||||
|
}
|
||||||
|
val dnName = UUID
|
||||||
|
.randomUUID()
|
||||||
|
.toString()
|
||||||
|
.replace("-", "")
|
||||||
|
.prependIndent("CN=")
|
||||||
|
.let { X500Name(it) }
|
||||||
|
val serial = BigInteger(64, SecureRandom())
|
||||||
|
|
||||||
|
val signer = JcaContentSignerBuilder("SHA256withRSA").setProvider(BouncyCastleProvider()).build(keyPair.private)
|
||||||
|
|
||||||
|
return JcaX509v3CertificateBuilder(dnName, serial, startDate, expireDate, dnName, keyPair.public).build(signer).let {
|
||||||
|
JcaX509CertificateConverter().setProvider(BouncyCastleProvider()).getCertificate(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSignature(file: File) {
|
||||||
|
val ks = KeyStore.getInstance(KeyStore.getDefaultType())
|
||||||
|
ks.load(null, KS_PASSWORD.toCharArray())
|
||||||
|
|
||||||
|
val keyPair = KeyPairGenerator
|
||||||
|
.getInstance("RSA", BouncyCastleProvider())
|
||||||
|
.apply { initialize(2048) }
|
||||||
|
.generateKeyPair()
|
||||||
|
val cert = generateCertificate(keyPair)
|
||||||
|
|
||||||
|
ks.setKeyEntry(KS_ALIAS, keyPair.private, KS_PASSWORD.toCharArray(), arrayOf(cert))
|
||||||
|
|
||||||
|
FileOutputStream(file).use { stream ->
|
||||||
|
ks.store(stream, KS_PASSWORD.toCharArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSignature(file: File): SignerConfig {
|
||||||
|
if (!file.exists()) {
|
||||||
|
createSignature(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
return readSignature(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package moe.crx.ovrport.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CloudDownload
|
||||||
|
import androidx.compose.material.icons.filled.Download
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import moe.crx.ovrport.AndroidPlatform
|
||||||
|
import moe.crx.ovrport.getCurrentVersion
|
||||||
|
import moe.crx.ovrport.patch.Patch
|
||||||
|
import moe.crx.ovrport.getPlatform
|
||||||
|
import moe.crx.ovrport.model.GithubRelease
|
||||||
|
import moe.crx.ovrport.patch.Constants
|
||||||
|
import moe.crx.ovrport.ui.theme.OverportTheme
|
||||||
|
import moe.crx.ovrport.utils.HttpUtil
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import overportapp.composeapp.generated.resources.Res
|
||||||
|
import overportapp.composeapp.generated.resources.overport
|
||||||
|
import overportapp.composeapp.generated.resources.update_available
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AppContent(
|
||||||
|
viewModel: MainViewModel,
|
||||||
|
snackbarHostState: SnackbarHostState,
|
||||||
|
onOpen: () -> Unit,
|
||||||
|
onCancel: () -> Unit,
|
||||||
|
onConfirm: (List<Patch>) -> Unit
|
||||||
|
) {
|
||||||
|
OverportTheme(darkTheme = true) {
|
||||||
|
var versionToUpdate by remember { mutableStateOf<GithubRelease?>(null) }
|
||||||
|
val urlHandler = LocalUriHandler.current
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
versionToUpdate = viewModel.versionToUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.then(
|
||||||
|
if (getPlatform() is AndroidPlatform) {
|
||||||
|
Modifier.clip(RoundedCornerShape(32.dp))
|
||||||
|
} else Modifier
|
||||||
|
),
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Icon(
|
||||||
|
painterResource(Res.drawable.overport),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(112.dp, 24.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
Constants.OVRPORT_VERSION,
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
fontSize = 18.sp,
|
||||||
|
color = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
AnimatedVisibility(
|
||||||
|
versionToUpdate?.name != null && versionToUpdate?.name != getCurrentVersion(),
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut()
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clip(
|
||||||
|
RoundedCornerShape(16.dp)
|
||||||
|
).clickable {
|
||||||
|
versionToUpdate?.htmlUrl?.let { urlHandler.openUri(it) }
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.CloudDownload,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(Res.string.update_available),
|
||||||
|
fontSize = 18.sp,
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
modifier = Modifier.padding(0.dp, 8.dp, 8.dp, 8.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = Color.Transparent
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
val inProgress = viewModel.working
|
||||||
|
val isPatcherVisible = viewModel.isApkLoaded() && !inProgress
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
!isPatcherVisible,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut()
|
||||||
|
) {
|
||||||
|
StartingCard(innerPadding, inProgress) {
|
||||||
|
onOpen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
isPatcherVisible,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut()
|
||||||
|
) {
|
||||||
|
ApplicationInfoCard(
|
||||||
|
innerPadding,
|
||||||
|
viewModel.currentAppName(),
|
||||||
|
viewModel.currentAppPackage(),
|
||||||
|
viewModel.currentAppVersion(),
|
||||||
|
viewModel.currentAppIcon(),
|
||||||
|
onCancel = {
|
||||||
|
onCancel()
|
||||||
|
},
|
||||||
|
onConfirm = {
|
||||||
|
onConfirm(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package moe.crx.ovrport.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import moe.crx.ovrport.patch.Patch
|
||||||
|
import moe.crx.ovrport.patch.PatchStore
|
||||||
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.material.icons.filled.Widgets
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import overportapp.composeapp.generated.resources.Res
|
||||||
|
import overportapp.composeapp.generated.resources.unknown_package
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
fun ApplicationInfoCard(
|
||||||
|
innerPadding: PaddingValues = PaddingValues(16.dp),
|
||||||
|
applicationName: String? = "Application",
|
||||||
|
applicationPackage: String? = "com.company.application",
|
||||||
|
applicationVersion: String? = "1.0.0",
|
||||||
|
applicationIcon: ImageBitmap? = null,
|
||||||
|
onCancel: () -> Unit = {},
|
||||||
|
onConfirm: (List<Patch>) -> Unit = {}
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(innerPadding),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
if (applicationIcon == null) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Widgets,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(48.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Image(
|
||||||
|
applicationIcon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(48.dp).clip(RoundedCornerShape(8.dp))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.height(48.dp),
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text("$applicationName ($applicationVersion)")
|
||||||
|
Text(
|
||||||
|
applicationPackage ?: stringResource(Res.string.unknown_package),
|
||||||
|
color = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val patches = remember { mutableStateListOf(*PatchStore.PATCHES.toTypedArray()) }
|
||||||
|
|
||||||
|
fun togglePatch(patch: Patch) {
|
||||||
|
val checked = patches.contains(patch)
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
patches.remove(patch)
|
||||||
|
} else {
|
||||||
|
patches.add(patch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
items(PatchStore.PATCHES) {
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Text(it.name)
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
it.desc?.let {
|
||||||
|
Text(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Checkbox(patches.contains(it), { _ ->
|
||||||
|
togglePatch(it)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
togglePatch(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
onClick = onCancel
|
||||||
|
) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
onClick = {
|
||||||
|
onConfirm(patches)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Confirm")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package moe.crx.ovrport.ui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import moe.crx.ovrport.model.GithubRelease
|
||||||
|
import moe.crx.ovrport.patch.*
|
||||||
|
import moe.crx.ovrport.utils.HttpUtil.download
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
class MainViewModel : ViewModel() {
|
||||||
|
|
||||||
|
private var patcherState by mutableStateOf<PatcherContext?>(null)
|
||||||
|
var working by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
private var fileName: String = "game"
|
||||||
|
private var imageConverter: ((File) -> ImageBitmap)? = null
|
||||||
|
|
||||||
|
private suspend fun usePatcher(block: PatcherContext.() -> Unit) {
|
||||||
|
working = true
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
patcherState?.block()
|
||||||
|
}
|
||||||
|
working = false
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun import(dataDirectory: File, name: String, inputStream: InputStream) {
|
||||||
|
fileName = name
|
||||||
|
patcherState = PatcherContext(dataDirectory)
|
||||||
|
usePatcher { prepare(inputStream) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun patchedName(): String {
|
||||||
|
return fileName.substringBeforeLast('.').let { "$it.output.apk" }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun process(patches: List<Patch>) {
|
||||||
|
usePatcher { patch(patches) }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun export(outputStream: OutputStream) {
|
||||||
|
usePatcher { export(outputStream) }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun cancel() {
|
||||||
|
usePatcher { cleanup() }
|
||||||
|
fileName = "game"
|
||||||
|
patcherState = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun currentAppName(): String? {
|
||||||
|
return patcherState?.appName()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun currentAppPackage(): String? {
|
||||||
|
return patcherState?.appPackage()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun currentAppVersion(): String? {
|
||||||
|
return patcherState?.appVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAppIconConverter(converter: (File) -> ImageBitmap) {
|
||||||
|
imageConverter = converter
|
||||||
|
}
|
||||||
|
|
||||||
|
fun currentAppIcon(): ImageBitmap? {
|
||||||
|
return patcherState?.appIcon()?.let { file ->
|
||||||
|
imageConverter?.let {
|
||||||
|
it(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isApkLoaded(): Boolean {
|
||||||
|
return patcherState?.workingApk != null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun versionToUpdate(): GithubRelease? {
|
||||||
|
return runCatching {
|
||||||
|
json
|
||||||
|
.decodeFromString<List<GithubRelease>>(String(download(Constants.RELEASES_URL)))
|
||||||
|
.reduceOrNull { left, right -> if (left.publishedAt > right.publishedAt) left else right }
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package moe.crx.ovrport.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.LinearEasing
|
||||||
|
import androidx.compose.animation.core.RepeatMode
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.infiniteRepeatable
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.AddCircle
|
||||||
|
import androidx.compose.material.icons.filled.Bolt
|
||||||
|
import androidx.compose.material.icons.filled.Download
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.PathEffect
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import moe.crx.ovrport.AndroidPlatform
|
||||||
|
import moe.crx.ovrport.getPlatform
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
import overportapp.composeapp.generated.resources.Res
|
||||||
|
import overportapp.composeapp.generated.resources.decoding_apk
|
||||||
|
import overportapp.composeapp.generated.resources.or_drag_and_drop_it
|
||||||
|
import overportapp.composeapp.generated.resources.select_apk_to_begin
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
fun StartingCard(
|
||||||
|
innerPadding: PaddingValues = PaddingValues(16.dp),
|
||||||
|
showLoading: Boolean = false,
|
||||||
|
onCardClick: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
val strokeColor = LocalContentColor.current
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxSize()
|
||||||
|
.drawBehind {
|
||||||
|
drawRoundRect(
|
||||||
|
color = strokeColor,
|
||||||
|
style = Stroke(
|
||||||
|
width = 4.dp.toPx(),
|
||||||
|
pathEffect = PathEffect.dashPathEffect(
|
||||||
|
floatArrayOf(
|
||||||
|
16.dp.toPx(),
|
||||||
|
16.dp.toPx()
|
||||||
|
), 16.dp.toPx()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
cornerRadius = CornerRadius(32.dp.toPx())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.clip(RoundedCornerShape(32.dp))
|
||||||
|
.clickable(!showLoading, onClick = onCardClick),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = Color.Transparent
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
if (!showLoading) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Download,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.width(64.dp)
|
||||||
|
.height(64.dp)
|
||||||
|
)
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.select_apk_to_begin)
|
||||||
|
)
|
||||||
|
if (getPlatform() !is AndroidPlatform) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.or_drag_and_drop_it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BreathingIcon(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.rotate(30f)
|
||||||
|
.width(96.dp)
|
||||||
|
.height(96.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.decoding_apk)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BreathingIcon(modifier: Modifier, duration: Int = 1500) {
|
||||||
|
var isFaded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val alpha by animateFloatAsState(
|
||||||
|
targetValue = if (isFaded) 0.2f else 1f,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = tween(durationMillis = duration, easing = LinearEasing),
|
||||||
|
repeatMode = RepeatMode.Reverse
|
||||||
|
),
|
||||||
|
label = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
while (true) {
|
||||||
|
isFaded = !isFaded
|
||||||
|
delay(duration.toLong())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Bolt,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier.alpha(alpha)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package moe.crx.ovrport.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val Purple80 = Color(0xFFD0BCFF)
|
||||||
|
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||||
|
val Pink80 = Color(0xFFEFB8C8)
|
||||||
|
|
||||||
|
val Purple40 = Color(0xFF6650a4)
|
||||||
|
val PurpleGrey40 = Color(0xFF625b71)
|
||||||
|
val Pink40 = Color(0xFF7D5260)
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package moe.crx.ovrport.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
private val DarkColorScheme = darkColorScheme(
|
||||||
|
primary = Purple80,
|
||||||
|
secondary = PurpleGrey80,
|
||||||
|
tertiary = Pink80,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val LightColorScheme = lightColorScheme(
|
||||||
|
primary = Purple40,
|
||||||
|
secondary = PurpleGrey40,
|
||||||
|
tertiary = Pink40,
|
||||||
|
|
||||||
|
/* Other default colors to override
|
||||||
|
background = Color(0xFFFFFBFE),
|
||||||
|
surface = Color(0xFFFFFBFE),
|
||||||
|
onPrimary = Color.White,
|
||||||
|
onSecondary = Color.White,
|
||||||
|
onTertiary = Color.White,
|
||||||
|
onBackground = Color(0xFF1C1B1F),
|
||||||
|
onSurface = Color(0xFF1C1B1F),
|
||||||
|
*/
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OverportTheme(
|
||||||
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val colorScheme = when {
|
||||||
|
darkTheme -> DarkColorScheme
|
||||||
|
else -> LightColorScheme
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
typography = Typography,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package moe.crx.ovrport.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material3.Typography
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
// Set of Material typography styles to start with
|
||||||
|
val Typography = Typography(
|
||||||
|
bodyLarge = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 24.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
)
|
||||||
|
/* Other default text styles to override
|
||||||
|
titleLarge = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 22.sp,
|
||||||
|
lineHeight = 28.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
labelSmall = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
lineHeight = 16.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package moe.crx.ovrport.utils
|
||||||
|
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object HttpUtil {
|
||||||
|
fun download(url: String): ByteArray {
|
||||||
|
val uri = URI(url).toURL().openConnection() as HttpURLConnection
|
||||||
|
|
||||||
|
val bytes = uri.errorStream?.use {
|
||||||
|
it.readBytes()
|
||||||
|
} ?: uri.inputStream.use {
|
||||||
|
it.readBytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
uri.disconnect()
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package moe.crx.ovrport.utils
|
||||||
|
|
||||||
|
import com.reandroid.json.JSONArray
|
||||||
|
import com.reandroid.json.JSONObject
|
||||||
|
|
||||||
|
inline fun <reified T> JSONObject?.elem(name: String): T? {
|
||||||
|
return this?.opt(name).takeIf { it is T } as T?
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> JSONArray?.elem(index: Int): T? {
|
||||||
|
return this?.opt(index).takeIf { it is T } as T?
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> JSONObject?.take(name: String, block: T?.() -> T? = { this }): JSONObject? {
|
||||||
|
this?.put(name, block(elem<T>(name)))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> JSONArray?.take(index: Int, block: T?.() -> T? = { this }): JSONArray? {
|
||||||
|
this?.put(index, block(elem<T>(index)))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> JSONObject?.takeEach(
|
||||||
|
condition: T?.() -> Boolean = { true },
|
||||||
|
block: T?.() -> T? = { this }
|
||||||
|
): JSONObject? {
|
||||||
|
this?.keys()?.asSequence()?.toList()
|
||||||
|
?.forEach { i ->
|
||||||
|
elem<T>(i).let {
|
||||||
|
if (condition(it)) {
|
||||||
|
block(it)?.let { value -> put(i, value) }
|
||||||
|
?: remove(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> JSONArray?.takeEach(
|
||||||
|
condition: T?.() -> Boolean = { true },
|
||||||
|
block: T?.() -> T? = { this }
|
||||||
|
): JSONArray? {
|
||||||
|
this?.length()?.let { (it - 1).downTo(0) }
|
||||||
|
?.forEach { i ->
|
||||||
|
elem<T>(i).let {
|
||||||
|
if (condition(it)) {
|
||||||
|
block(it)?.let { value -> put(i, value) }
|
||||||
|
?: remove(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> JSONObject?.elemEach(condition: T?.() -> Boolean = { true }): List<T> {
|
||||||
|
return this?.keys()?.asSequence()?.toList()
|
||||||
|
?.mapNotNull { i -> elem<T>(i) }
|
||||||
|
?.filter { condition(it) }
|
||||||
|
?.toList()
|
||||||
|
?: listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> JSONArray?.elemEach(condition: T?.() -> Boolean = { true }): List<T> {
|
||||||
|
return this?.length()?.let { (it - 1).downTo(0) }
|
||||||
|
?.mapNotNull { i -> elem<T>(i) }
|
||||||
|
?.filter { condition(it) }
|
||||||
|
?.toList()
|
||||||
|
?: listOf()
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package moe.crx.ovrport.utils
|
||||||
|
|
||||||
|
import com.reandroid.json.JSONArray
|
||||||
|
import com.reandroid.json.JSONObject
|
||||||
|
|
||||||
|
fun JSONObject?.named(name: String?): Boolean {
|
||||||
|
return elem<String>("name") == name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JSONObject?.takeNodes(block: JSONArray?.() -> JSONArray? = { this }): JSONObject? {
|
||||||
|
return take("nodes", block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JSONObject?.takeAttributes(block: JSONArray?.() -> JSONArray? = { this }): JSONObject? {
|
||||||
|
return take("attributes", block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JSONObject?.takeNodesEach(
|
||||||
|
condition: JSONObject?.() -> Boolean = { true },
|
||||||
|
block: JSONObject?.() -> JSONObject? = { this }
|
||||||
|
): JSONObject? {
|
||||||
|
return takeNodes {
|
||||||
|
takeEach(condition, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JSONObject?.takeAttributesEach(
|
||||||
|
condition: JSONObject?.() -> Boolean = { true },
|
||||||
|
block: JSONObject?.() -> JSONObject? = { this }
|
||||||
|
): JSONObject? {
|
||||||
|
return takeAttributes {
|
||||||
|
takeEach(condition, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JSONObject?.attributeName(): String? {
|
||||||
|
return elem<JSONArray>("attributes")
|
||||||
|
.elemEach<JSONObject> { named("name") }
|
||||||
|
.firstOrNull()
|
||||||
|
.elem<String>("data")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createMetadata(name: String, value: String): JSONObject {
|
||||||
|
return JSONObject()
|
||||||
|
.put("node_type", "element")
|
||||||
|
.put("name", "meta-data")
|
||||||
|
.put(
|
||||||
|
"attributes",
|
||||||
|
JSONArray()
|
||||||
|
.put(
|
||||||
|
JSONObject()
|
||||||
|
.put("name", "name")
|
||||||
|
.put("id", 16842755)
|
||||||
|
.put("uri", "http://schemas.android.com/apk/res/android")
|
||||||
|
.put("prefix", "android")
|
||||||
|
.put("value_type", "STRING")
|
||||||
|
.put("data", name)
|
||||||
|
)
|
||||||
|
.put(
|
||||||
|
JSONObject()
|
||||||
|
.put("name", "value")
|
||||||
|
.put("id", 16842788)
|
||||||
|
.put("uri", "http://schemas.android.com/apk/res/android")
|
||||||
|
.put("prefix", "android")
|
||||||
|
.put("value_type", "STRING")
|
||||||
|
.put("data", value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createCategory(value: String): JSONObject {
|
||||||
|
return JSONObject()
|
||||||
|
.put("node_type", "element")
|
||||||
|
.put("name", "category")
|
||||||
|
.put(
|
||||||
|
"attributes",
|
||||||
|
JSONArray()
|
||||||
|
.put(
|
||||||
|
JSONObject()
|
||||||
|
.put("name", "name")
|
||||||
|
.put("id", 16842755)
|
||||||
|
.put("uri", "http://schemas.android.com/apk/res/android")
|
||||||
|
.put("prefix", "android")
|
||||||
|
.put("value_type", "STRING")
|
||||||
|
.put("data", value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package moe.crx.ovrport
|
||||||
|
|
||||||
|
actual fun getPlatform(): Platform = JVMPlatform("Java ${System.getProperty("java.version")}")
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package moe.crx.ovrport
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.draganddrop.dragAndDropTarget
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draganddrop.DragAndDropEvent
|
||||||
|
import androidx.compose.ui.draganddrop.DragAndDropTarget
|
||||||
|
import androidx.compose.ui.draganddrop.awtTransferable
|
||||||
|
import androidx.compose.ui.graphics.toComposeImageBitmap
|
||||||
|
import androidx.compose.ui.window.Window
|
||||||
|
import androidx.compose.ui.window.application
|
||||||
|
import moe.crx.ovrport.ui.AppContent
|
||||||
|
import org.jetbrains.skia.Image
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import moe.crx.ovrport.patch.CantDecodeApkException
|
||||||
|
import moe.crx.ovrport.patch.CantUpdateOverportException
|
||||||
|
import moe.crx.ovrport.ui.MainViewModel
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import overportapp.composeapp.generated.resources.*
|
||||||
|
import overportapp.composeapp.generated.resources.Res
|
||||||
|
import overportapp.composeapp.generated.resources.apk_file_exported
|
||||||
|
import overportapp.composeapp.generated.resources.cant_read_apk
|
||||||
|
import overportapp.composeapp.generated.resources.window_icon
|
||||||
|
import java.awt.FileDialog
|
||||||
|
import java.awt.Frame
|
||||||
|
import java.awt.datatransfer.DataFlavor
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
|
||||||
|
fun main() = application {
|
||||||
|
Window(
|
||||||
|
onCloseRequest = ::exitApplication,
|
||||||
|
title = "overport",
|
||||||
|
icon = painterResource(Res.drawable.window_icon),
|
||||||
|
) {
|
||||||
|
val viewModel: MainViewModel = viewModel { MainViewModel() }
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
// TODO Deal with this copy-paste
|
||||||
|
val cantReadApkMessage = stringResource(Res.string.cant_read_apk)
|
||||||
|
val apkFileExportedMessage = stringResource(Res.string.apk_file_exported)
|
||||||
|
val cantUpdateOverportMessage = stringResource(Res.string.cant_update_overport)
|
||||||
|
val unknownErrorMessage = stringResource(Res.string.unknown_error)
|
||||||
|
|
||||||
|
suspend fun openFile(file: File) {
|
||||||
|
if (!file.exists() || file.extension != "apk") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val dataDir = File(System.getProperty("user.home")).resolve("overport/cache")
|
||||||
|
dataDir.mkdirs()
|
||||||
|
|
||||||
|
FileInputStream(file).use { s ->
|
||||||
|
try {
|
||||||
|
viewModel.import(dataDir, file.name, s)
|
||||||
|
} catch (_: CantDecodeApkException) {
|
||||||
|
viewModel.cancel()
|
||||||
|
snackbarHostState.showSnackbar(cantReadApkMessage)
|
||||||
|
} catch (_: CantUpdateOverportException) {
|
||||||
|
viewModel.cancel()
|
||||||
|
snackbarHostState.showSnackbar(cantUpdateOverportMessage)
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
viewModel.cancel()
|
||||||
|
snackbarHostState.showSnackbar(unknownErrorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val dragAndDropTarget = remember {
|
||||||
|
object: DragAndDropTarget {
|
||||||
|
override fun onDrop(event: DragAndDropEvent): Boolean {
|
||||||
|
val value: List<File>? = event.awtTransferable
|
||||||
|
.takeIf { it.isDataFlavorSupported(DataFlavor.javaFileListFlavor) }
|
||||||
|
?.getTransferData(DataFlavor.javaFileListFlavor)
|
||||||
|
?.let { it as? List<*> }
|
||||||
|
?.filterIsInstance<File>()
|
||||||
|
|
||||||
|
if (value?.size == 1) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
openFile(value.first())
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize().dragAndDropTarget(
|
||||||
|
shouldStartDragAndDrop = { !viewModel.working && !viewModel.isApkLoaded() },
|
||||||
|
target = dragAndDropTarget
|
||||||
|
)) {
|
||||||
|
AppContent(
|
||||||
|
viewModel = viewModel.apply {
|
||||||
|
setAppIconConverter {
|
||||||
|
FileInputStream(it).use { stream ->
|
||||||
|
Image.makeFromEncoded(stream.readAllBytes()).toComposeImageBitmap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
snackbarHostState = snackbarHostState,
|
||||||
|
onOpen = {
|
||||||
|
val fileDialog = FileDialog(Frame(), "Select a file", FileDialog.LOAD)
|
||||||
|
fileDialog.isVisible = true
|
||||||
|
val file = fileDialog.file?.let { File(fileDialog.directory).resolve(it) }
|
||||||
|
|
||||||
|
coroutineScope.launch {
|
||||||
|
if (file != null) {
|
||||||
|
openFile(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onConfirm = {
|
||||||
|
val fileDialog = FileDialog(Frame(), "Select a file", FileDialog.SAVE)
|
||||||
|
|
||||||
|
coroutineScope.run {
|
||||||
|
fileDialog.file = viewModel.patchedName()
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDialog.isVisible = true
|
||||||
|
val file = fileDialog.file?.let { File(fileDialog.directory).resolve(it) }
|
||||||
|
|
||||||
|
coroutineScope.launch {
|
||||||
|
if (file != null) {
|
||||||
|
viewModel.process(it)
|
||||||
|
FileOutputStream(file).use { s ->
|
||||||
|
viewModel.export(s)
|
||||||
|
launch {
|
||||||
|
snackbarHostState.showSnackbar(apkFileExportedMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
viewModel.cancel()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#Kotlin
|
||||||
|
kotlin.code.style=official
|
||||||
|
kotlin.daemon.jvmargs=-Xmx2048M
|
||||||
|
|
||||||
|
#Gradle
|
||||||
|
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
|
||||||
|
|
||||||
|
#Android
|
||||||
|
android.nonTransitiveRClass=true
|
||||||
|
android.useAndroidX=true
|
||||||
|
|
||||||
|
#Application
|
||||||
|
appVersion=1.0.0
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
[versions]
|
||||||
|
agp = "8.5.2"
|
||||||
|
android-compileSdk = "34"
|
||||||
|
android-minSdk = "24"
|
||||||
|
android-targetSdk = "34"
|
||||||
|
androidx-activityCompose = "1.9.3" # Do not update it
|
||||||
|
androidx-lifecycle = "2.8.4"
|
||||||
|
compose-multiplatform = "1.7.0"
|
||||||
|
kotlin = "2.1.0"
|
||||||
|
kotlinx-coroutines = "1.10.1"
|
||||||
|
apksig = "8.10.0-alpha07"
|
||||||
|
bcpkix = "1.80"
|
||||||
|
kotlinxSerializationJson = "1.8.0"
|
||||||
|
lifecycleViewmodelCompose = "2.8.2"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidx-activityCompose" }
|
||||||
|
|
||||||
|
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
|
||||||
|
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
||||||
|
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||||
|
androidx-lifecycle-viewmodel-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
|
||||||
|
|
||||||
|
android-tools-build = { group = "com.android.tools.build", name = "apksig", version.ref = "apksig" }
|
||||||
|
bouncycastle-pkix = { group = "org.bouncycastle", name = "bcpkix-jdk18on", version.ref = "bcpkix" }
|
||||||
|
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name ="kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
androidLibrary = { id = "com.android.library", version.ref = "agp" }
|
||||||
|
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
|
||||||
|
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||||
|
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
Vendored
BIN
Binary file not shown.
+7
@@ -0,0 +1,7 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||||
|
' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
Vendored
+94
@@ -0,0 +1,94 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
rootProject.name = "overportapp"
|
||||||
|
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||||
|
|
||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
google {
|
||||||
|
mavenContent {
|
||||||
|
includeGroupAndSubgroups("androidx")
|
||||||
|
includeGroupAndSubgroups("com.android")
|
||||||
|
includeGroupAndSubgroups("com.google")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositories {
|
||||||
|
google {
|
||||||
|
mavenContent {
|
||||||
|
includeGroupAndSubgroups("androidx")
|
||||||
|
includeGroupAndSubgroups("com.android")
|
||||||
|
includeGroupAndSubgroups("com.google")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
include(":composeApp")
|
||||||
Reference in new issue
Block a user