Init commit
35
.gitignore
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# built application files
|
||||||
|
*.apk
|
||||||
|
*.ap_
|
||||||
|
|
||||||
|
# files for the dex VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
/*/.externalNativeBuild/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Eclipse project files
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
|
||||||
|
# Android Studio
|
||||||
|
.idea/
|
||||||
|
.gradle
|
||||||
|
build
|
||||||
|
/*/local.properties
|
||||||
|
/*/out
|
||||||
|
/*/*/build
|
||||||
|
/*/*/production
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
*.ipr
|
||||||
|
*~
|
||||||
|
*.swp
|
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "app/src/main/cpp/xdelta3"]
|
||||||
|
path = app/src/main/cpp/xdelta3
|
||||||
|
url = https://github.com/jmacd/xdelta
|
674
COPYING
Normal file
|
@ -0,0 +1,674 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://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 <http://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
|
||||||
|
<http://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
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
4
app/CMakeLists.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
cmake_minimum_required(VERSION 3.4.1)
|
||||||
|
add_library( xdelta3 SHARED src/main/cpp/xdelta3.c )
|
||||||
|
find_library( log-lib log )
|
||||||
|
target_link_libraries( xdelta3 ${log-lib} )
|
83
app/build.gradle
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 24
|
||||||
|
buildToolsVersion '24.0.2'
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
release
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "org.emunix.unipatcher"
|
||||||
|
minSdkVersion 14
|
||||||
|
targetSdkVersion 24
|
||||||
|
versionCode 100000
|
||||||
|
versionName "0.10"
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
cppFlags ""
|
||||||
|
arguments "-DANDROID_PLATFORM=android-14"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFile './proguard-android.txt'
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path "CMakeLists.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def Properties props = new Properties()
|
||||||
|
def propFile = file('../../signing.properties')
|
||||||
|
if (propFile.canRead()){
|
||||||
|
props.load(new FileInputStream(propFile))
|
||||||
|
|
||||||
|
if (props!=null && props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&
|
||||||
|
props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {
|
||||||
|
|
||||||
|
println 'RELEASE BUILD SIGNING'
|
||||||
|
|
||||||
|
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
|
||||||
|
android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
|
||||||
|
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
|
||||||
|
android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
|
||||||
|
} else {
|
||||||
|
println 'RELEASE BUILD NOT FOUND SIGNING PROPERTIES'
|
||||||
|
|
||||||
|
android.buildTypes.release.signingConfig = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println 'RELEASE BUILD NOT FOUND SIGNING FILE'
|
||||||
|
android.buildTypes.release.signingConfig = null
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testCompile 'junit:junit:4.12'
|
||||||
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
|
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
compile 'com.android.support:support-v4:24.2.0'
|
||||||
|
compile 'com.android.support:appcompat-v7:24.2.0'
|
||||||
|
compile 'com.android.support:cardview-v7:24.2.0'
|
||||||
|
compile 'com.android.support:preference-v14:24.2.0'
|
||||||
|
compile 'com.android.support:recyclerview-v7:24.2.0'
|
||||||
|
compile 'com.android.support:design:24.2.0'
|
||||||
|
compile 'com.google.firebase:firebase-ads:9.4.0'
|
||||||
|
compile 'com.google.firebase:firebase-core:9.4.0'
|
||||||
|
compile 'com.google.firebase:firebase-crash:9.4.0'
|
||||||
|
compile 'com.anjlab.android.iab.v3:library:1.0.32'
|
||||||
|
compile 'commons-io:commons-io:2.5'
|
||||||
|
compile 'org.sufficientlysecure:html-textview:2.0'
|
||||||
|
compile 'com.afollestad.material-dialogs:core:0.9.0.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
53
app/google-services.json
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "32713218397",
|
||||||
|
"firebase_url": "https://unipatcher-897b3.firebaseio.com",
|
||||||
|
"project_id": "unipatcher-897b3",
|
||||||
|
"storage_bucket": "unipatcher-897b3.appspot.com"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:32713218397:android:4b0afdca1aa174e1",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "org.emunix.unipatcher"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "32713218397-h8ptc6lelg9l871v10pgq11ni8ejaavg.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "org.emunix.unipatcher",
|
||||||
|
"certificate_hash": "C31CB23D974F426B38D7AF0848A6F1066FCF1FD9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "32713218397-bhgevevp6i5coukhnkti1g9cecnujuav.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyD_8JprF21wL7wpRV7JymLzlbHR-jUBZKI"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"analytics_service": {
|
||||||
|
"status": 2,
|
||||||
|
"analytics_property": {
|
||||||
|
"tracking_id": "UA-77394676-1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appinvite_service": {
|
||||||
|
"status": 1,
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
},
|
||||||
|
"ads_service": {
|
||||||
|
"status": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
59
app/proguard-android.txt
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
# This is a configuration file for ProGuard.
|
||||||
|
# http://proguard.sourceforge.net/index.html#manual/usage.html
|
||||||
|
|
||||||
|
-dontusemixedcaseclassnames
|
||||||
|
-dontskipnonpubliclibraryclasses
|
||||||
|
-verbose
|
||||||
|
|
||||||
|
# Optimization is turned off by default. Dex does not like code run
|
||||||
|
# through the ProGuard optimize and preverify steps (and performs some
|
||||||
|
# of these optimizations on its own).
|
||||||
|
-dontoptimize
|
||||||
|
-dontpreverify
|
||||||
|
# Note that if you want to enable optimization, you cannot just
|
||||||
|
# include optimization flags in your own project configuration file;
|
||||||
|
# instead you will need to point to the
|
||||||
|
# "proguard-android-optimize.txt" file instead of this one from your
|
||||||
|
# project.properties file.
|
||||||
|
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
-keep public class com.google.vending.licensing.ILicensingService
|
||||||
|
-keep public class com.android.vending.licensing.ILicensingService
|
||||||
|
|
||||||
|
-keep class com.android.vending.billing.**
|
||||||
|
|
||||||
|
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
|
||||||
|
-keepclasseswithmembernames class * {
|
||||||
|
native <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# keep setters in Views so that animations can still work.
|
||||||
|
# see http://proguard.sourceforge.net/manual/examples.html#beans
|
||||||
|
-keepclassmembers public class * extends android.view.View {
|
||||||
|
void set*(***);
|
||||||
|
*** get*();
|
||||||
|
}
|
||||||
|
|
||||||
|
# We want to keep methods in Activity that could be used in the XML attribute onClick
|
||||||
|
-keepclassmembers class * extends android.app.Activity {
|
||||||
|
public void *(android.view.View);
|
||||||
|
}
|
||||||
|
|
||||||
|
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
|
||||||
|
-keepclassmembers enum * {
|
||||||
|
public static **[] values();
|
||||||
|
public static ** valueOf(java.lang.String);
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclassmembers class * implements android.os.Parcelable {
|
||||||
|
public static final android.os.Parcelable$Creator CREATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclassmembers class **.R$* {
|
||||||
|
public static <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# The support library contains references to newer platform versions.
|
||||||
|
# Don't warn about those in case this app is linking against an older
|
||||||
|
# platform version. We know about them, and they are safe.
|
||||||
|
-dontwarn android.support.**
|
77
app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.emunix.unipatcher"
|
||||||
|
android:installLocation="auto">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="com.android.vending.BILLING" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:fullBackupContent="false"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:supportsRtl="false"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar">
|
||||||
|
<activity
|
||||||
|
android:name=".ui.activity.MainActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:launchMode="singleTask">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="file" />
|
||||||
|
<data android:mimeType="*/*" />
|
||||||
|
<data android:pathPattern="/.*\\.ips" />
|
||||||
|
<data android:pathPattern="/.*\\.ups" />
|
||||||
|
<data android:pathPattern="/.*\\.bps" />
|
||||||
|
<data android:pathPattern="/.*\\.ppf" />
|
||||||
|
<data android:pathPattern="/.*\\.dps" />
|
||||||
|
<data android:pathPattern="/.*\\.xdelta" />
|
||||||
|
<data android:pathPattern="/.*\\.xdelta3" />
|
||||||
|
<data android:pathPattern="/.*\\.vcdiff" />
|
||||||
|
<data android:host="*" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.activity.FilePickerActivity"
|
||||||
|
android:label="@string/file_picker_activity_title" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.activity.SettingsActivity"
|
||||||
|
android:label="@string/settings_activity_title"
|
||||||
|
android:theme="@style/PreferenceTheme" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.activity.HelpActivity"
|
||||||
|
android:label="@string/help_activity_title" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.version"
|
||||||
|
android:value="@integer/google_play_services_version" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.google.android.gms.ads.AdActivity"
|
||||||
|
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
|
||||||
|
android:theme="@android:style/Theme.Translucent" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".WorkerService"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
BIN
app/src/main/assets/fonts/Roboto-Light.ttf
Normal file
1
app/src/main/cpp/xdelta3
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit a089d04bf19d21a8ac5e03a9066e5a2e36e0b87a
|
174
app/src/main/cpp/xdelta3.c
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
This file based on encode_decode_test.c from XDelta3 sources.
|
||||||
|
|
||||||
|
Copyright (C) 2007 Ralf Junker
|
||||||
|
Copyright (C) 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <android/log.h>
|
||||||
|
|
||||||
|
#define SIZEOF_SIZE_T 4
|
||||||
|
#define SIZEOF_UNSIGNED_LONG_LONG 8
|
||||||
|
|
||||||
|
#include "xdelta3/xdelta3/xdelta3.h"
|
||||||
|
#include "xdelta3/xdelta3/xdelta3.c"
|
||||||
|
|
||||||
|
int apply(FILE *patch, FILE *in, FILE *out);
|
||||||
|
|
||||||
|
const int ERR_UNABLE_OPEN_PATCH = -5001;
|
||||||
|
const int ERR_UNABLE_OPEN_ROM = -5002;
|
||||||
|
const int ERR_UNABLE_OPEN_OUTPUT = -5003;
|
||||||
|
const int ERR_WRONG_CHECKSUM = -5010;
|
||||||
|
|
||||||
|
int Java_org_emunix_unipatcher_patch_XDelta_xdelta3apply(JNIEnv *env,
|
||||||
|
jobject this,
|
||||||
|
jstring patchPath,
|
||||||
|
jstring romPath,
|
||||||
|
jstring outputPath)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
const char *patchName = (*env)->GetStringUTFChars(env, patchPath, NULL);
|
||||||
|
const char *romName = (*env)->GetStringUTFChars(env, romPath, NULL);
|
||||||
|
const char *outputName = (*env)->GetStringUTFChars(env, outputPath, NULL);
|
||||||
|
|
||||||
|
FILE *patchFile = fopen(patchName, "rb");
|
||||||
|
FILE *romFile = fopen(romName, "rb");
|
||||||
|
FILE *outputFile = fopen(outputName, "wb");
|
||||||
|
|
||||||
|
(*env)->ReleaseStringUTFChars(env, patchPath, patchName);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, romPath, romName);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, outputPath, outputName);
|
||||||
|
|
||||||
|
if (!patchFile)
|
||||||
|
{
|
||||||
|
return ERR_UNABLE_OPEN_PATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!romFile)
|
||||||
|
{
|
||||||
|
fclose(patchFile);
|
||||||
|
return ERR_UNABLE_OPEN_ROM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!outputFile)
|
||||||
|
{
|
||||||
|
fclose(patchFile);
|
||||||
|
fclose(romFile);
|
||||||
|
return ERR_UNABLE_OPEN_OUTPUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = apply(patchFile, romFile, outputFile);
|
||||||
|
|
||||||
|
fclose(patchFile);
|
||||||
|
fclose(romFile);
|
||||||
|
fclose(outputFile);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int apply(FILE *patch, FILE *in, FILE *out)
|
||||||
|
{
|
||||||
|
int BUFFER_SIZE = 32768;
|
||||||
|
|
||||||
|
int r, ret;
|
||||||
|
xd3_stream stream;
|
||||||
|
xd3_config config;
|
||||||
|
xd3_source source;
|
||||||
|
void* Input_Buf;
|
||||||
|
int Input_Buf_Read;
|
||||||
|
|
||||||
|
memset (&stream, 0, sizeof (stream));
|
||||||
|
memset (&source, 0, sizeof (source));
|
||||||
|
|
||||||
|
xd3_init_config(&config, 0);
|
||||||
|
config.winsize = BUFFER_SIZE;
|
||||||
|
xd3_config_stream(&stream, &config);
|
||||||
|
|
||||||
|
source.blksize = BUFFER_SIZE;
|
||||||
|
source.curblk = malloc(source.blksize);
|
||||||
|
|
||||||
|
/* Load 1st block of stream. */
|
||||||
|
r = fseek(in, 0, SEEK_SET);
|
||||||
|
if (r)
|
||||||
|
return r;
|
||||||
|
source.onblk = fread((void*)source.curblk, 1, source.blksize, in);
|
||||||
|
source.curblkno = 0;
|
||||||
|
xd3_set_source(&stream, &source);
|
||||||
|
|
||||||
|
Input_Buf = malloc(BUFFER_SIZE);
|
||||||
|
|
||||||
|
fseek(patch, 0, SEEK_SET);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
Input_Buf_Read = fread(Input_Buf, 1, BUFFER_SIZE, patch);
|
||||||
|
if (Input_Buf_Read < BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
xd3_set_flags(&stream, XD3_FLUSH | stream.flags);
|
||||||
|
}
|
||||||
|
xd3_avail_input(&stream, Input_Buf, Input_Buf_Read);
|
||||||
|
|
||||||
|
process:
|
||||||
|
|
||||||
|
ret = xd3_decode_input(&stream);
|
||||||
|
|
||||||
|
switch (ret)
|
||||||
|
{
|
||||||
|
case XD3_INPUT:
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case XD3_OUTPUT:
|
||||||
|
r = fwrite(stream.next_out, 1, stream.avail_out, out);
|
||||||
|
if (r != (int)stream.avail_out)
|
||||||
|
return r;
|
||||||
|
xd3_consume_output(&stream);
|
||||||
|
goto process;
|
||||||
|
|
||||||
|
case XD3_GETSRCBLK:
|
||||||
|
r = fseek(in, source.blksize * source.getblkno, SEEK_SET);
|
||||||
|
if (r)
|
||||||
|
return r;
|
||||||
|
source.onblk = fread((void*)source.curblk, 1, source.blksize, in);
|
||||||
|
source.curblkno = source.getblkno;
|
||||||
|
goto process;
|
||||||
|
|
||||||
|
case XD3_GOTHEADER:
|
||||||
|
case XD3_WINSTART:
|
||||||
|
case XD3_WINFINISH:
|
||||||
|
goto process;
|
||||||
|
|
||||||
|
default:
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "XDelta3", "Error %d: %s", ret, stream.msg);
|
||||||
|
if (stream.msg != NULL) {
|
||||||
|
if (strcmp(stream.msg, "target window checksum mismatch") == 0)
|
||||||
|
return ERR_WRONG_CHECKSUM;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (Input_Buf_Read == BUFFER_SIZE);
|
||||||
|
|
||||||
|
free(Input_Buf);
|
||||||
|
|
||||||
|
free((void*)source.curblk);
|
||||||
|
xd3_close_stream(&stream);
|
||||||
|
xd3_free_stream(&stream);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
51
app/src/main/java/org/emunix/unipatcher/Globals.java
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013-2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher;
|
||||||
|
|
||||||
|
public class Globals {
|
||||||
|
private static final String KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA384jTCBEuJ8nCWaC4S6AFrnMQN4mBlmkOXHV3Xg5hlFOl8TkVwiCfqz8r20yJpEy0IJ1+3QRnlq59zadUxbkD+PacJlGB/r2b3mbKfu+m0K+e/0aL6eWupjMSIyPgpnbN3uswiBEGUb4ytzYF53ZKTbLARnruQdMnjV6+VyfwMgpor/48anVQawDARBj/AIAj6VGtRHLmg6DmKDyOGQ7uCgXSv+ysnBKJjtIX/L/5nQgL8Q+9jsr2knuWY7j9BmrtpUXaDH3Kb50M1TOCKiqxPGa8lInOOIndABWxcpqmSMXP06SPYOanUlEH7lT0jjqpHpFNx8hRTT9xf652rgMJwIDAQAB";
|
||||||
|
private static String cmdArgument = null;
|
||||||
|
private static boolean isFullVersion = false;
|
||||||
|
|
||||||
|
public static String getCmdArgument() {
|
||||||
|
return cmdArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCmdArgument(String cmdArgument) {
|
||||||
|
Globals.cmdArgument = cmdArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isFullVersion() {
|
||||||
|
return isFullVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setFullVersion() {
|
||||||
|
isFullVersion = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getKey() {
|
||||||
|
return KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int ACTION_PATCHING = 1;
|
||||||
|
public static final int ACTION_SMD_FIX_CHECKSUM = 2;
|
||||||
|
public static final int ACTION_SNES_ADD_SMC_HEADER = 3;
|
||||||
|
public static final int ACTION_SNES_DELETE_SMC_HEADER = 4;
|
||||||
|
}
|
67
app/src/main/java/org/emunix/unipatcher/Settings.java
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.support.v7.preference.PreferenceManager;
|
||||||
|
|
||||||
|
public class Settings {
|
||||||
|
|
||||||
|
public static String getLastRomDir(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return prefs.getString("last_rom_directory", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLastRomDir(Context context, String directory) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putString("last_rom_directory", directory);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLastPatchDir(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return prefs.getString("last_patch_directory", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLastPatchDir(Context context, String directory) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putString("last_patch_directory", directory);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRomDir(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
if (prefs.getBoolean("remember_last_directories", true)) {
|
||||||
|
return getLastRomDir(context);
|
||||||
|
} else
|
||||||
|
return prefs.getString("rom_directory", "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPatchDir(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
if (prefs.getBoolean("remember_last_directories", true)) {
|
||||||
|
return getLastPatchDir(context);
|
||||||
|
} else
|
||||||
|
return prefs.getString("patch_directory", "/");
|
||||||
|
}
|
||||||
|
}
|
156
app/src/main/java/org/emunix/unipatcher/Utils.java
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013-2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.StatFs;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
private static final String LOG_TAG = "Utils";
|
||||||
|
|
||||||
|
private static final int BUFFER_SIZE = 10240; // 10 Kb
|
||||||
|
|
||||||
|
public static String getAppVersion(Context context) {
|
||||||
|
String versionName = "N/A";
|
||||||
|
try {
|
||||||
|
PackageInfo pinfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||||
|
versionName = pinfo.versionName;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(LOG_TAG, "App version is not available");
|
||||||
|
}
|
||||||
|
return versionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasStoragePermission(Context context) {
|
||||||
|
return ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isOnline(Context context) {
|
||||||
|
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo netInfo = cm.getActiveNetworkInfo();
|
||||||
|
return netInfo != null && netInfo.isConnectedOrConnecting();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int dpToPx(Context context, int dp) {
|
||||||
|
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
|
||||||
|
return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(18)
|
||||||
|
public static long getFreeSpace(File file) {
|
||||||
|
StatFs stat = new StatFs(file.getPath());
|
||||||
|
long bytesAvailable;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||||
|
bytesAvailable = stat.getAvailableBytes();
|
||||||
|
else
|
||||||
|
//noinspection deprecation
|
||||||
|
bytesAvailable = (long) stat.getBlockSize() * (long) stat.getAvailableBlocks();
|
||||||
|
return bytesAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copyFile(Context context, File from, File to) throws IOException {
|
||||||
|
if (Utils.getFreeSpace(to.getParentFile()) < from.length())
|
||||||
|
throw new IOException(context.getString(R.string.notify_error_not_enough_space));
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileUtils.copyFile(from, to);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOException(context.getString(R.string.notify_error_could_not_copy_file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void moveFile(Context context, File from, File to) throws IOException {
|
||||||
|
FileUtils.deleteQuietly(to);
|
||||||
|
if(!from.renameTo(to)) {
|
||||||
|
copyFile(context, from, to);
|
||||||
|
FileUtils.deleteQuietly(from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copy(InputStream from, OutputStream to, long size) throws IOException {
|
||||||
|
byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
|
int c;
|
||||||
|
while (size > 0) {
|
||||||
|
if (size < BUFFER_SIZE) {
|
||||||
|
c = from.read(buffer, 0, (int) size);
|
||||||
|
} else {
|
||||||
|
c = from.read(buffer);
|
||||||
|
}
|
||||||
|
if (c != -1) {
|
||||||
|
to.write(buffer, 0, c);
|
||||||
|
size -= c;
|
||||||
|
} else {
|
||||||
|
copy(size, (byte) 0x0, to);
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copy(long count, byte b, OutputStream to) throws IOException {
|
||||||
|
byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
|
Arrays.fill(buffer, b);
|
||||||
|
while (count > 0) {
|
||||||
|
if (count >= BUFFER_SIZE) {
|
||||||
|
to.write(buffer);
|
||||||
|
count -= BUFFER_SIZE;
|
||||||
|
} else {
|
||||||
|
to.write(buffer, 0, (int) count);
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPatch(File file) {
|
||||||
|
String[] patches = {"ips", "ups", "bps", "ppf", "dps", "xdelta", "xdelta3", "vcdiff"};
|
||||||
|
String ext = FilenameUtils.getExtension(file.getName()).toLowerCase(Locale.getDefault());
|
||||||
|
for (String patch : patches) {
|
||||||
|
if (ext.equals(patch))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isArchive(String path) {
|
||||||
|
String ext = FilenameUtils.getExtension(path).toLowerCase(Locale.getDefault());
|
||||||
|
return ext.equals("zip") || ext.equals("rar") || ext.equals("7z") || ext.equals("gz") || ext.equals("tgz");
|
||||||
|
}
|
||||||
|
}
|
251
app/src/main/java/org/emunix/unipatcher/WorkerService.java
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013-2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher;
|
||||||
|
|
||||||
|
import android.app.IntentService;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
import org.emunix.unipatcher.patch.BPS;
|
||||||
|
import org.emunix.unipatcher.patch.DPS;
|
||||||
|
import org.emunix.unipatcher.patch.IPS;
|
||||||
|
import org.emunix.unipatcher.patch.PPF;
|
||||||
|
import org.emunix.unipatcher.patch.Patch;
|
||||||
|
import org.emunix.unipatcher.patch.PatchException;
|
||||||
|
import org.emunix.unipatcher.patch.UPS;
|
||||||
|
import org.emunix.unipatcher.patch.XDelta;
|
||||||
|
import org.emunix.unipatcher.tools.RomException;
|
||||||
|
import org.emunix.unipatcher.tools.SmdFixChecksum;
|
||||||
|
import org.emunix.unipatcher.tools.SnesSmcHeader;
|
||||||
|
import org.emunix.unipatcher.ui.activity.MainActivity;
|
||||||
|
import org.emunix.unipatcher.ui.notify.Notify;
|
||||||
|
import org.emunix.unipatcher.ui.notify.PatchingNotify;
|
||||||
|
import org.emunix.unipatcher.ui.notify.SmdFixChecksumNotify;
|
||||||
|
import org.emunix.unipatcher.ui.notify.SnesAddSmcHeaderNotify;
|
||||||
|
import org.emunix.unipatcher.ui.notify.SnesDeleteSmcHeaderNotify;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class WorkerService extends IntentService {
|
||||||
|
|
||||||
|
public WorkerService() {
|
||||||
|
super("WorkerService");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onHandleIntent(Intent intent) {
|
||||||
|
// if user deny write storage permission
|
||||||
|
if (!Utils.hasStoragePermission(this)) {
|
||||||
|
Intent notificationIntent = new Intent(this, MainActivity.class);
|
||||||
|
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
Notification notify = new NotificationCompat.Builder(this)
|
||||||
|
.setContentTitle(getString(R.string.notify_error))
|
||||||
|
.setContentText(getString(R.string.permissions_storage_error_notify_access_denied))
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_patching)
|
||||||
|
.setContentIntent(PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT))
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.build();
|
||||||
|
nm.notify(0, notify);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||||
|
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "UniPatcher");
|
||||||
|
wakeLock.acquire();
|
||||||
|
|
||||||
|
try {
|
||||||
|
int action = intent.getIntExtra("action", 0);
|
||||||
|
switch (action) {
|
||||||
|
case Globals.ACTION_PATCHING:
|
||||||
|
actionPatching(intent);
|
||||||
|
break;
|
||||||
|
case Globals.ACTION_SMD_FIX_CHECKSUM:
|
||||||
|
actionSmdFixChecksum(intent);
|
||||||
|
break;
|
||||||
|
case Globals.ACTION_SNES_ADD_SMC_HEADER:
|
||||||
|
actionSnesAddSmcHeader(intent);
|
||||||
|
break;
|
||||||
|
case Globals.ACTION_SNES_DELETE_SMC_HEADER:
|
||||||
|
actionSnesDeleteSmcHeader(intent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
wakeLock.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionPatching(Intent intent) {
|
||||||
|
String errorMsg = null;
|
||||||
|
File romFile = new File(intent.getStringExtra("romPath"));
|
||||||
|
File patchFile = new File(intent.getStringExtra("patchPath"));
|
||||||
|
File outputFile = new File(intent.getStringExtra("outputPath"));
|
||||||
|
Patch patcher = null;
|
||||||
|
|
||||||
|
if(!fileExists(patchFile) || !fileExists(romFile))
|
||||||
|
return;
|
||||||
|
|
||||||
|
String ext = FilenameUtils.getExtension(patchFile.getName()).toLowerCase(Locale.getDefault());
|
||||||
|
if ("ips".equals(ext))
|
||||||
|
patcher = new IPS(this, patchFile, romFile, outputFile);
|
||||||
|
else if ("ups".equals(ext))
|
||||||
|
patcher = new UPS(this, patchFile, romFile, outputFile);
|
||||||
|
else if ("bps".equals(ext))
|
||||||
|
patcher = new BPS(this, patchFile, romFile, outputFile);
|
||||||
|
else if ("ppf".equals(ext))
|
||||||
|
patcher = new PPF(this, patchFile, romFile, outputFile);
|
||||||
|
else if ("dps".equals(ext))
|
||||||
|
patcher = new DPS(this, patchFile, romFile, outputFile);
|
||||||
|
else if ("xdelta".equals(ext) || "xdelta3".equals(ext) || "vcdiff".equals(ext))
|
||||||
|
patcher = new XDelta(this, patchFile, romFile, outputFile);
|
||||||
|
else
|
||||||
|
errorMsg = getString(R.string.notify_error_unknown_patch_format);
|
||||||
|
|
||||||
|
Notify notify = new PatchingNotify(this, outputFile.getName());
|
||||||
|
|
||||||
|
if (errorMsg != null) {
|
||||||
|
notify.showResult(errorMsg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
startForeground(notify.getID(), notify.getNotifyBuilder().build());
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ("ppf".equals(ext))
|
||||||
|
Utils.copyFile(this, romFile, outputFile);
|
||||||
|
patcher.apply();
|
||||||
|
} catch (PatchException | IOException e) {
|
||||||
|
if (Utils.getFreeSpace(outputFile.getParentFile()) == 0) {
|
||||||
|
errorMsg = getString(R.string.notify_error_not_enough_space);
|
||||||
|
} else {
|
||||||
|
errorMsg = e.getMessage();
|
||||||
|
}
|
||||||
|
FileUtils.deleteQuietly(outputFile);
|
||||||
|
} finally {
|
||||||
|
stopForeground(true);
|
||||||
|
}
|
||||||
|
notify.showResult(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionSmdFixChecksum(Intent intent) {
|
||||||
|
String errorMsg = null;
|
||||||
|
File romFile = new File(intent.getStringExtra("romPath"));
|
||||||
|
|
||||||
|
if(!fileExists(romFile))
|
||||||
|
return;
|
||||||
|
|
||||||
|
SmdFixChecksum fixer = new SmdFixChecksum(this, romFile);
|
||||||
|
|
||||||
|
Notify notify = new SmdFixChecksumNotify(this, romFile.getName());
|
||||||
|
startForeground(notify.getID(), notify.getNotifyBuilder().build());
|
||||||
|
|
||||||
|
try {
|
||||||
|
fixer.fixChecksum();
|
||||||
|
} catch (RomException | IOException e) {
|
||||||
|
errorMsg = e.getMessage();
|
||||||
|
} finally {
|
||||||
|
stopForeground(true);
|
||||||
|
}
|
||||||
|
notify.showResult(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionSnesAddSmcHeader(Intent intent) {
|
||||||
|
String errorMsg = null;
|
||||||
|
|
||||||
|
File romFile = new File(intent.getStringExtra("romPath"));
|
||||||
|
String headerPath = intent.getStringExtra("headerPath");
|
||||||
|
|
||||||
|
if(!fileExists(romFile))
|
||||||
|
return;
|
||||||
|
|
||||||
|
SnesSmcHeader worker = new SnesSmcHeader();
|
||||||
|
|
||||||
|
Notify notify = new SnesAddSmcHeaderNotify(this, romFile.getName());
|
||||||
|
startForeground(notify.getID(), notify.getNotifyBuilder().build());
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (headerPath == null)
|
||||||
|
worker.addSnesSmcHeader(this, romFile);
|
||||||
|
else
|
||||||
|
worker.addSnesSmcHeader(this, romFile, new File(headerPath));
|
||||||
|
} catch (RomException | IOException e) {
|
||||||
|
if (Utils.getFreeSpace(romFile.getParentFile()) == 0) {
|
||||||
|
errorMsg = getString(R.string.notify_error_not_enough_space);
|
||||||
|
} else {
|
||||||
|
errorMsg = e.getMessage();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
stopForeground(true);
|
||||||
|
}
|
||||||
|
notify.showResult(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionSnesDeleteSmcHeader(Intent intent) {
|
||||||
|
String errorMsg = null;
|
||||||
|
|
||||||
|
File romFile = new File(intent.getStringExtra("romPath"));
|
||||||
|
|
||||||
|
if(!fileExists(romFile))
|
||||||
|
return;
|
||||||
|
|
||||||
|
SnesSmcHeader worker = new SnesSmcHeader();
|
||||||
|
|
||||||
|
Notify notify = new SnesDeleteSmcHeaderNotify(this, romFile.getName());
|
||||||
|
startForeground(notify.getID(), notify.getNotifyBuilder().build());
|
||||||
|
|
||||||
|
try {
|
||||||
|
worker.deleteSnesSmcHeader(this, romFile);
|
||||||
|
} catch (RomException | IOException e) {
|
||||||
|
if (Utils.getFreeSpace(romFile.getParentFile()) == 0) {
|
||||||
|
errorMsg = getString(R.string.notify_error_not_enough_space);
|
||||||
|
} else {
|
||||||
|
errorMsg = e.getMessage();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
stopForeground(true);
|
||||||
|
}
|
||||||
|
notify.showResult(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean fileExists(File f) {
|
||||||
|
if (!f.exists() || f.isDirectory()) {
|
||||||
|
Intent notificationIntent = new Intent(this, MainActivity.class);
|
||||||
|
String text = getString(R.string.notify_error_file_not_found).concat(": ").concat(f.getName());
|
||||||
|
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
Notification notify = new NotificationCompat.Builder(this)
|
||||||
|
.setContentTitle(getString(R.string.notify_error))
|
||||||
|
.setContentText(text)
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_patching)
|
||||||
|
.setContentIntent(PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT))
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.build();
|
||||||
|
nm.notify(0, notify);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ad;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import com.google.android.gms.ads.AdListener;
|
||||||
|
import com.google.android.gms.ads.AdRequest;
|
||||||
|
import com.google.android.gms.ads.AdView;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.Globals;
|
||||||
|
|
||||||
|
public class AdMobController implements AdsController {
|
||||||
|
|
||||||
|
private static final String ADMOB_ID = "ca-app-pub-2445378722408015/8831379284";
|
||||||
|
private AdView adView;
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
public AdMobController(Context c, FrameLayout layout) {
|
||||||
|
context = c;
|
||||||
|
createView(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createView(FrameLayout layout) {
|
||||||
|
adView = new AdView(context);
|
||||||
|
adView.setAdSize(com.google.android.gms.ads.AdSize.SMART_BANNER);
|
||||||
|
adView.setAdUnitId(ADMOB_ID);
|
||||||
|
AdRequest adRequest = new AdRequest.Builder()
|
||||||
|
.addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
|
||||||
|
.addTestDevice("018B7216B142ABE97F9BFCD880C02EBC") // htc one v
|
||||||
|
.addTestDevice("64F8D0EE579BA448E833172DB2D91CBB") // lg nexus 5
|
||||||
|
.build();
|
||||||
|
adView.setAdListener(new AdListener() {
|
||||||
|
@Override
|
||||||
|
public void onAdLoaded() {
|
||||||
|
super.onAdLoaded();
|
||||||
|
if (Globals.isFullVersion()) {
|
||||||
|
adView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onAdOpened() {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
adView.loadAd(adRequest);
|
||||||
|
layout.addView(adView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show(boolean show) {
|
||||||
|
if (adView != null) {
|
||||||
|
if (!show) {
|
||||||
|
adView.setVisibility(View.GONE);
|
||||||
|
} else if (!Globals.isFullVersion()) {
|
||||||
|
adView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pause() {
|
||||||
|
if (adView != null) {
|
||||||
|
adView.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resume() {
|
||||||
|
if (adView != null) {
|
||||||
|
adView.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {}
|
||||||
|
|
||||||
|
public void destroy() {
|
||||||
|
if (adView != null) {
|
||||||
|
adView.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ad;
|
||||||
|
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
interface AdsController {
|
||||||
|
void createView(FrameLayout layout);
|
||||||
|
void show(boolean show);
|
||||||
|
void start();
|
||||||
|
void destroy();
|
||||||
|
void resume();
|
||||||
|
void pause();
|
||||||
|
}
|
301
app/src/main/java/org/emunix/unipatcher/patch/BPS.java
Normal file
|
@ -0,0 +1,301 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.patch;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
public class BPS extends Patch {
|
||||||
|
|
||||||
|
private static final byte[] MAGIC_NUMBER = {0x42, 0x50, 0x53, 0x31}; // "BPS1"
|
||||||
|
private static final byte SOURCE_READ = 0b00;
|
||||||
|
private static final byte TARGET_READ = 0b01;
|
||||||
|
private static final byte SOURCE_COPY = 0b10;
|
||||||
|
private static final byte TARGET_COPY = 0b11;
|
||||||
|
|
||||||
|
private ByteBuffer buffer = ByteBuffer.allocate(10485760); // 10mb
|
||||||
|
|
||||||
|
public BPS(Context context, File patch, File rom, File output) {
|
||||||
|
super(context, patch, rom, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply() throws PatchException, IOException {
|
||||||
|
|
||||||
|
if (patchFile.length() < 19) {
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
}
|
||||||
|
|
||||||
|
FileChannel patch = null;
|
||||||
|
FileChannel rom = null;
|
||||||
|
FileChannel output = null;
|
||||||
|
BpsCrc bpsCrc;
|
||||||
|
try {
|
||||||
|
if (!checkMagic(patchFile))
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_not_bps_patch));
|
||||||
|
|
||||||
|
bpsCrc = readBpsCrc(context, patchFile);
|
||||||
|
if (bpsCrc.getPatchFileCRC() != bpsCrc.getRealPatchCRC())
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
|
||||||
|
long realRomCrc = FileUtils.checksumCRC32(romFile);
|
||||||
|
if (realRomCrc != bpsCrc.getInputFileCRC()) {
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_rom_not_compatible_with_patch));
|
||||||
|
}
|
||||||
|
|
||||||
|
patch = new RandomAccessFile(patchFile, "r").getChannel();
|
||||||
|
patch.position(4); // skip magic
|
||||||
|
|
||||||
|
// decode rom size
|
||||||
|
long romSize = decode(patch);
|
||||||
|
rom = new RandomAccessFile(romFile, "r").getChannel();
|
||||||
|
|
||||||
|
// decode output size
|
||||||
|
long outputSize = decode(patch);
|
||||||
|
output = new RandomAccessFile(outputFile, "rw").getChannel();
|
||||||
|
|
||||||
|
// decode metadata size and skip
|
||||||
|
int metadataSize = (int) decode(patch);
|
||||||
|
for (int i = 0; i < metadataSize; i++) {
|
||||||
|
patch.position(patch.position() + metadataSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
int romRelOffset = 0;
|
||||||
|
int outRelOffset = 0;
|
||||||
|
long offset;
|
||||||
|
long length;
|
||||||
|
byte mode;
|
||||||
|
|
||||||
|
while (patch.position() < patchFile.length() - 12) {
|
||||||
|
length = decode(patch);
|
||||||
|
mode = (byte) (length & 0b11);
|
||||||
|
length = (length >> 2) + 1;
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case SOURCE_READ:
|
||||||
|
copy(rom, output.position(), length, output);
|
||||||
|
break;
|
||||||
|
case TARGET_READ:
|
||||||
|
copy(patch, patch.position(), length, output);
|
||||||
|
patch.position(patch.position() + length);
|
||||||
|
break;
|
||||||
|
case SOURCE_COPY:
|
||||||
|
case TARGET_COPY:
|
||||||
|
offset = decode(patch);
|
||||||
|
byte negative = (byte) (offset & 1);
|
||||||
|
offset >>= 1;
|
||||||
|
if (negative == 1) offset = -offset;
|
||||||
|
|
||||||
|
if (mode == SOURCE_COPY) {
|
||||||
|
romRelOffset += offset;
|
||||||
|
copy(rom, romRelOffset, length, output);
|
||||||
|
romRelOffset += length;
|
||||||
|
} else {
|
||||||
|
outRelOffset += offset;
|
||||||
|
copyTarget(output, outRelOffset, length);
|
||||||
|
outRelOffset += length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(patch);
|
||||||
|
IOUtils.closeQuietly(rom);
|
||||||
|
IOUtils.closeQuietly(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
long realOutCrc = FileUtils.checksumCRC32(outputFile);
|
||||||
|
if (realOutCrc != bpsCrc.getOutputFileCRC())
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_wrong_checksum_after_patching));
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode pointer
|
||||||
|
private long decode(FileChannel fc) throws IOException {
|
||||||
|
buffer.clear();
|
||||||
|
buffer.limit(1);
|
||||||
|
long offset = 0;
|
||||||
|
int shift = 1;
|
||||||
|
int c, x;
|
||||||
|
while (true) {
|
||||||
|
c = fc.read(buffer);
|
||||||
|
if (c < 1) throw new IOException("read < 1 byte");
|
||||||
|
x = buffer.get(0);
|
||||||
|
offset += (x & 0x7fL) * shift;
|
||||||
|
if ((x & 0x80) != 0) break;
|
||||||
|
shift <<= 7;
|
||||||
|
offset += shift;
|
||||||
|
buffer.flip();
|
||||||
|
}
|
||||||
|
buffer.clear();
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(FileChannel from, long pos, long size, FileChannel to) throws IOException {
|
||||||
|
buffer.clear();
|
||||||
|
int c1, c2;
|
||||||
|
while (size > 0) {
|
||||||
|
if (size < buffer.capacity())
|
||||||
|
buffer.limit((int) size);
|
||||||
|
c1 = from.read(buffer, pos);
|
||||||
|
buffer.flip();
|
||||||
|
c2 = to.write(buffer);
|
||||||
|
buffer.clear();
|
||||||
|
if (c1 != c2)
|
||||||
|
throw new IOException("Read and write a different number of bytes");
|
||||||
|
pos += c1;
|
||||||
|
size -= c1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyTarget(FileChannel fc, int offset, long size) throws IOException {
|
||||||
|
long bufSize = fc.position() + size - offset;
|
||||||
|
buffer.clear();
|
||||||
|
if (bufSize <= buffer.capacity()) {
|
||||||
|
buffer.limit((int) bufSize);
|
||||||
|
fc.read(buffer, offset);
|
||||||
|
buffer.position((int) (fc.position() - offset));
|
||||||
|
int bufferOffset = 0;
|
||||||
|
while (size-- != 0) {
|
||||||
|
buffer.put(buffer.get(bufferOffset++));
|
||||||
|
}
|
||||||
|
buffer.position((int) (fc.position() - offset));
|
||||||
|
fc.write(buffer);
|
||||||
|
} else { // very strange patch
|
||||||
|
buffer.limit(1);
|
||||||
|
while (size-- != 0) {
|
||||||
|
fc.read(buffer, offset);
|
||||||
|
buffer.flip();
|
||||||
|
fc.write(buffer);
|
||||||
|
buffer.flip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkMagic(File f) throws IOException {
|
||||||
|
FileInputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = new FileInputStream(f);
|
||||||
|
byte[] buffer = new byte[4];
|
||||||
|
stream.read(buffer);
|
||||||
|
return Arrays.equals(buffer, MAGIC_NUMBER);
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BpsCrc readBpsCrc(Context context, File f) throws PatchException, IOException {
|
||||||
|
BufferedInputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = new BufferedInputStream(new FileInputStream(f));
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
int x;
|
||||||
|
for (long i = f.length() - 12; i != 0; i--) {
|
||||||
|
x = stream.read();
|
||||||
|
if (x == -1)
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
crc.update(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
long inputCrc = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
x = stream.read();
|
||||||
|
if (x == -1)
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
crc.update(x);
|
||||||
|
inputCrc += ((long) x) << (i * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
long outputCrc = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
x = stream.read();
|
||||||
|
if (x == -1)
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
crc.update(x);
|
||||||
|
outputCrc += ((long) x) << (i * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
long realPatchCrc = crc.getValue();
|
||||||
|
long patchCrc = readLong(stream);
|
||||||
|
if (patchCrc == -1)
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
return new BpsCrc(inputCrc, outputCrc, patchCrc, realPatchCrc);
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long readLong(BufferedInputStream stream) throws IOException {
|
||||||
|
long result = 0;
|
||||||
|
int x;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
x = stream.read();
|
||||||
|
if (x == -1)
|
||||||
|
return -1;
|
||||||
|
result += ((long) x) << (i * 8);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BpsCrc {
|
||||||
|
private long inputFileCRC;
|
||||||
|
private long outputFileCRC;
|
||||||
|
private long patchFileCRC;
|
||||||
|
private long realPatchCRC;
|
||||||
|
|
||||||
|
public BpsCrc(long inputFileCRC, long outputFileCRC, long patchFileCRC, long realPatchCRC) {
|
||||||
|
|
||||||
|
this.inputFileCRC = inputFileCRC;
|
||||||
|
this.outputFileCRC = outputFileCRC;
|
||||||
|
this.patchFileCRC = patchFileCRC;
|
||||||
|
this.realPatchCRC = realPatchCRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getInputFileCRC() {
|
||||||
|
return inputFileCRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getOutputFileCRC() {
|
||||||
|
return outputFileCRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPatchFileCRC() {
|
||||||
|
return patchFileCRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRealPatchCRC() {
|
||||||
|
return realPatchCRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
127
app/src/main/java/org/emunix/unipatcher/patch/DPS.java
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014, 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.patch;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
public class DPS extends Patch {
|
||||||
|
|
||||||
|
private static final int MIN_SIZE_PATCH = 136;
|
||||||
|
private static final int BUFFER_SIZE = 32768;
|
||||||
|
private static final int COPY_DATA = 0;
|
||||||
|
private static final int ENCLOSED_DATA = 1;
|
||||||
|
|
||||||
|
public DPS(Context context, File patch, File rom, File output) {
|
||||||
|
super(context, patch, rom, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply() throws PatchException, IOException {
|
||||||
|
|
||||||
|
if (patchFile.length() < MIN_SIZE_PATCH) {
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedInputStream patchStream = null;
|
||||||
|
RandomAccessFile romStream = null;
|
||||||
|
RandomAccessFile outputStream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
patchStream = new BufferedInputStream(new FileInputStream(patchFile));
|
||||||
|
|
||||||
|
byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
|
|
||||||
|
// check version of dps patch
|
||||||
|
long i = patchStream.read(buffer, 0, 198);
|
||||||
|
if (buffer[193] != 1)
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_not_dps_patch));
|
||||||
|
|
||||||
|
// verify rom
|
||||||
|
long romSize = getUInt(buffer, 194);
|
||||||
|
if (romSize != romFile.length())
|
||||||
|
throw new IOException(context.getString(R.string.notify_error_rom_not_compatible_with_patch));
|
||||||
|
|
||||||
|
romStream = new RandomAccessFile(romFile, "r");
|
||||||
|
outputStream = new RandomAccessFile(outputFile, "rw");
|
||||||
|
|
||||||
|
int mode;
|
||||||
|
long offset;
|
||||||
|
long length;
|
||||||
|
while ((i = patchStream.read(buffer, 0, 5)) != -1) {
|
||||||
|
mode = buffer[0];
|
||||||
|
offset = getUInt(buffer, 1);
|
||||||
|
outputStream.seek(offset);
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case COPY_DATA:
|
||||||
|
i = patchStream.read(buffer, 0, 8);
|
||||||
|
offset = getUInt(buffer, 0);
|
||||||
|
length = getUInt(buffer, 4);
|
||||||
|
romStream.seek(offset);
|
||||||
|
while (length > 0) {
|
||||||
|
if (length < BUFFER_SIZE) {
|
||||||
|
i = romStream.read(buffer, 0, (int) length);
|
||||||
|
outputStream.write(buffer, 0, (int) i);
|
||||||
|
length -= i;
|
||||||
|
} else {
|
||||||
|
i = romStream.read(buffer, 0, BUFFER_SIZE);
|
||||||
|
outputStream.write(buffer, 0, (int) i);
|
||||||
|
length -= i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ENCLOSED_DATA:
|
||||||
|
i = patchStream.read(buffer, 0, 4);
|
||||||
|
length = getUInt(buffer, 0);
|
||||||
|
while (length > 0) {
|
||||||
|
if (length < BUFFER_SIZE) {
|
||||||
|
i = patchStream.read(buffer, 0, (int) length);
|
||||||
|
outputStream.write(buffer, 0, (int) i);
|
||||||
|
length -= i;
|
||||||
|
} else {
|
||||||
|
i = patchStream.read(buffer, 0, BUFFER_SIZE);
|
||||||
|
outputStream.write(buffer, 0, (int) i);
|
||||||
|
length -= i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(romStream);
|
||||||
|
IOUtils.closeQuietly(outputStream);
|
||||||
|
IOUtils.closeQuietly(patchStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getUInt(byte[] a, int offset) {
|
||||||
|
return ((long)(a[offset] & 0xff)) + ((long)(a[offset + 1] & 0xff) << 8) +
|
||||||
|
((long)(a[offset + 2] & 0xff) << 16) + ((long)(a[offset + 3] & 0xff) << 24);
|
||||||
|
}
|
||||||
|
}
|
155
app/src/main/java/org/emunix/unipatcher/patch/IPS.java
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013, 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.patch;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.emunix.unipatcher.Utils;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class IPS extends Patch {
|
||||||
|
|
||||||
|
private static final byte[] MAGIC_NUMBER = {0x50, 0x41, 0x54, 0x43, 0x48}; // "PATCH"
|
||||||
|
|
||||||
|
public IPS(Context context, File patch, File rom, File output) {
|
||||||
|
super(context, patch, rom, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply() throws PatchException, IOException {
|
||||||
|
|
||||||
|
BufferedInputStream romStream = null;
|
||||||
|
BufferedInputStream patchStream = null;
|
||||||
|
BufferedOutputStream outputStream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
romStream = new BufferedInputStream(new FileInputStream(romFile));
|
||||||
|
patchStream = new BufferedInputStream(new FileInputStream(patchFile));
|
||||||
|
outputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
|
||||||
|
|
||||||
|
long romSize = romFile.length();
|
||||||
|
int romPos = 0;
|
||||||
|
int outPos = 0;
|
||||||
|
int offset;
|
||||||
|
int size;
|
||||||
|
|
||||||
|
if (patchFile.length() < 14) {
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check magic string
|
||||||
|
byte[] magic = new byte[5];
|
||||||
|
size = patchStream.read(magic);
|
||||||
|
if (size != 5 || !Arrays.equals(magic, MAGIC_NUMBER))
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_not_ips_patch));
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
offset = readOffset(patchStream, 3);
|
||||||
|
if (offset < 0)
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
if (offset == 0x454f46) { // EOF
|
||||||
|
// truncate file or copy tail
|
||||||
|
if (romPos < romSize) {
|
||||||
|
offset = readOffset(patchStream, 3);
|
||||||
|
if (offset != -1 && offset >= romPos) {
|
||||||
|
size = offset - romPos;
|
||||||
|
} else {
|
||||||
|
size = (int) romSize - romPos;
|
||||||
|
}
|
||||||
|
Utils.copy(romStream, outputStream, size);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset <= romSize) {
|
||||||
|
if (outPos < offset) {
|
||||||
|
size = offset - outPos;
|
||||||
|
Utils.copy(romStream, outputStream, size);
|
||||||
|
romPos += size;
|
||||||
|
outPos += size;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (outPos < romSize) {
|
||||||
|
size = (int) romSize - outPos;
|
||||||
|
Utils.copy(romStream, outputStream, size);
|
||||||
|
romPos += size;
|
||||||
|
outPos += size;
|
||||||
|
}
|
||||||
|
if (outPos < offset) {
|
||||||
|
size = offset - outPos;
|
||||||
|
Utils.copy(size, (byte) 0x0, outputStream);
|
||||||
|
outPos += size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size = (patchStream.read() << 8) + patchStream.read();
|
||||||
|
if (size != 0) {
|
||||||
|
byte[] data = new byte[size];
|
||||||
|
patchStream.read(data);
|
||||||
|
outputStream.write(data);
|
||||||
|
outPos += size;
|
||||||
|
} else { // RLE
|
||||||
|
size = (patchStream.read() << 8) + patchStream.read();
|
||||||
|
byte val = (byte) patchStream.read();
|
||||||
|
byte[] data = new byte[size];
|
||||||
|
Arrays.fill(data, val);
|
||||||
|
outputStream.write(data);
|
||||||
|
outPos += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset <= romSize) {
|
||||||
|
if (romPos + size > romSize) {
|
||||||
|
romPos = (int) romSize;
|
||||||
|
} else {
|
||||||
|
// Не используй romStream.skip(size), оно по какой-то причине не всегда пропускает байты.
|
||||||
|
byte[] buf = new byte[size];
|
||||||
|
romStream.read(buf);
|
||||||
|
romPos += size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(romStream);
|
||||||
|
IOUtils.closeQuietly(patchStream);
|
||||||
|
IOUtils.closeQuietly(outputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readOffset(InputStream stream, int numBytes) throws IOException {
|
||||||
|
int offset = 0;
|
||||||
|
while (numBytes-- != 0) {
|
||||||
|
int b = stream.read();
|
||||||
|
if (b == -1)
|
||||||
|
return -1;
|
||||||
|
offset = (offset << 8) + b;
|
||||||
|
}
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
}
|
267
app/src/main/java/org/emunix/unipatcher/patch/PPF.java
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.patch;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class PPF extends Patch {
|
||||||
|
|
||||||
|
// private static final String LOG_TAG = "PPF";
|
||||||
|
private static final byte[] MAGIC_NUMBER = {0x50, 0x50, 0x46}; // "PPF" without version
|
||||||
|
|
||||||
|
private RandomAccessFile patchStream;
|
||||||
|
private RandomAccessFile outputStream;
|
||||||
|
|
||||||
|
public PPF(Context context, File patch, File rom, File output) {
|
||||||
|
super(context, patch, rom, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check what PPF version we have.
|
||||||
|
*
|
||||||
|
* @param file PPF patch
|
||||||
|
* @return PPF patch version or 0 if the file is not a PPF patch
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private int getPPFVersion(File file) throws IOException {
|
||||||
|
FileInputStream stream = null;
|
||||||
|
int version = 0;
|
||||||
|
try {
|
||||||
|
stream = new FileInputStream(file);
|
||||||
|
byte[] buffer = new byte[3];
|
||||||
|
stream.read(buffer);
|
||||||
|
if (Arrays.equals(buffer, MAGIC_NUMBER)) {
|
||||||
|
int b = stream.read();
|
||||||
|
if (b == 0x31) version = 1;
|
||||||
|
else if (b == 0x32) version = 2;
|
||||||
|
else if (b == 0x33) version = 3;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply() throws PatchException, IOException {
|
||||||
|
if (patchFile.length() < 61) {
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (getPPFVersion(patchFile)) {
|
||||||
|
case 1: applyPPF1(); break;
|
||||||
|
case 2: applyPPF2(); break;
|
||||||
|
case 3: applyPPF3(); break;
|
||||||
|
default: throw new PatchException(context.getString(R.string.notify_error_not_ppf_patch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyPPF1() throws IOException {
|
||||||
|
try {
|
||||||
|
patchStream = new RandomAccessFile(patchFile, "r");
|
||||||
|
outputStream = new RandomAccessFile(outputFile, "rw");
|
||||||
|
|
||||||
|
long dataEnd = patchFile.length();
|
||||||
|
int chunkSize;
|
||||||
|
byte[] chunkData = new byte[256];
|
||||||
|
long offset;
|
||||||
|
|
||||||
|
patchStream.seek(56);
|
||||||
|
while (patchStream.getFilePointer() < dataEnd) {
|
||||||
|
offset = readLittleEndianInt(patchStream);
|
||||||
|
chunkSize = patchStream.readUnsignedByte();
|
||||||
|
patchStream.read(chunkData, 0, chunkSize);
|
||||||
|
outputStream.seek(offset);
|
||||||
|
outputStream.write(chunkData, 0, chunkSize);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(patchStream);
|
||||||
|
IOUtils.closeQuietly(outputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyPPF2() throws IOException, PatchException {
|
||||||
|
try {
|
||||||
|
patchStream = new RandomAccessFile(patchFile, "r");
|
||||||
|
|
||||||
|
// Check size of ROM
|
||||||
|
patchStream.seek(56);
|
||||||
|
long romSize = readLittleEndianInt(patchStream);
|
||||||
|
if (romSize != romFile.length()) {
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_rom_not_compatible_with_patch));
|
||||||
|
}
|
||||||
|
|
||||||
|
outputStream = new RandomAccessFile(outputFile, "rw");
|
||||||
|
|
||||||
|
// Check binary block
|
||||||
|
byte[] patchBinaryBlock = new byte[1024];
|
||||||
|
byte[] romBinaryBlock = new byte[1024];
|
||||||
|
outputStream.seek(0x9320);
|
||||||
|
patchStream.read(patchBinaryBlock, 0, 1024);
|
||||||
|
outputStream.read(romBinaryBlock, 0, 1024);
|
||||||
|
if (!Arrays.equals(patchBinaryBlock, romBinaryBlock))
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_rom_not_compatible_with_patch));
|
||||||
|
|
||||||
|
// Calculate end of patch data
|
||||||
|
long dataEnd = patchFile.length();
|
||||||
|
int sizeFileId = getSizeFileId(patchStream, 2);
|
||||||
|
if (sizeFileId > 0) {
|
||||||
|
dataEnd -= (18 + sizeFileId + 16 + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply patch
|
||||||
|
int chunkSize;
|
||||||
|
byte[] chunkData = new byte[256];
|
||||||
|
long offset;
|
||||||
|
|
||||||
|
patchStream.seek(1084);
|
||||||
|
while (patchStream.getFilePointer() < dataEnd) {
|
||||||
|
offset = readLittleEndianInt(patchStream);
|
||||||
|
chunkSize = patchStream.readUnsignedByte();
|
||||||
|
patchStream.read(chunkData, 0, chunkSize);
|
||||||
|
outputStream.seek(offset);
|
||||||
|
outputStream.write(chunkData, 0, chunkSize);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(patchStream);
|
||||||
|
IOUtils.closeQuietly(outputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyPPF3() throws IOException, PatchException {
|
||||||
|
try {
|
||||||
|
patchStream = new RandomAccessFile(patchFile, "r");
|
||||||
|
outputStream = new RandomAccessFile(outputFile, "rw");
|
||||||
|
|
||||||
|
patchStream.seek(56);
|
||||||
|
byte imagetype = patchStream.readByte();
|
||||||
|
byte blockcheck = patchStream.readByte();
|
||||||
|
byte undo = patchStream.readByte();
|
||||||
|
|
||||||
|
// Check binary block
|
||||||
|
if (blockcheck == 0x01) {
|
||||||
|
byte[] patchBinaryBlock = new byte[1024];
|
||||||
|
byte[] romBinaryBlock = new byte[1024];
|
||||||
|
patchStream.seek(60);
|
||||||
|
if (imagetype == 0x01) {
|
||||||
|
outputStream.seek(0x80A0);
|
||||||
|
} else {
|
||||||
|
outputStream.seek(0x9320);
|
||||||
|
}
|
||||||
|
patchStream.read(patchBinaryBlock, 0, 1024);
|
||||||
|
outputStream.read(romBinaryBlock, 0, 1024);
|
||||||
|
if (!Arrays.equals(patchBinaryBlock, romBinaryBlock))
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_rom_not_compatible_with_patch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate end of patch data
|
||||||
|
long dataEnd = patchFile.length();
|
||||||
|
int sizeFileId = getSizeFileId(patchStream, 3);
|
||||||
|
if (sizeFileId > 0) {
|
||||||
|
dataEnd -= (18 + sizeFileId + 16 + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek start address of patch data
|
||||||
|
if (blockcheck == 0x01) {
|
||||||
|
patchStream.seek(1084);
|
||||||
|
} else {
|
||||||
|
patchStream.seek(60);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply patch
|
||||||
|
int chunkSize;
|
||||||
|
byte[] chunkData = new byte[512];
|
||||||
|
long offset;
|
||||||
|
|
||||||
|
while (patchStream.getFilePointer() < dataEnd) {
|
||||||
|
offset = readLittleEndianLong(patchStream);
|
||||||
|
//Log.d(LOG_TAG, String.valueOf(patchStream.getFilePointer()) + ' ' + String.valueOf(offset));
|
||||||
|
chunkSize = patchStream.readUnsignedByte();
|
||||||
|
patchStream.read(chunkData, 0, chunkSize);
|
||||||
|
if (undo == 0x01) patchStream.seek(patchStream.getFilePointer() + chunkSize);
|
||||||
|
outputStream.seek(offset);
|
||||||
|
outputStream.write(chunkData, 0, chunkSize);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(patchStream);
|
||||||
|
IOUtils.closeQuietly(outputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long readLittleEndianLong(RandomAccessFile stream) throws IOException {
|
||||||
|
byte[] b = new byte[8];
|
||||||
|
stream.read(b);
|
||||||
|
long result = ((long)(b[7] & 0xff) << 56) + ((long)(b[6] & 0xff) << 48 ) +
|
||||||
|
((long)(b[5] & 0xff) << 40) + ((long)(b[4] & 0xff) << 32 ) +
|
||||||
|
((long)(b[3] & 0xff) << 24) + ((long)(b[2] & 0xff) << 16 ) +
|
||||||
|
((long)(b[1] & 0xff) << 8) + ((long)b[0] & 0xff);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readLittleEndianInt(RandomAccessFile stream) throws IOException {
|
||||||
|
byte[] b = new byte[4];
|
||||||
|
stream.read(b);
|
||||||
|
return ((b[3] & 0xff) << 24) + ((b[2] & 0xff) <<16 ) +
|
||||||
|
((b[1] & 0xff) << 8) + (b[0]&0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns size of FileID
|
||||||
|
*
|
||||||
|
* @param stream stream of PPF file
|
||||||
|
* @param ppfVersion version of PPF patch
|
||||||
|
* @return size of FileID or 0
|
||||||
|
*/
|
||||||
|
private int getSizeFileId(RandomAccessFile stream, int ppfVersion) throws IOException {
|
||||||
|
final byte[] magic = {0x2E, 0x44, 0x49, 0x5A}; // ".DIZ"
|
||||||
|
byte[] buffer = new byte[4];
|
||||||
|
int result;
|
||||||
|
|
||||||
|
if (ppfVersion == 2) {
|
||||||
|
stream.seek(stream.length() - 4 - 4);
|
||||||
|
} else {
|
||||||
|
stream.seek(stream.length() - 2 - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.read(buffer, 0, 4);
|
||||||
|
if (!Arrays.equals(magic, buffer)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ppfVersion == 2) {
|
||||||
|
result = readLittleEndianInt(stream);
|
||||||
|
} else {
|
||||||
|
result = stream.readUnsignedByte() + (stream.readUnsignedByte() << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result > 3072) result = 3072;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
41
app/src/main/java/org/emunix/unipatcher/patch/Patch.java
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.patch;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public abstract class Patch {
|
||||||
|
protected File patchFile = null;
|
||||||
|
protected File romFile = null;
|
||||||
|
protected File outputFile = null;
|
||||||
|
protected Context context = null;
|
||||||
|
|
||||||
|
public Patch(Context c, File patch, File rom, File output) {
|
||||||
|
context = c;
|
||||||
|
patchFile = patch;
|
||||||
|
romFile = rom;
|
||||||
|
outputFile = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void apply() throws PatchException, IOException;
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.patch;
|
||||||
|
|
||||||
|
public class PatchException extends Exception {
|
||||||
|
|
||||||
|
public PatchException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatchException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
271
app/src/main/java/org/emunix/unipatcher/patch/UPS.java
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013, 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.patch;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.emunix.unipatcher.Utils;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
public class UPS extends Patch {
|
||||||
|
|
||||||
|
private static final byte[] MAGIC_NUMBER = {0x55, 0x50, 0x53, 0x31}; // "UPS1"
|
||||||
|
public UPS(Context context, File patch, File rom, File output) {
|
||||||
|
super(context, patch, rom, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply() throws PatchException, IOException {
|
||||||
|
|
||||||
|
if (patchFile.length() < 18) {
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedInputStream patchStream = null;
|
||||||
|
BufferedInputStream romStream = null;
|
||||||
|
BufferedOutputStream outputStream = null;
|
||||||
|
UpsCrc upsCrc;
|
||||||
|
try {
|
||||||
|
if (!checkMagic(patchFile))
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_not_ups_patch));
|
||||||
|
|
||||||
|
upsCrc = readUpsCrc(context, patchFile);
|
||||||
|
if (upsCrc.getPatchFileCRC() != upsCrc.getRealPatchCRC())
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
|
||||||
|
patchStream = new BufferedInputStream(new FileInputStream(patchFile));
|
||||||
|
long patchPos = 0;
|
||||||
|
// skip magic
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
patchStream.read();
|
||||||
|
}
|
||||||
|
patchPos += 4;
|
||||||
|
|
||||||
|
// decode rom and output size
|
||||||
|
Pair p;
|
||||||
|
p = decode(patchStream);
|
||||||
|
long xSize = p.getValue();
|
||||||
|
patchPos += p.getSize();
|
||||||
|
p = decode(patchStream);
|
||||||
|
long ySize = p.getValue();
|
||||||
|
patchPos += p.getSize();
|
||||||
|
|
||||||
|
long realRomCrc = FileUtils.checksumCRC32(romFile);
|
||||||
|
|
||||||
|
if (romFile.length() == xSize && realRomCrc == upsCrc.getInputFileCRC()) {
|
||||||
|
// xSize, ySize, inCRC, outCRC not change
|
||||||
|
} else if (romFile.length() == ySize && realRomCrc == upsCrc.getOutputFileCRC()) {
|
||||||
|
// swap(xSize, ySize) and swap(inCRC, outCRC)
|
||||||
|
long tmp = xSize;
|
||||||
|
xSize = ySize;
|
||||||
|
ySize = tmp;
|
||||||
|
upsCrc.swapInOut();
|
||||||
|
} else {
|
||||||
|
throw new IOException(context.getString(R.string.notify_error_rom_not_compatible_with_patch));
|
||||||
|
}
|
||||||
|
|
||||||
|
romStream = new BufferedInputStream(new FileInputStream(romFile));
|
||||||
|
outputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
|
||||||
|
long outPos = 0;
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
long offset = 0;
|
||||||
|
while (patchPos < patchFile.length() - 12) {
|
||||||
|
p = decode(patchStream);
|
||||||
|
offset += p.getValue();
|
||||||
|
patchPos += p.getSize();
|
||||||
|
if (offset > ySize) continue;
|
||||||
|
Utils.copy(romStream, outputStream, offset - outPos);
|
||||||
|
outPos += offset - outPos;
|
||||||
|
for (long i = offset; i < ySize; i++) {
|
||||||
|
x = patchStream.read();
|
||||||
|
patchPos++;
|
||||||
|
offset++;
|
||||||
|
if (x == 0x00) break; // chunk terminating byte - 0x00
|
||||||
|
y = i < xSize ? romStream.read() : 0x00;
|
||||||
|
outputStream.write(x ^ y);
|
||||||
|
outPos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write rom tail and trim
|
||||||
|
Utils.copy(romStream, outputStream, ySize - outPos);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(patchStream);
|
||||||
|
IOUtils.closeQuietly(romStream);
|
||||||
|
IOUtils.closeQuietly(outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
long realOutCrc = FileUtils.checksumCRC32(outputFile);
|
||||||
|
if (realOutCrc != upsCrc.getOutputFileCRC())
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_wrong_checksum_after_patching));
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode pointer
|
||||||
|
private Pair decode(BufferedInputStream stream) throws PatchException, IOException {
|
||||||
|
long offset = 0;
|
||||||
|
long size = 0;
|
||||||
|
int shift = 1;
|
||||||
|
int x;
|
||||||
|
while (true) {
|
||||||
|
x = stream.read();
|
||||||
|
if (x == -1)
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
size++;
|
||||||
|
offset += (x & 0x7fL) * shift;
|
||||||
|
if ((x & 0x80) != 0) break;
|
||||||
|
shift <<= 7;
|
||||||
|
offset += shift;
|
||||||
|
}
|
||||||
|
return new Pair(offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkMagic(File f) throws IOException {
|
||||||
|
FileInputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = new FileInputStream(f);
|
||||||
|
byte[] buffer = new byte[4];
|
||||||
|
stream.read(buffer);
|
||||||
|
return Arrays.equals(buffer, MAGIC_NUMBER);
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UpsCrc readUpsCrc(Context context, File f) throws PatchException, IOException {
|
||||||
|
BufferedInputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = new BufferedInputStream(new FileInputStream(f));
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
int x;
|
||||||
|
for (long i = f.length() - 12; i != 0; i--) {
|
||||||
|
x = stream.read();
|
||||||
|
if (x == -1)
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
crc.update(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
long inputCrc = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
x = stream.read();
|
||||||
|
if (x == -1)
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
crc.update(x);
|
||||||
|
inputCrc += ((long) x) << (i * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
long outputCrc = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
x = stream.read();
|
||||||
|
if (x == -1)
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
crc.update(x);
|
||||||
|
outputCrc += ((long) x) << (i * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
long realPatchCrc = crc.getValue();
|
||||||
|
long patchCrc = readLong(stream);
|
||||||
|
if (patchCrc == -1)
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
|
||||||
|
return new UpsCrc(inputCrc, outputCrc, patchCrc, realPatchCrc);
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long readLong(BufferedInputStream stream) throws IOException {
|
||||||
|
long result = 0;
|
||||||
|
int x;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
x = stream.read();
|
||||||
|
if (x == -1)
|
||||||
|
return -1;
|
||||||
|
result += ((long) x) << (i * 8);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class UpsCrc {
|
||||||
|
private long inputFileCRC;
|
||||||
|
private long outputFileCRC;
|
||||||
|
private long patchFileCRC;
|
||||||
|
private long realPatchCRC;
|
||||||
|
|
||||||
|
public UpsCrc(long inputFileCRC, long outputFileCRC, long patchFileCRC, long realPatchCRC) {
|
||||||
|
|
||||||
|
this.inputFileCRC = inputFileCRC;
|
||||||
|
this.outputFileCRC = outputFileCRC;
|
||||||
|
this.patchFileCRC = patchFileCRC;
|
||||||
|
this.realPatchCRC = realPatchCRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getInputFileCRC() {
|
||||||
|
return inputFileCRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getOutputFileCRC() {
|
||||||
|
return outputFileCRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPatchFileCRC() {
|
||||||
|
return patchFileCRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRealPatchCRC() {
|
||||||
|
return realPatchCRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void swapInOut(){
|
||||||
|
long tmp;
|
||||||
|
tmp = inputFileCRC;
|
||||||
|
inputFileCRC = outputFileCRC;
|
||||||
|
outputFileCRC = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class Pair {
|
||||||
|
private final long value;
|
||||||
|
private final long size;
|
||||||
|
|
||||||
|
public Pair(long value, long size) {
|
||||||
|
this.value = value;
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
app/src/main/java/org/emunix/unipatcher/patch/XDelta.java
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.patch;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class XDelta extends Patch {
|
||||||
|
|
||||||
|
private static final int NO_ERROR = 0;
|
||||||
|
private static final int ERR_UNABLE_OPEN_PATCH = -5001;
|
||||||
|
private static final int ERR_UNABLE_OPEN_ROM = -5002;
|
||||||
|
private static final int ERR_UNABLE_OPEN_OUTPUT = -5003;
|
||||||
|
private static final int ERR_WRONG_CHECKSUM = -5010;
|
||||||
|
private static final int ERR_INVALID_INPUT = -17712;
|
||||||
|
|
||||||
|
public static native int xdelta3apply(String patchPath, String romPath, String outputPath);
|
||||||
|
|
||||||
|
public XDelta(Context context, File patch, File rom, File output) {
|
||||||
|
super(context, patch, rom, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply() throws PatchException, IOException {
|
||||||
|
if (checkXDelta1(patchFile))
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_xdelta1_unsupported));
|
||||||
|
|
||||||
|
try {
|
||||||
|
System.loadLibrary("xdelta3");
|
||||||
|
} catch (UnsatisfiedLinkError e) {
|
||||||
|
throw new PatchException("Failed to load library libxdelta3.so");
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = xdelta3apply(patchFile.getPath(), romFile.getPath(), outputFile.getPath());
|
||||||
|
|
||||||
|
switch (ret) {
|
||||||
|
case NO_ERROR:
|
||||||
|
return;
|
||||||
|
case ERR_UNABLE_OPEN_PATCH:
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_unable_open_file)
|
||||||
|
.concat(" ").concat(patchFile.getName()));
|
||||||
|
case ERR_UNABLE_OPEN_ROM:
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_unable_open_file)
|
||||||
|
.concat(" ").concat(romFile.getName()));
|
||||||
|
case ERR_UNABLE_OPEN_OUTPUT:
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_unable_open_file)
|
||||||
|
.concat(" ").concat(outputFile.getName()));
|
||||||
|
case ERR_WRONG_CHECKSUM:
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_rom_not_compatible_with_patch));
|
||||||
|
case ERR_INVALID_INPUT:
|
||||||
|
throw new PatchException(context.getString(R.string.notify_error_not_xdelta3_patch));
|
||||||
|
default:
|
||||||
|
throw new PatchException("Unknown error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkXDelta1(File file) throws IOException {
|
||||||
|
String[] MAGIC_XDELTA1 = {"%XDELTA%", "%XDZ000%", "%XDZ001%",
|
||||||
|
"%XDZ002%", "%XDZ003%", "%XDZ004%"};
|
||||||
|
|
||||||
|
FileInputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = new FileInputStream(file);
|
||||||
|
byte[] magic = new byte[8];
|
||||||
|
stream.read(magic);
|
||||||
|
for (String xdelta1 : MAGIC_XDELTA1) {
|
||||||
|
if (Arrays.equals(magic, xdelta1.getBytes()))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.tools;
|
||||||
|
|
||||||
|
public class RomException extends Exception {
|
||||||
|
|
||||||
|
public RomException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RomException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.tools;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
|
||||||
|
public class SmdFixChecksum {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
private File smdFile = null;
|
||||||
|
|
||||||
|
public SmdFixChecksum(Context c, File file) {
|
||||||
|
context = c;
|
||||||
|
smdFile = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculateChecksum() throws RomException, IOException {
|
||||||
|
long length = smdFile.length();
|
||||||
|
if (length < 514) {
|
||||||
|
throw new RomException(context.getString(R.string.notify_error_not_smd_rom));
|
||||||
|
}
|
||||||
|
|
||||||
|
long sum = 0;
|
||||||
|
BufferedInputStream smdStream = null;
|
||||||
|
try {
|
||||||
|
smdStream = new BufferedInputStream(new FileInputStream(smdFile));
|
||||||
|
|
||||||
|
long c = IOUtils.skip(smdStream, 512);
|
||||||
|
if (c != 512)
|
||||||
|
throw new IOException("Skip failed");
|
||||||
|
|
||||||
|
int b1, b2;
|
||||||
|
while (c < length) {
|
||||||
|
b1 = smdStream.read();
|
||||||
|
b2 = smdStream.read();
|
||||||
|
if (b1 == -1 || b2 == -1)
|
||||||
|
throw new RomException(context.getString(R.string.notify_error_unexpected_end_of_file));
|
||||||
|
|
||||||
|
sum += (b1 << 8) + b2;
|
||||||
|
c += 2;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(smdStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) sum & 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fixChecksum() throws RomException, IOException {
|
||||||
|
int sum = calculateChecksum();
|
||||||
|
|
||||||
|
RandomAccessFile smdStream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
smdStream = new RandomAccessFile(smdFile, "rw");
|
||||||
|
smdStream.seek(0x18e);
|
||||||
|
smdStream.writeByte((sum >> 8) & 0xff);
|
||||||
|
smdStream.writeByte(sum & 0xff);
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(smdStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
125
app/src/main/java/org/emunix/unipatcher/tools/SnesSmcHeader.java
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.tools;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.emunix.unipatcher.Utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class SnesSmcHeader {
|
||||||
|
|
||||||
|
private final static int HEADER_SIZE = 512;
|
||||||
|
|
||||||
|
public boolean isHasSmcHeader(File romfile) {
|
||||||
|
long romSize = romfile.length();
|
||||||
|
return (romSize & 0x7fff) == 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteSnesSmcHeader(Context context, File romfile) throws IOException, RomException {
|
||||||
|
if (!isHasSmcHeader(romfile)) {
|
||||||
|
throw new RomException();
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInputStream inputRom = null;
|
||||||
|
FileOutputStream outputRom = null;
|
||||||
|
FileOutputStream outputHeader = null;
|
||||||
|
File tmpfile;
|
||||||
|
|
||||||
|
try {
|
||||||
|
tmpfile = File.createTempFile(romfile.getName(), null, romfile.getParentFile());
|
||||||
|
File headerfile = new File(romfile.getPath()+".smc_header");
|
||||||
|
|
||||||
|
inputRom = new FileInputStream(romfile);
|
||||||
|
outputRom = new FileOutputStream(tmpfile);
|
||||||
|
outputHeader = new FileOutputStream(headerfile);
|
||||||
|
|
||||||
|
// write smc header in a file
|
||||||
|
byte[] header = new byte[HEADER_SIZE];
|
||||||
|
int length;
|
||||||
|
length = inputRom.read(header);
|
||||||
|
outputHeader.write(header, 0, length);
|
||||||
|
|
||||||
|
// write headerless rom in tmp file
|
||||||
|
byte[] buffer = new byte[32768];
|
||||||
|
while ((length = inputRom.read(buffer)) > 0) {
|
||||||
|
outputRom.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(inputRom);
|
||||||
|
IOUtils.closeQuietly(outputRom);
|
||||||
|
IOUtils.closeQuietly(outputHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils.moveFile(context, tmpfile, romfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSnesSmcHeader(Context context, File romfile, File headerfile) throws IOException, RomException {
|
||||||
|
if (isHasSmcHeader(romfile)) {
|
||||||
|
throw new RomException();
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInputStream inputRom = null;
|
||||||
|
FileInputStream inputHeader = null;
|
||||||
|
FileOutputStream outputRom = null;
|
||||||
|
File tmpfile;
|
||||||
|
|
||||||
|
try {
|
||||||
|
tmpfile = File.createTempFile(romfile.getName(), null, romfile.getParentFile());
|
||||||
|
|
||||||
|
inputRom = new FileInputStream(romfile);
|
||||||
|
outputRom = new FileOutputStream(tmpfile);
|
||||||
|
|
||||||
|
// write header to tmp file
|
||||||
|
byte[] header = new byte[HEADER_SIZE];
|
||||||
|
int length;
|
||||||
|
if (headerfile == null) {
|
||||||
|
Arrays.fill(header, (byte) 0);
|
||||||
|
length = HEADER_SIZE;
|
||||||
|
} else {
|
||||||
|
inputHeader = new FileInputStream(headerfile);
|
||||||
|
length = inputHeader.read(header);
|
||||||
|
}
|
||||||
|
outputRom.write(header, 0, length);
|
||||||
|
|
||||||
|
// write headerless rom in tmp file
|
||||||
|
byte[] buffer = new byte[32768];
|
||||||
|
while ((length = inputRom.read(buffer)) > 0) {
|
||||||
|
outputRom.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(inputRom);
|
||||||
|
IOUtils.closeQuietly(inputHeader);
|
||||||
|
IOUtils.closeQuietly(outputRom);
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils.moveFile(context, tmpfile, romfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSnesSmcHeader(Context context, File romfile) throws IOException, RomException {
|
||||||
|
addSnesSmcHeader(context, romfile, null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,440 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013, 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.activity;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
import org.emunix.unipatcher.Globals;
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.emunix.unipatcher.Utils;
|
||||||
|
import org.emunix.unipatcher.ad.AdMobController;
|
||||||
|
import org.emunix.unipatcher.ui.adapter.FilePickerAdapter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
|
||||||
|
public class FilePickerActivity extends AppCompatActivity implements FilePickerAdapter.OnItemClickListener {
|
||||||
|
|
||||||
|
private AdMobController ad;
|
||||||
|
private RecyclerView list;
|
||||||
|
private FilePickerAdapter listAdapter;
|
||||||
|
private TextView permissionErrorText;
|
||||||
|
|
||||||
|
private TextView crc32;
|
||||||
|
private TextView md5;
|
||||||
|
private TextView sha1;
|
||||||
|
|
||||||
|
private List<FileEntry> fileList = new ArrayList<>();
|
||||||
|
private File currentDir;
|
||||||
|
private String savedCurrentDir;
|
||||||
|
private String intentDir;
|
||||||
|
|
||||||
|
private static final int REQUEST_PERMISSION_WRITE_STORAGE = 1;
|
||||||
|
|
||||||
|
private static final String CRC32 = "CRC32";
|
||||||
|
private static final String MD5 = "MD5";
|
||||||
|
private static final String SHA1 = "SHA-1";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_file_picker);
|
||||||
|
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
try {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
} catch (NullPointerException e) {/* empty */} // TODO log
|
||||||
|
|
||||||
|
String title = getIntent().getStringExtra("title");
|
||||||
|
if (title == null)
|
||||||
|
title = getString(R.string.file_picker_activity_title);
|
||||||
|
getSupportActionBar().setTitle(title);
|
||||||
|
|
||||||
|
intentDir = getIntent().getStringExtra("directory");
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
savedCurrentDir = savedInstanceState.getString("currentDirectory");
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionErrorText = (TextView) findViewById(R.id.empty_view);
|
||||||
|
|
||||||
|
list = (RecyclerView) findViewById(R.id.list);
|
||||||
|
try {
|
||||||
|
list.setHasFixedSize(true);
|
||||||
|
} catch (NullPointerException e) {/* TODO log */}
|
||||||
|
RecyclerView.LayoutManager listLayoutManager = new LinearLayoutManager(this);
|
||||||
|
list.setLayoutManager(listLayoutManager);
|
||||||
|
listAdapter = new FilePickerAdapter(fileList);
|
||||||
|
list.setAdapter(listAdapter);
|
||||||
|
listAdapter.setOnItemClickListener(this);
|
||||||
|
|
||||||
|
requestStoragePermission();
|
||||||
|
|
||||||
|
// Load ads
|
||||||
|
if (!Globals.isFullVersion()) {
|
||||||
|
FrameLayout adView = (FrameLayout) findViewById(R.id.adView);
|
||||||
|
ad = new AdMobController(this, adView);
|
||||||
|
if (!Utils.isOnline(this))
|
||||||
|
ad.show(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(View view, int position) {
|
||||||
|
if (position < 0 || position > fileList.size()) // fix 'fast tapping' crash
|
||||||
|
return;
|
||||||
|
|
||||||
|
String fileName = fileList.get(position).getName();
|
||||||
|
if (position == 0 && fileName.equals("..")) {
|
||||||
|
browseTo(currentDir.getParentFile());
|
||||||
|
} else {
|
||||||
|
browseTo(new File(currentDir.getPath() + File.separator + fileName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemLongClick(View view, int position) {
|
||||||
|
String fileName = currentDir.getPath() + File.separator + fileList.get(position).getName();
|
||||||
|
showFileDetails(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readListOfFiles() {
|
||||||
|
if (savedCurrentDir != null) {
|
||||||
|
currentDir = new File(savedCurrentDir);
|
||||||
|
} else if (intentDir == null) {
|
||||||
|
currentDir = getExternalOrRoot();
|
||||||
|
} else {
|
||||||
|
currentDir = new File(intentDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentDir.canRead()) {
|
||||||
|
String err = getString(R.string.file_picker_activity_error_unable_read_dir);
|
||||||
|
err = String.format(err, currentDir.getAbsolutePath());
|
||||||
|
Toast.makeText(this, err, Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
|
currentDir = getExternalOrRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
browseTo(currentDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getExternalOrRoot() {
|
||||||
|
Boolean isSDPresent = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
|
||||||
|
if (isSDPresent) {
|
||||||
|
return Environment.getExternalStorageDirectory().getAbsoluteFile();
|
||||||
|
} else {
|
||||||
|
return new File("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File[] sortFiles(File[] files) {
|
||||||
|
Comparator<File> comp = new Comparator<File>() {
|
||||||
|
public int compare(File f1, File f2) {
|
||||||
|
if (f1.isDirectory() && !f2.isDirectory()) {
|
||||||
|
return -1;
|
||||||
|
} else if (!f1.isDirectory() && f2.isDirectory()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return f1.compareTo(f2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Arrays.sort(files, comp);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void browseTo(final File dir) {
|
||||||
|
if (dir.isDirectory()) {
|
||||||
|
currentDir = dir;
|
||||||
|
fillFileList(sortFiles(dir.listFiles()));
|
||||||
|
} else {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.putExtra("path", dir.getAbsolutePath());
|
||||||
|
setResult(Activity.RESULT_OK, intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillFileList(File[] files) {
|
||||||
|
int size = fileList.size();
|
||||||
|
fileList.clear();
|
||||||
|
listAdapter.notifyItemRangeRemoved(0, size);
|
||||||
|
|
||||||
|
FileEntry entry;
|
||||||
|
|
||||||
|
if (currentDir.getParent() != null && currentDir.getParentFile().canRead()) {
|
||||||
|
entry = new FileEntry();
|
||||||
|
entry.setIcon(R.drawable.ic_folder_upload_grey600_24dp);
|
||||||
|
entry.setName("..");
|
||||||
|
fileList.add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.isHidden() || !file.canRead())
|
||||||
|
continue;
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
entry = new FileEntry();
|
||||||
|
entry.setIcon(R.drawable.ic_folder_grey600_24dp);
|
||||||
|
entry.setName(file.getName());
|
||||||
|
fileList.add(entry);
|
||||||
|
} else {
|
||||||
|
entry = new FileEntry();
|
||||||
|
if (Utils.isPatch(file)){
|
||||||
|
entry.setIcon(R.drawable.ic_healing_grey600_24dp);
|
||||||
|
} else {
|
||||||
|
entry.setIcon(R.drawable.ic_insert_drive_file_grey600_24dp);
|
||||||
|
}
|
||||||
|
entry.setName(file.getName());
|
||||||
|
fileList.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listAdapter.notifyItemRangeInserted(0, fileList.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home:
|
||||||
|
// This is called when the Home (Up) button is pressed in the Action Bar.
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||||
|
super.onSaveInstanceState(savedInstanceState);
|
||||||
|
if (Utils.hasStoragePermission(this))
|
||||||
|
savedInstanceState.putString("currentDirectory", currentDir.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
if (ad != null) {
|
||||||
|
ad.pause();
|
||||||
|
}
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (ad != null) {
|
||||||
|
ad.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
if (ad != null) {
|
||||||
|
ad.destroy();
|
||||||
|
}
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestStoragePermission() {
|
||||||
|
if (!Utils.hasStoragePermission(this)) {
|
||||||
|
ActivityCompat.requestPermissions(this,
|
||||||
|
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||||
|
REQUEST_PERMISSION_WRITE_STORAGE);
|
||||||
|
} else {
|
||||||
|
readListOfFiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode,
|
||||||
|
@NonNull String[] permissions,
|
||||||
|
@NonNull int[] grantResults) {
|
||||||
|
if (requestCode == REQUEST_PERMISSION_WRITE_STORAGE) {
|
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
showPermissionError(false);
|
||||||
|
readListOfFiles();
|
||||||
|
} else {
|
||||||
|
showPermissionError(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPermissionError(boolean on) {
|
||||||
|
if (on) {
|
||||||
|
list.setVisibility(View.GONE);
|
||||||
|
if (ad != null)
|
||||||
|
ad.show(false);
|
||||||
|
permissionErrorText.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
permissionErrorText.setVisibility(View.GONE);
|
||||||
|
if (ad != null)
|
||||||
|
ad.show(true);
|
||||||
|
list.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showFileDetails(String filename) {
|
||||||
|
MaterialDialog dialog = new MaterialDialog.Builder(this)
|
||||||
|
.title(R.string.file_properties_dialog_title)
|
||||||
|
.customView(R.layout.fragment_file_details, true)
|
||||||
|
.negativeText(R.string.file_properties_dialog_close_button)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TextView name = (TextView) dialog.getCustomView().findViewById(R.id.name_value);
|
||||||
|
name.setText(FilenameUtils.getName(filename));
|
||||||
|
|
||||||
|
TextView path = (TextView) dialog.getCustomView().findViewById(R.id.path_value);
|
||||||
|
path.setText(FilenameUtils.getPath(filename));
|
||||||
|
|
||||||
|
File file = new File(filename);
|
||||||
|
long filesize = file.length();
|
||||||
|
|
||||||
|
TextView size = (TextView) dialog.getCustomView().findViewById(R.id.size_value);
|
||||||
|
String svFmt = getString(R.string.file_properties_dialog_size_value);
|
||||||
|
size.setText(String.format(svFmt, FileUtils.byteCountToDisplaySize(filesize), filesize));
|
||||||
|
|
||||||
|
crc32 = (TextView) dialog.getCustomView().findViewById(R.id.crc32_value);
|
||||||
|
md5 = (TextView) dialog.getCustomView().findViewById(R.id.md5_value);
|
||||||
|
sha1 = (TextView) dialog.getCustomView().findViewById(R.id.sha1_value);
|
||||||
|
new FileChecksumsTask().execute(file);
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
static public class FileEntry {
|
||||||
|
private int icon;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public int getIcon() {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIcon(int icon) {
|
||||||
|
this.icon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FileChecksumsTask extends AsyncTask<File, Void, HashMap<String, String>> {
|
||||||
|
@Override
|
||||||
|
protected HashMap<String, String> doInBackground(File... params) {
|
||||||
|
HashMap<String, String> checksum = null;
|
||||||
|
try {
|
||||||
|
if (params.length > 0)
|
||||||
|
checksum = getFileChecksums(params[0]);
|
||||||
|
} catch (NoSuchAlgorithmException | IOException | IllegalArgumentException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(HashMap<String, String> result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
if (result != null) {
|
||||||
|
crc32.setText(result.get(CRC32));
|
||||||
|
md5.setText(result.get(MD5));
|
||||||
|
sha1.setText(result.get(SHA1));
|
||||||
|
} else {
|
||||||
|
crc32.setText("-");
|
||||||
|
md5.setText("-");
|
||||||
|
sha1.setText("-");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap<String, String> getFileChecksums(File file) throws IOException, NoSuchAlgorithmException, IllegalArgumentException
|
||||||
|
{
|
||||||
|
if (file.isDirectory())
|
||||||
|
throw new IllegalArgumentException("Unable calculate checksum for directory");
|
||||||
|
|
||||||
|
FileInputStream fis = new FileInputStream(file);
|
||||||
|
|
||||||
|
CRC32 crc32Digest = new CRC32();
|
||||||
|
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
|
||||||
|
MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1");
|
||||||
|
|
||||||
|
byte[] byteArray = new byte[32768];
|
||||||
|
int bytesCount = 0;
|
||||||
|
while ((bytesCount = fis.read(byteArray)) != -1) {
|
||||||
|
crc32Digest.update(byteArray, 0, bytesCount);
|
||||||
|
md5Digest.update(byteArray, 0, bytesCount);
|
||||||
|
sha1Digest.update(byteArray, 0, bytesCount);
|
||||||
|
}
|
||||||
|
fis.close();
|
||||||
|
|
||||||
|
String crc32 = Long.toHexString(crc32Digest.getValue());
|
||||||
|
String md5 = bytesToHexString(md5Digest.digest());
|
||||||
|
String sha1 = bytesToHexString(sha1Digest.digest());
|
||||||
|
|
||||||
|
HashMap<String, String> checksum = new HashMap<>();
|
||||||
|
checksum.put(CRC32, crc32);
|
||||||
|
checksum.put(MD5, md5);
|
||||||
|
checksum.put(SHA1, sha1);
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String bytesToHexString(byte[] bytes) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for(int i = 0; i < bytes.length ;i++) {
|
||||||
|
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.activity;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.design.widget.TabLayout;
|
||||||
|
import android.support.v4.view.PagerAdapter;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.emunix.unipatcher.ui.adapter.HelpPagerAdapter;
|
||||||
|
|
||||||
|
public class HelpActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_help);
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
|
TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
|
||||||
|
tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.help_activity_faq_tab_title)));
|
||||||
|
tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.help_activity_changelog_tab_title)));
|
||||||
|
tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.help_activity_about_tab_title)));
|
||||||
|
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
|
||||||
|
|
||||||
|
final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
|
||||||
|
final PagerAdapter adapter = new HelpPagerAdapter(getSupportFragmentManager(), tabLayout.getTabCount());
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
|
||||||
|
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onTabSelected(TabLayout.Tab tab) {
|
||||||
|
viewPager.setCurrentItem(tab.getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabUnselected(TabLayout.Tab tab) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabReselected(TabLayout.Tab tab) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.activity_help, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home:
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
case R.id.action_send_feedback:
|
||||||
|
sendFeedback();
|
||||||
|
return true;
|
||||||
|
case R.id.action_visit_website:
|
||||||
|
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.app_site)));
|
||||||
|
startActivity(browserIntent);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendFeedback() {
|
||||||
|
Intent feedbackIntent = new Intent(Intent.ACTION_SEND);
|
||||||
|
feedbackIntent.setType("message/rfc822");
|
||||||
|
feedbackIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{getString(R.string.app_email)});
|
||||||
|
feedbackIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.app_name));
|
||||||
|
try {
|
||||||
|
startActivity(Intent.createChooser(feedbackIntent, getString(R.string.send_feedback_dialog_title)));
|
||||||
|
} catch (android.content.ActivityNotFoundException ex) {
|
||||||
|
Toast.makeText(this, R.string.send_feedback_error_no_email_apps, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,271 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013-2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.activity;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.support.design.widget.FloatingActionButton;
|
||||||
|
import android.support.design.widget.NavigationView;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
import android.support.v4.view.GravityCompat;
|
||||||
|
import android.support.v4.widget.DrawerLayout;
|
||||||
|
import android.support.v7.app.ActionBarDrawerToggle;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.anjlab.android.iab.v3.BillingProcessor;
|
||||||
|
import com.anjlab.android.iab.v3.TransactionDetails;
|
||||||
|
import com.google.firebase.analytics.FirebaseAnalytics;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.BuildConfig;
|
||||||
|
import org.emunix.unipatcher.Globals;
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.emunix.unipatcher.Utils;
|
||||||
|
import org.emunix.unipatcher.ad.AdMobController;
|
||||||
|
import org.emunix.unipatcher.ui.dialog.RateThisApp;
|
||||||
|
import org.emunix.unipatcher.ui.fragment.ActionFragment;
|
||||||
|
import org.emunix.unipatcher.ui.fragment.PatchingFragment;
|
||||||
|
import org.emunix.unipatcher.ui.fragment.SmdFixChecksumFragment;
|
||||||
|
import org.emunix.unipatcher.ui.fragment.SnesSmcHeaderFragment;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity
|
||||||
|
implements NavigationView.OnNavigationItemSelectedListener {
|
||||||
|
private static final String LOG_TAG = "org.emunix.unipatcher";
|
||||||
|
|
||||||
|
private static final String SKU_FULL = "full";
|
||||||
|
private static final String SKU_REMOVE_ADS = "ad";
|
||||||
|
private boolean readyToPurchase = false;
|
||||||
|
private BillingProcessor bp;
|
||||||
|
private AdMobController ad;
|
||||||
|
private FirebaseAnalytics firebaseAnalytics;
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
context = this;
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
firebaseAnalytics = FirebaseAnalytics.getInstance(this);
|
||||||
|
if (BuildConfig.DEBUG)
|
||||||
|
firebaseAnalytics.setAnalyticsCollectionEnabled(false);
|
||||||
|
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||||
|
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
|
||||||
|
this, drawer, toolbar, R.string.nav_drawer_open, R.string.nav_drawer_close);
|
||||||
|
drawer.addDrawerListener(toggle);
|
||||||
|
toggle.syncState();
|
||||||
|
|
||||||
|
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
|
||||||
|
fab.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||||
|
ActionFragment fragment = (ActionFragment) fragmentManager.findFragmentById(R.id.content_frame);
|
||||||
|
if (fragment != null){
|
||||||
|
boolean ret = fragment.runAction();
|
||||||
|
}
|
||||||
|
//Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
|
||||||
|
// .setAction("Action", null).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
|
||||||
|
navigationView.setNavigationItemSelectedListener(this);
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
selectDrawerItem(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseArgument();
|
||||||
|
bp = new BillingProcessor(this, Globals.getKey(), new BillingProcessor.IBillingHandler() {
|
||||||
|
@Override
|
||||||
|
public void onBillingInitialized() {
|
||||||
|
Log.d(LOG_TAG, "Billing initialized");
|
||||||
|
readyToPurchase = true;
|
||||||
|
if (bp.isPurchased(SKU_FULL) || bp.isPurchased(SKU_REMOVE_ADS)) {
|
||||||
|
setFullVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onProductPurchased(String productId, TransactionDetails details) {
|
||||||
|
Log.d(LOG_TAG, "Item purchased: " + productId);
|
||||||
|
complain(getString(R.string.purchase_successful));
|
||||||
|
setFullVersion();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onBillingError(int errorCode, Throwable error) {
|
||||||
|
if (errorCode != 110) // cancel purchase
|
||||||
|
complain("Billing error: " + Integer.toString(errorCode));
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onPurchaseHistoryRestored() {
|
||||||
|
for(String sku : bp.listOwnedProducts())
|
||||||
|
Log.d(LOG_TAG, "Owned Managed Product: " + sku);
|
||||||
|
if (bp.isPurchased(SKU_FULL) || bp.isPurchased(SKU_REMOVE_ADS)) {
|
||||||
|
setFullVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
RateThisApp.launch(this);
|
||||||
|
|
||||||
|
// Load ads
|
||||||
|
if (!Globals.isFullVersion()) {
|
||||||
|
Handler adHandler = new Handler();
|
||||||
|
adHandler.postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
FrameLayout adView = (FrameLayout) findViewById(R.id.adView);
|
||||||
|
ad = new AdMobController(context, adView);
|
||||||
|
if (!Utils.isOnline(context))
|
||||||
|
ad.show(false);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onNavigationItemSelected(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
|
||||||
|
if (id == R.id.nav_apply_patch) {
|
||||||
|
selectDrawerItem(0);
|
||||||
|
} else if (id == R.id.nav_smd_fix_checksum) {
|
||||||
|
selectDrawerItem(1);
|
||||||
|
} else if (id == R.id.nav_snes_add_del_smc_header) {
|
||||||
|
selectDrawerItem(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||||
|
drawer.closeDrawer(GravityCompat.START);
|
||||||
|
|
||||||
|
if (id == R.id.nav_settings) {
|
||||||
|
Intent settingsIntent = new Intent(this, SettingsActivity.class);
|
||||||
|
startActivity(settingsIntent);
|
||||||
|
} else if (id == R.id.nav_rate) {
|
||||||
|
RateThisApp.rate(this);
|
||||||
|
} else if (id == R.id.nav_buy) {
|
||||||
|
buyFullVersion();
|
||||||
|
} else if (id == R.id.nav_share) {
|
||||||
|
shareApp();
|
||||||
|
} else if (id == R.id.nav_help) {
|
||||||
|
Intent helpIntent = new Intent(this, HelpActivity.class);
|
||||||
|
startActivity(helpIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectDrawerItem(int position) {
|
||||||
|
// update the main content by replacing fragments
|
||||||
|
Fragment fragment;
|
||||||
|
switch (position) {
|
||||||
|
case 1: fragment = new SmdFixChecksumFragment(); break;
|
||||||
|
case 2: fragment = new SnesSmcHeaderFragment(); break;
|
||||||
|
default: fragment = new PatchingFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||||
|
ft.setCustomAnimations(R.anim.slide_from_bottom, android.R.anim.fade_out);
|
||||||
|
ft.replace(R.id.content_frame, fragment).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseArgument() {
|
||||||
|
try {
|
||||||
|
String arg = getIntent().getData().getPath();
|
||||||
|
Globals.setCmdArgument(arg);
|
||||||
|
Log.d(LOG_TAG, "Cmd argument: " + arg);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
Log.e(LOG_TAG, "NullPointerException in argument fetching");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||||
|
Log.d(LOG_TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
|
||||||
|
if (!bp.handleActivityResult(requestCode, resultCode, data))
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shareApp() {
|
||||||
|
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||||
|
shareIntent.setType("text/plain");
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.app_name));
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.share_text) + "https://play.google.com/store/apps/details?id=org.eminix.unipatcher");
|
||||||
|
startActivity(Intent.createChooser(shareIntent, getString(R.string.share_dialog_title)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buyFullVersion() {
|
||||||
|
if (readyToPurchase)
|
||||||
|
bp.purchase(this, SKU_REMOVE_ADS);
|
||||||
|
else
|
||||||
|
complain("Billing not initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFullVersion() {
|
||||||
|
Globals.setFullVersion();
|
||||||
|
if (ad != null)
|
||||||
|
ad.show(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void complain(String message) {
|
||||||
|
Log.d(LOG_TAG, message);
|
||||||
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
if (ad != null) {
|
||||||
|
ad.pause();
|
||||||
|
}
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (ad != null) {
|
||||||
|
ad.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
if (bp != null) {
|
||||||
|
bp.release();
|
||||||
|
}
|
||||||
|
if (ad != null) {
|
||||||
|
ad.destroy();
|
||||||
|
}
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.activity;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.emunix.unipatcher.ui.fragment.SettingsFragment;
|
||||||
|
|
||||||
|
public class SettingsActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_settings);
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.content, new SettingsFragment())
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home:
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.adapter;
|
||||||
|
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.emunix.unipatcher.ui.activity.FilePickerActivity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FilePickerAdapter extends RecyclerView.Adapter<FilePickerAdapter.ViewHolder> {
|
||||||
|
private List<FilePickerActivity.FileEntry> data;
|
||||||
|
private static OnItemClickListener listener;
|
||||||
|
|
||||||
|
public interface OnItemClickListener {
|
||||||
|
void onItemClick(View v, int position);
|
||||||
|
void onItemLongClick(View v, int position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||||
|
FilePickerAdapter.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private TextView name;
|
||||||
|
private ImageView icon;
|
||||||
|
|
||||||
|
public ViewHolder(final View view) {
|
||||||
|
super(view);
|
||||||
|
name = (TextView) view.findViewById(R.id.row_text);
|
||||||
|
icon = (ImageView) view.findViewById(R.id.row_image);
|
||||||
|
view.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (listener != null)
|
||||||
|
listener.onItemClick(view, getLayoutPosition());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
if (listener != null)
|
||||||
|
listener.onItemLongClick(view, getLayoutPosition());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Typeface roboto_light = Typeface.createFromAsset(name.getContext().getAssets(), "fonts/Roboto-Light.ttf");
|
||||||
|
name.setTypeface(roboto_light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilePickerAdapter(List<FilePickerActivity.FileEntry> data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FilePickerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.file_picker_row_item, parent, false);
|
||||||
|
return new ViewHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
|
FilePickerActivity.FileEntry entry = data.get(position);
|
||||||
|
holder.name.setText(entry.getName());
|
||||||
|
holder.icon.setImageResource(entry.getIcon());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.adapter;
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.ui.fragment.AboutFragment;
|
||||||
|
import org.emunix.unipatcher.ui.fragment.ChangelogFragment;
|
||||||
|
import org.emunix.unipatcher.ui.fragment.FaqFragment;
|
||||||
|
|
||||||
|
public class HelpPagerAdapter extends FragmentStatePagerAdapter {
|
||||||
|
int numOfTabs;
|
||||||
|
|
||||||
|
public HelpPagerAdapter(FragmentManager manager, int numOfTabs) {
|
||||||
|
super(manager);
|
||||||
|
this.numOfTabs = numOfTabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
FaqFragment tab1 = new FaqFragment();
|
||||||
|
return tab1;
|
||||||
|
case 1:
|
||||||
|
ChangelogFragment tab2 = new ChangelogFragment();
|
||||||
|
return tab2;
|
||||||
|
case 2:
|
||||||
|
AboutFragment tab3 = new AboutFragment();
|
||||||
|
return tab3;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return numOfTabs;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.dialog;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
|
||||||
|
public class RateThisApp {
|
||||||
|
|
||||||
|
private final static int LAUNCHES_UNTIL_PROMPT = 7;
|
||||||
|
|
||||||
|
public static void launch(Context context) {
|
||||||
|
SharedPreferences preferences = context.getSharedPreferences("RateThisApp", 0);
|
||||||
|
if (preferences.getBoolean("dont_show_again", false))
|
||||||
|
return;
|
||||||
|
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
long launchCount = preferences.getLong("launch_count", 0) + 1;
|
||||||
|
editor.putLong("launch_count", launchCount);
|
||||||
|
|
||||||
|
if (launchCount % LAUNCHES_UNTIL_PROMPT == 0)
|
||||||
|
showDialog(context);
|
||||||
|
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void rate(Context context) {
|
||||||
|
String packageName = context.getApplicationContext().getPackageName();
|
||||||
|
Intent rateAppIntent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
rateAppIntent.setData(Uri.parse("market://details?id=" + packageName));
|
||||||
|
if (context.getPackageManager().queryIntentActivities(rateAppIntent, 0).size() == 0) {
|
||||||
|
// Market app is not installed. Open web browser
|
||||||
|
rateAppIntent.setData(Uri.parse("https://play.google.com/store/apps/details?id=" + packageName));
|
||||||
|
}
|
||||||
|
context.startActivity(rateAppIntent);
|
||||||
|
dontShowDialogAgain(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showDialog(final Context context) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
|
builder.setTitle(R.string.rate_dialog_title);
|
||||||
|
builder.setMessage(R.string.rate_dialog_message);
|
||||||
|
builder.setPositiveButton(R.string.rate_dialog_ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
rate(context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.setNegativeButton(R.string.rate_dialog_later, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
dialogInterface.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AlertDialog dialog = builder.create();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void dontShowDialogAgain(Context context) {
|
||||||
|
SharedPreferences preferences = context.getSharedPreferences("RateThisApp", 0);
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.putBoolean("dont_show_again", true);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.fragment;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.emunix.unipatcher.Utils;
|
||||||
|
import org.sufficientlysecure.htmltextview.HtmlResImageGetter;
|
||||||
|
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||||
|
|
||||||
|
public class AboutFragment extends Fragment {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_about, container, false);
|
||||||
|
|
||||||
|
TextView versionText = (TextView) view.findViewById(R.id.versionText);
|
||||||
|
versionText.setText(getString(R.string.help_activity_about_tab_version, Utils.getAppVersion(getActivity())));
|
||||||
|
HtmlTextView aboutText = (HtmlTextView) view.findViewById(R.id.aboutText);
|
||||||
|
aboutText.setHtml(R.raw.about, new HtmlResImageGetter(aboutText));
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014, 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.fragment;
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
|
public abstract class ActionFragment extends Fragment {
|
||||||
|
|
||||||
|
public abstract boolean runAction();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.fragment;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.sufficientlysecure.htmltextview.HtmlResImageGetter;
|
||||||
|
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||||
|
|
||||||
|
public class ChangelogFragment extends Fragment {
|
||||||
|
|
||||||
|
public ChangelogFragment() {
|
||||||
|
// Required empty public constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_changelog, container, false);
|
||||||
|
|
||||||
|
HtmlTextView changelogText = (HtmlTextView) view.findViewById(R.id.changelogText);
|
||||||
|
changelogText.setHtml(R.raw.changelog, new HtmlResImageGetter(changelogText));
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.fragment;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.sufficientlysecure.htmltextview.HtmlResImageGetter;
|
||||||
|
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||||
|
|
||||||
|
public class FaqFragment extends Fragment {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_faq, container, false);
|
||||||
|
|
||||||
|
HtmlTextView faqText = (HtmlTextView) view.findViewById(R.id.faqText);
|
||||||
|
faqText.setHtml(R.raw.faq, new HtmlResImageGetter(faqText));
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,268 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014, 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.fragment;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.widget.CardView;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
import org.emunix.unipatcher.Globals;
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.emunix.unipatcher.Settings;
|
||||||
|
import org.emunix.unipatcher.Utils;
|
||||||
|
import org.emunix.unipatcher.WorkerService;
|
||||||
|
import org.emunix.unipatcher.ui.activity.FilePickerActivity;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class PatchingFragment extends ActionFragment implements View.OnClickListener {
|
||||||
|
|
||||||
|
private static final String LOG_TAG = "org.emunix.unipatcher";
|
||||||
|
|
||||||
|
private static final int SELECT_ROM_FILE = 1;
|
||||||
|
private static final int SELECT_PATCH_FILE = 2;
|
||||||
|
private TextView romNameTextView;
|
||||||
|
private TextView patchNameTextView;
|
||||||
|
private TextView outputNameTextView;
|
||||||
|
private String romPath = null;
|
||||||
|
private String patchPath = null;
|
||||||
|
private String outputPath = null;
|
||||||
|
|
||||||
|
public PatchingFragment() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.patching_fragment, container, false);
|
||||||
|
|
||||||
|
romNameTextView = (TextView) view.findViewById(R.id.romNameTextView);
|
||||||
|
patchNameTextView = (TextView) view.findViewById(R.id.patchNameTextView);
|
||||||
|
outputNameTextView = (TextView) view.findViewById(R.id.outputNameTextView);
|
||||||
|
|
||||||
|
CardView patchCardView = (CardView) view.findViewById(R.id.patchCardView);
|
||||||
|
patchCardView.setOnClickListener(this);
|
||||||
|
CardView romCardView = (CardView) view.findViewById(R.id.romCardView);
|
||||||
|
romCardView.setOnClickListener(this);
|
||||||
|
CardView outputCardView = (CardView) view.findViewById(R.id.outputCardView);
|
||||||
|
outputCardView.setOnClickListener(this);
|
||||||
|
|
||||||
|
restoreState(savedInstanceState);
|
||||||
|
|
||||||
|
setFonts(view);
|
||||||
|
|
||||||
|
// Set action bar title
|
||||||
|
getActivity().setTitle(R.string.nav_apply_patch);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
parseArgument();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseArgument() {
|
||||||
|
patchPath = Globals.getCmdArgument();
|
||||||
|
if (patchPath != null) {
|
||||||
|
patchNameTextView.setText(new File(patchPath).getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFonts(View view) {
|
||||||
|
TextView patchLabel = (TextView) view.findViewById(R.id.patchLabel);
|
||||||
|
TextView romLabel = (TextView) view.findViewById(R.id.romLabel);
|
||||||
|
TextView outputLabel = (TextView) view.findViewById(R.id.outputLabel);
|
||||||
|
|
||||||
|
Typeface roboto_light = Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Light.ttf");
|
||||||
|
|
||||||
|
patchLabel.setTypeface(roboto_light);
|
||||||
|
romLabel.setTypeface(roboto_light);
|
||||||
|
outputLabel.setTypeface(roboto_light);
|
||||||
|
patchNameTextView.setTypeface(roboto_light);
|
||||||
|
romNameTextView.setTypeface(roboto_light);
|
||||||
|
outputNameTextView.setTypeface(roboto_light);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreState(Bundle savedInstanceState) {
|
||||||
|
if (savedInstanceState != null){
|
||||||
|
romPath = savedInstanceState.getString("romPath");
|
||||||
|
patchPath = savedInstanceState.getString("patchPath");
|
||||||
|
outputPath = savedInstanceState.getString("outputPath");
|
||||||
|
if (romPath != null)
|
||||||
|
romNameTextView.setText(new File(romPath).getName());
|
||||||
|
if (patchPath != null)
|
||||||
|
patchNameTextView.setText(new File (patchPath).getName());
|
||||||
|
if (outputPath != null)
|
||||||
|
outputNameTextView.setText(new File(outputPath).getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||||
|
super.onSaveInstanceState(savedInstanceState);
|
||||||
|
savedInstanceState.putString("romPath", romPath);
|
||||||
|
savedInstanceState.putString("patchPath", patchPath);
|
||||||
|
savedInstanceState.putString("outputPath", outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view){
|
||||||
|
Intent intent = new Intent(getActivity(), FilePickerActivity.class);
|
||||||
|
switch (view.getId()) {
|
||||||
|
case R.id.patchCardView:
|
||||||
|
intent.putExtra("title", getString(R.string.file_picker_activity_title_select_patch));
|
||||||
|
intent.putExtra("directory", Settings.getPatchDir(getActivity()));
|
||||||
|
startActivityForResult(intent, SELECT_PATCH_FILE);
|
||||||
|
break;
|
||||||
|
case R.id.romCardView:
|
||||||
|
intent.putExtra("title", getString(R.string.file_picker_activity_title_select_rom));
|
||||||
|
intent.putExtra("directory", Settings.getRomDir(getActivity()));
|
||||||
|
startActivityForResult(intent, SELECT_ROM_FILE);
|
||||||
|
break;
|
||||||
|
case R.id.outputCardView:
|
||||||
|
renameOutputRom();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||||
|
Log.d(LOG_TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
String path = data.getStringExtra("path");
|
||||||
|
File fpath = new File(path);
|
||||||
|
|
||||||
|
if (Utils.isArchive(path)) {
|
||||||
|
Toast.makeText(getActivity(), R.string.main_activity_toast_archives_not_supported, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (requestCode) {
|
||||||
|
case SELECT_ROM_FILE:
|
||||||
|
romPath = path;
|
||||||
|
romNameTextView.setVisibility(View.VISIBLE);
|
||||||
|
romNameTextView.setText(fpath.getName());
|
||||||
|
Settings.setLastRomDir(getActivity(), fpath.getParent());
|
||||||
|
outputPath = makeOutputPath(path);
|
||||||
|
outputNameTextView.setText(new File(outputPath).getName());
|
||||||
|
break;
|
||||||
|
case SELECT_PATCH_FILE:
|
||||||
|
patchPath = path;
|
||||||
|
patchNameTextView.setVisibility(View.VISIBLE);
|
||||||
|
patchNameTextView.setText(fpath.getName());
|
||||||
|
Settings.setLastPatchDir(getActivity(), fpath.getParent());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String makeOutputPath(String fullname) {
|
||||||
|
String dir = FilenameUtils.getPath(fullname);
|
||||||
|
String baseName = FilenameUtils.getBaseName(fullname);
|
||||||
|
String ext = FilenameUtils.getExtension(fullname);
|
||||||
|
return FilenameUtils.concat(dir, baseName.concat(" [patched].").concat(ext));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean runAction(){
|
||||||
|
if (romPath == null & patchPath == null){
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.main_activity_toast_rom_and_patch_not_selected), Toast.LENGTH_LONG).show();
|
||||||
|
return false;
|
||||||
|
} else if (romPath == null){
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.main_activity_toast_rom_not_selected), Toast.LENGTH_LONG).show();
|
||||||
|
return false;
|
||||||
|
} else if (patchPath == null){
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.main_activity_toast_patch_not_selected), Toast.LENGTH_LONG).show();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(getActivity(), WorkerService.class);
|
||||||
|
intent.putExtra("action", Globals.ACTION_PATCHING);
|
||||||
|
intent.putExtra("romPath", romPath);
|
||||||
|
intent.putExtra("patchPath", patchPath);
|
||||||
|
intent.putExtra("outputPath", outputPath);
|
||||||
|
getActivity().startService(intent);
|
||||||
|
|
||||||
|
Toast.makeText(getActivity(), R.string.toast_patching_started_check_notify,Toast.LENGTH_SHORT).show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renameOutputRom(){
|
||||||
|
if (romPath == null) {
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.main_activity_toast_rom_not_selected), Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity());
|
||||||
|
dialog.setTitle(R.string.dialog_rename_title);
|
||||||
|
final EditText input = new EditText(getActivity());
|
||||||
|
input.setText(outputNameTextView.getText());
|
||||||
|
|
||||||
|
// add left and right margins to EditText.
|
||||||
|
FrameLayout container = new FrameLayout(getActivity());
|
||||||
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
int dp_24 = Utils.dpToPx(getActivity(), 24);
|
||||||
|
params.setMargins(dp_24, 0, dp_24, 0);
|
||||||
|
input.setLayoutParams(params);
|
||||||
|
container.addView(input);
|
||||||
|
dialog.setView(container);
|
||||||
|
|
||||||
|
dialog.setPositiveButton(R.string.dialog_rename_ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
String newName = input.getText().toString();
|
||||||
|
if (newName.equals(romNameTextView.getText())) {
|
||||||
|
Toast.makeText(getActivity(), R.string.dialog_rename_error_same_name, Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newName.contains("/")) {
|
||||||
|
newName = newName.replaceAll("/", "_");
|
||||||
|
Toast.makeText(getActivity(), R.string.dialog_rename_error_invalid_chars, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
outputNameTextView.setText(newName);
|
||||||
|
outputPath = new File(romPath).getParent().concat(File.separator).concat(newName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.setNegativeButton(R.string.dialog_rename_cancel, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2016 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.fragment;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
|
||||||
|
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
|
setPreferencesFromResource(R.xml.preferences, rootKey);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.fragment;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.widget.CardView;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.Globals;
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.emunix.unipatcher.Settings;
|
||||||
|
import org.emunix.unipatcher.Utils;
|
||||||
|
import org.emunix.unipatcher.WorkerService;
|
||||||
|
import org.emunix.unipatcher.ui.activity.FilePickerActivity;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class SmdFixChecksumFragment extends ActionFragment implements View.OnClickListener {
|
||||||
|
private static final String LOG_TAG = "org.emunix.unipatcher";
|
||||||
|
|
||||||
|
private static final int SELECT_ROM_FILE = 1;
|
||||||
|
|
||||||
|
private TextView romNameTextView;
|
||||||
|
private TextView fixChecksumInfoTextview;
|
||||||
|
private String romPath = null;
|
||||||
|
|
||||||
|
public SmdFixChecksumFragment() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.smd_fix_checksum_fragment, container, false);
|
||||||
|
|
||||||
|
romNameTextView = (TextView) view.findViewById(R.id.romNameTextView);
|
||||||
|
fixChecksumInfoTextview = (TextView) view.findViewById(R.id.fixChecksumInfoTextView);
|
||||||
|
|
||||||
|
CardView romCardView = (CardView) view.findViewById(R.id.romCardView);
|
||||||
|
romCardView.setOnClickListener(this);
|
||||||
|
|
||||||
|
restoreState(savedInstanceState);
|
||||||
|
|
||||||
|
setFonts(view);
|
||||||
|
|
||||||
|
// Set action bar title
|
||||||
|
getActivity().setTitle(R.string.nav_smd_fix_checksum);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFonts(View view) {
|
||||||
|
TextView romLabel = (TextView) view.findViewById(R.id.romLabel);
|
||||||
|
|
||||||
|
Typeface roboto_light = Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Light.ttf");
|
||||||
|
|
||||||
|
romLabel.setTypeface(roboto_light);
|
||||||
|
romNameTextView.setTypeface(roboto_light);
|
||||||
|
fixChecksumInfoTextview.setTypeface((roboto_light));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreState(Bundle savedInstanceState) {
|
||||||
|
if (savedInstanceState != null){
|
||||||
|
romPath = savedInstanceState.getString("romPath");
|
||||||
|
if (romPath != null)
|
||||||
|
romNameTextView.setText(new File(romPath).getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||||
|
super.onSaveInstanceState(savedInstanceState);
|
||||||
|
savedInstanceState.putString("romPath", romPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view){
|
||||||
|
Intent intent = new Intent(getActivity(), FilePickerActivity.class);
|
||||||
|
switch (view.getId()) {
|
||||||
|
case R.id.romCardView:
|
||||||
|
intent.putExtra("title", getString(R.string.file_picker_activity_title_select_rom));
|
||||||
|
intent.putExtra("directory", Settings.getRomDir(getActivity()));
|
||||||
|
startActivityForResult(intent, SELECT_ROM_FILE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||||
|
Log.d(LOG_TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
String path = data.getStringExtra("path");
|
||||||
|
|
||||||
|
if (Utils.isArchive(path)) {
|
||||||
|
Toast.makeText(getActivity(), R.string.main_activity_toast_archives_not_supported, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (requestCode) {
|
||||||
|
case SELECT_ROM_FILE:
|
||||||
|
romPath = path;
|
||||||
|
romNameTextView.setVisibility(View.VISIBLE);
|
||||||
|
romNameTextView.setText(new File(path).getName());
|
||||||
|
Settings.setLastRomDir(getActivity(), new File(path).getParent());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean runAction(){
|
||||||
|
if (romPath == null){
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.main_activity_toast_rom_not_selected), Toast.LENGTH_LONG).show();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(getActivity(), WorkerService.class);
|
||||||
|
intent.putExtra("action", Globals.ACTION_SMD_FIX_CHECKSUM);
|
||||||
|
intent.putExtra("romPath", romPath);
|
||||||
|
getActivity().startService(intent);
|
||||||
|
|
||||||
|
Toast.makeText(getActivity(), R.string.notify_smd_fix_checksum_started_check_notify,Toast.LENGTH_SHORT).show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.fragment;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.widget.CardView;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.Globals;
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
import org.emunix.unipatcher.Settings;
|
||||||
|
import org.emunix.unipatcher.Utils;
|
||||||
|
import org.emunix.unipatcher.WorkerService;
|
||||||
|
import org.emunix.unipatcher.tools.SnesSmcHeader;
|
||||||
|
import org.emunix.unipatcher.ui.activity.FilePickerActivity;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class SnesSmcHeaderFragment extends ActionFragment implements View.OnClickListener {
|
||||||
|
private static final String LOG_TAG = "org.emunix.unipatcher";
|
||||||
|
private static final int SELECT_ROM_FILE = 1;
|
||||||
|
private static final int SELECT_HEADER_FILE = 2;
|
||||||
|
|
||||||
|
private TextView romNameTextView;
|
||||||
|
private TextView headerNameTextView;
|
||||||
|
private TextView headerInfoTextView;
|
||||||
|
private CardView headerCardView;
|
||||||
|
private String romPath = null;
|
||||||
|
private String headerPath = null;
|
||||||
|
|
||||||
|
private int action = 0;
|
||||||
|
|
||||||
|
public SnesSmcHeaderFragment() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.snes_smc_header_fragment, container, false);
|
||||||
|
|
||||||
|
romNameTextView = (TextView) view.findViewById(R.id.romNameTextView);
|
||||||
|
headerNameTextView = (TextView) view.findViewById(R.id.headerNameTextView);
|
||||||
|
headerInfoTextView = (TextView) view.findViewById(R.id.headerInfoTextView);
|
||||||
|
|
||||||
|
CardView romCardView = (CardView) view.findViewById(R.id.romCardView);
|
||||||
|
romCardView.setOnClickListener(this);
|
||||||
|
headerCardView = (CardView) view.findViewById(R.id.headerCardView);
|
||||||
|
headerCardView.setOnClickListener(this);
|
||||||
|
|
||||||
|
restoreState(savedInstanceState);
|
||||||
|
|
||||||
|
setFonts(view);
|
||||||
|
|
||||||
|
// Set action bar title
|
||||||
|
getActivity().setTitle(R.string.nav_snes_add_del_smc_header);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFonts(View view) {
|
||||||
|
TextView romLabel = (TextView) view.findViewById(R.id.romLabel);
|
||||||
|
TextView headerLabel = (TextView) view.findViewById(R.id.headerLabel);
|
||||||
|
|
||||||
|
Typeface roboto_light = Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Light.ttf");
|
||||||
|
|
||||||
|
romLabel.setTypeface(roboto_light);
|
||||||
|
romNameTextView.setTypeface(roboto_light);
|
||||||
|
headerLabel.setTypeface(roboto_light);
|
||||||
|
headerNameTextView.setTypeface(roboto_light);
|
||||||
|
headerInfoTextView.setTypeface(roboto_light);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreState(Bundle savedInstanceState) {
|
||||||
|
if (savedInstanceState != null){
|
||||||
|
romPath = savedInstanceState.getString("romPath");
|
||||||
|
headerPath = savedInstanceState.getString("headerPath");
|
||||||
|
action = savedInstanceState.getInt("action");
|
||||||
|
if (action == Globals.ACTION_SNES_ADD_SMC_HEADER) {
|
||||||
|
headerInfoTextView.setText(R.string.snes_smc_header_will_be_added);
|
||||||
|
headerCardView.setVisibility(View.VISIBLE);
|
||||||
|
} else if (action == Globals.ACTION_SNES_DELETE_SMC_HEADER) {
|
||||||
|
headerInfoTextView.setText(R.string.snes_smc_header_will_be_removed);
|
||||||
|
headerCardView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
if (romPath != null)
|
||||||
|
romNameTextView.setText(new File(romPath).getName());
|
||||||
|
if (headerPath != null)
|
||||||
|
headerNameTextView.setText(new File(headerPath).getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||||
|
super.onSaveInstanceState(savedInstanceState);
|
||||||
|
savedInstanceState.putString("romPath", romPath);
|
||||||
|
savedInstanceState.putString("headerPath", headerPath);
|
||||||
|
savedInstanceState.putInt("action", action);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view){
|
||||||
|
Intent intent = new Intent(getActivity(), FilePickerActivity.class);
|
||||||
|
switch (view.getId()) {
|
||||||
|
case R.id.romCardView:
|
||||||
|
intent.putExtra("title", getString(R.string.file_picker_activity_title_select_rom));
|
||||||
|
intent.putExtra("directory", Settings.getRomDir(getActivity()));
|
||||||
|
startActivityForResult(intent, SELECT_ROM_FILE);
|
||||||
|
break;
|
||||||
|
case R.id.headerCardView:
|
||||||
|
intent.putExtra("title", getString(R.string.file_picker_activity_title_select_header));
|
||||||
|
startActivityForResult(intent, SELECT_HEADER_FILE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||||
|
Log.d(LOG_TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
String path = data.getStringExtra("path");
|
||||||
|
|
||||||
|
if (Utils.isArchive(path)) {
|
||||||
|
Toast.makeText(getActivity(), R.string.main_activity_toast_archives_not_supported, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (requestCode) {
|
||||||
|
case SELECT_ROM_FILE:
|
||||||
|
romPath = path;
|
||||||
|
romNameTextView.setVisibility(View.VISIBLE);
|
||||||
|
romNameTextView.setText(new File(path).getName());
|
||||||
|
Settings.setLastRomDir(getActivity(), new File(path).getParent());
|
||||||
|
SnesSmcHeader checker = new SnesSmcHeader();
|
||||||
|
if (checker.isHasSmcHeader(new File(path))) {
|
||||||
|
action = Globals.ACTION_SNES_DELETE_SMC_HEADER;
|
||||||
|
headerCardView.setVisibility(View.GONE);
|
||||||
|
headerInfoTextView.setText(R.string.snes_smc_header_will_be_removed);
|
||||||
|
} else {
|
||||||
|
action = Globals.ACTION_SNES_ADD_SMC_HEADER;
|
||||||
|
headerCardView.setVisibility(View.VISIBLE);
|
||||||
|
headerInfoTextView.setText(R.string.snes_smc_header_will_be_added);
|
||||||
|
}
|
||||||
|
headerPath = null;
|
||||||
|
headerNameTextView.setText(R.string.main_activity_tap_to_select);
|
||||||
|
break;
|
||||||
|
case SELECT_HEADER_FILE:
|
||||||
|
headerPath = path;
|
||||||
|
headerNameTextView.setText(new File(path).getName());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean runAction(){
|
||||||
|
if (romPath == null){
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.main_activity_toast_rom_not_selected), Toast.LENGTH_LONG).show();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(getActivity(), WorkerService.class);
|
||||||
|
intent.putExtra("action", action);
|
||||||
|
intent.putExtra("romPath", romPath);
|
||||||
|
intent.putExtra("headerPath", headerPath);
|
||||||
|
getActivity().startService(intent);
|
||||||
|
|
||||||
|
if (action == Globals.ACTION_SNES_ADD_SMC_HEADER) {
|
||||||
|
Toast.makeText(getActivity(), R.string.notify_snes_add_smc_header_stared_check_noify, Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getActivity(), R.string.notify_snes_delete_smc_header_stared_check_noify, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.emunix.unipatcher.ui.notify;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.app.NotificationManagerCompat;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.ui.activity.MainActivity;
|
||||||
|
|
||||||
|
public abstract class Notify {
|
||||||
|
protected static int count = 1;
|
||||||
|
protected final int ID = count;
|
||||||
|
protected Context context;
|
||||||
|
|
||||||
|
protected NotificationCompat.Builder notifyBuilder;
|
||||||
|
protected NotificationManagerCompat notifyMng;
|
||||||
|
|
||||||
|
public Notify(Context c) {
|
||||||
|
context = c;
|
||||||
|
count++;
|
||||||
|
|
||||||
|
Intent notificationIntent = new Intent(context, MainActivity.class);
|
||||||
|
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
notifyBuilder = new NotificationCompat.Builder(context);
|
||||||
|
notifyBuilder.setContentIntent(PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT));
|
||||||
|
//notifyMng = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
|
||||||
|
notifyMng = NotificationManagerCompat.from(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
notifyMng.notify(ID, notifyBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getID() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationCompat.Builder getNotifyBuilder() {
|
||||||
|
return notifyBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showResult(String message) {
|
||||||
|
if (message == null) {
|
||||||
|
setCompleted();
|
||||||
|
} else {
|
||||||
|
setFailed(message);
|
||||||
|
}
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void setCompleted();
|
||||||
|
public abstract void setFailed(String message);
|
||||||
|
|
||||||
|
public void setProgress(boolean isEnabled) {
|
||||||
|
notifyBuilder.setProgress(0, 0, isEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSticked(boolean isSticked) {
|
||||||
|
notifyBuilder.setAutoCancel(!isSticked);
|
||||||
|
notifyBuilder.setOngoing(isSticked);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2013 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.notify;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
|
||||||
|
public class PatchingNotify extends Notify {
|
||||||
|
|
||||||
|
public PatchingNotify(Context c, String text) {
|
||||||
|
super(c);
|
||||||
|
notifyBuilder.setSmallIcon(R.drawable.ic_stat_patching);
|
||||||
|
notifyBuilder.setContentTitle(context.getString(R.string.notify_applying_patch));
|
||||||
|
notifyBuilder.setContentText(text);
|
||||||
|
notifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(text));
|
||||||
|
setSticked(true);
|
||||||
|
setProgress(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCompleted() {
|
||||||
|
setProgress(false);
|
||||||
|
setSticked(false);
|
||||||
|
notifyBuilder.setTicker(context.getText(R.string.notify_patching_complete));
|
||||||
|
notifyBuilder.setContentTitle(context.getText(R.string.notify_patching_complete));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFailed(String message) {
|
||||||
|
setProgress(false);
|
||||||
|
setSticked(false);
|
||||||
|
notifyBuilder.setTicker(context.getText(R.string.notify_error));
|
||||||
|
notifyBuilder.setContentTitle(context.getString(R.string.notify_error));
|
||||||
|
notifyBuilder.setContentText(message);
|
||||||
|
notifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(message));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.notify;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
|
||||||
|
public class SmdFixChecksumNotify extends Notify {
|
||||||
|
|
||||||
|
public SmdFixChecksumNotify(Context c, String text) {
|
||||||
|
super(c);
|
||||||
|
notifyBuilder.setSmallIcon(R.drawable.ic_stat_patching);
|
||||||
|
notifyBuilder.setContentTitle(context.getString(R.string.notify_smd_fix_checksum_in_progress));
|
||||||
|
notifyBuilder.setContentText(text);
|
||||||
|
notifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(text));
|
||||||
|
setSticked(true);
|
||||||
|
setProgress(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCompleted() {
|
||||||
|
setProgress(false);
|
||||||
|
setSticked(false);
|
||||||
|
notifyBuilder.setTicker(context.getText(R.string.notify_smd_fix_checksum_complete));
|
||||||
|
notifyBuilder.setContentTitle(context.getText(R.string.notify_smd_fix_checksum_complete));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFailed(String message) {
|
||||||
|
setProgress(false);
|
||||||
|
setSticked(false);
|
||||||
|
notifyBuilder.setTicker(context.getText(R.string.notify_error));
|
||||||
|
notifyBuilder.setContentTitle(context.getString(R.string.notify_error));
|
||||||
|
notifyBuilder.setContentText(message);
|
||||||
|
notifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(message));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.notify;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
|
||||||
|
public class SnesAddSmcHeaderNotify extends Notify {
|
||||||
|
public SnesAddSmcHeaderNotify(Context c, String text) {
|
||||||
|
super(c);
|
||||||
|
notifyBuilder.setSmallIcon(R.drawable.ic_stat_patching);
|
||||||
|
notifyBuilder.setContentTitle(context.getString(R.string.notify_snes_add_smc_header_in_progress));
|
||||||
|
notifyBuilder.setContentText(text);
|
||||||
|
notifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(text));
|
||||||
|
setSticked(true);
|
||||||
|
setProgress(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCompleted() {
|
||||||
|
setProgress(false);
|
||||||
|
setSticked(false);
|
||||||
|
notifyBuilder.setTicker(context.getText(R.string.notify_snes_add_smc_header_complete));
|
||||||
|
notifyBuilder.setContentTitle(context.getText(R.string.notify_snes_add_smc_header_complete));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFailed(String message) {
|
||||||
|
setProgress(false);
|
||||||
|
setSticked(false);
|
||||||
|
notifyBuilder.setTicker(context.getText(R.string.notify_error));
|
||||||
|
notifyBuilder.setContentTitle(context.getString(R.string.notify_error));
|
||||||
|
notifyBuilder.setContentText(message);
|
||||||
|
notifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(message));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014 Boris Timofeev
|
||||||
|
|
||||||
|
This file is part of UniPatcher.
|
||||||
|
|
||||||
|
UniPatcher 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.
|
||||||
|
|
||||||
|
UniPatcher 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 UniPatcher. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.emunix.unipatcher.ui.notify;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
||||||
|
import org.emunix.unipatcher.R;
|
||||||
|
|
||||||
|
public class SnesDeleteSmcHeaderNotify extends Notify {
|
||||||
|
public SnesDeleteSmcHeaderNotify(Context c, String text) {
|
||||||
|
super(c);
|
||||||
|
notifyBuilder.setSmallIcon(R.drawable.ic_stat_patching);
|
||||||
|
notifyBuilder.setContentTitle(context.getString(R.string.notify_snes_delete_smc_header_in_progress));
|
||||||
|
notifyBuilder.setContentText(text);
|
||||||
|
notifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(text));
|
||||||
|
setSticked(true);
|
||||||
|
setProgress(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCompleted() {
|
||||||
|
setProgress(false);
|
||||||
|
setSticked(false);
|
||||||
|
notifyBuilder.setTicker(context.getText(R.string.notify_snes_delete_smc_header_complete));
|
||||||
|
notifyBuilder.setContentTitle(context.getText(R.string.notify_snes_delete_smc_header_complete));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFailed(String message) {
|
||||||
|
setProgress(false);
|
||||||
|
setSticked(false);
|
||||||
|
notifyBuilder.setTicker(context.getText(R.string.notify_error));
|
||||||
|
notifyBuilder.setContentTitle(context.getString(R.string.notify_error));
|
||||||
|
notifyBuilder.setContentText(message);
|
||||||
|
notifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(message));
|
||||||
|
}
|
||||||
|
}
|
10
app/src/main/res/anim/slide_from_bottom.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:interpolator="@android:anim/decelerate_interpolator">
|
||||||
|
|
||||||
|
<translate
|
||||||
|
android:fromYDelta="100%"
|
||||||
|
android:toYDelta="0"
|
||||||
|
android:duration="500" />
|
||||||
|
|
||||||
|
</set>
|
BIN
app/src/main/res/drawable-hdpi/ic_content_cut_grey600_24dp.png
Normal file
After Width: | Height: | Size: 514 B |
BIN
app/src/main/res/drawable-hdpi/ic_edit_grey600_24dp.png
Normal file
After Width: | Height: | Size: 341 B |
BIN
app/src/main/res/drawable-hdpi/ic_fingerprint_grey600_24dp.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_folder_grey600_24dp.png
Normal file
After Width: | Height: | Size: 227 B |
BIN
app/src/main/res/drawable-hdpi/ic_folder_upload_grey600_24dp.png
Normal file
After Width: | Height: | Size: 447 B |
BIN
app/src/main/res/drawable-hdpi/ic_healing_grey600_24dp.png
Normal file
After Width: | Height: | Size: 560 B |
BIN
app/src/main/res/drawable-hdpi/ic_help_grey600_24dp.png
Normal file
After Width: | Height: | Size: 588 B |
After Width: | Height: | Size: 250 B |
BIN
app/src/main/res/drawable-hdpi/ic_save_white_24dp.png
Normal file
After Width: | Height: | Size: 341 B |
BIN
app/src/main/res/drawable-hdpi/ic_settings_grey600_24dp.png
Normal file
After Width: | Height: | Size: 572 B |
BIN
app/src/main/res/drawable-hdpi/ic_share_grey600_24dp.png
Normal file
After Width: | Height: | Size: 513 B |
BIN
app/src/main/res/drawable-hdpi/ic_shopping_cart_grey600_24dp.png
Normal file
After Width: | Height: | Size: 428 B |
BIN
app/src/main/res/drawable-hdpi/ic_stat_patching.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_thumb_up_grey600_24dp.png
Normal file
After Width: | Height: | Size: 387 B |
BIN
app/src/main/res/drawable-mdpi/ic_content_cut_grey600_24dp.png
Normal file
After Width: | Height: | Size: 365 B |
BIN
app/src/main/res/drawable-mdpi/ic_edit_grey600_24dp.png
Normal file
After Width: | Height: | Size: 276 B |
BIN
app/src/main/res/drawable-mdpi/ic_fingerprint_grey600_24dp.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_folder_grey600_24dp.png
Normal file
After Width: | Height: | Size: 207 B |
BIN
app/src/main/res/drawable-mdpi/ic_folder_upload_grey600_24dp.png
Normal file
After Width: | Height: | Size: 306 B |
BIN
app/src/main/res/drawable-mdpi/ic_healing_grey600_24dp.png
Normal file
After Width: | Height: | Size: 440 B |
BIN
app/src/main/res/drawable-mdpi/ic_help_grey600_24dp.png
Normal file
After Width: | Height: | Size: 406 B |
After Width: | Height: | Size: 220 B |
BIN
app/src/main/res/drawable-mdpi/ic_save_white_24dp.png
Normal file
After Width: | Height: | Size: 257 B |
BIN
app/src/main/res/drawable-mdpi/ic_settings_grey600_24dp.png
Normal file
After Width: | Height: | Size: 423 B |
BIN
app/src/main/res/drawable-mdpi/ic_share_grey600_24dp.png
Normal file
After Width: | Height: | Size: 371 B |
BIN
app/src/main/res/drawable-mdpi/ic_shopping_cart_grey600_24dp.png
Normal file
After Width: | Height: | Size: 307 B |
BIN
app/src/main/res/drawable-mdpi/ic_stat_patching.png
Normal file
After Width: | Height: | Size: 696 B |
BIN
app/src/main/res/drawable-mdpi/ic_thumb_up_grey600_24dp.png
Normal file
After Width: | Height: | Size: 284 B |
BIN
app/src/main/res/drawable-xhdpi/ic_content_cut_grey600_24dp.png
Normal file
After Width: | Height: | Size: 606 B |
BIN
app/src/main/res/drawable-xhdpi/ic_edit_grey600_24dp.png
Normal file
After Width: | Height: | Size: 379 B |
BIN
app/src/main/res/drawable-xhdpi/ic_fingerprint_grey600_24dp.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_folder_grey600_24dp.png
Normal file
After Width: | Height: | Size: 284 B |
After Width: | Height: | Size: 498 B |
BIN
app/src/main/res/drawable-xhdpi/ic_healing_grey600_24dp.png
Normal file
After Width: | Height: | Size: 676 B |
BIN
app/src/main/res/drawable-xhdpi/ic_help_grey600_24dp.png
Normal file
After Width: | Height: | Size: 716 B |
After Width: | Height: | Size: 301 B |
BIN
app/src/main/res/drawable-xhdpi/ic_save_white_24dp.png
Normal file
After Width: | Height: | Size: 359 B |
BIN
app/src/main/res/drawable-xhdpi/ic_settings_grey600_24dp.png
Normal file
After Width: | Height: | Size: 704 B |
BIN
app/src/main/res/drawable-xhdpi/ic_share_grey600_24dp.png
Normal file
After Width: | Height: | Size: 629 B |
After Width: | Height: | Size: 511 B |
BIN
app/src/main/res/drawable-xhdpi/ic_stat_patching.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_thumb_up_grey600_24dp.png
Normal file
After Width: | Height: | Size: 415 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_content_cut_grey600_24dp.png
Normal file
After Width: | Height: | Size: 841 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_edit_grey600_24dp.png
Normal file
After Width: | Height: | Size: 493 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_fingerprint_grey600_24dp.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_folder_grey600_24dp.png
Normal file
After Width: | Height: | Size: 356 B |
After Width: | Height: | Size: 725 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_healing_grey600_24dp.png
Normal file
After Width: | Height: | Size: 943 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_help_grey600_24dp.png
Normal file
After Width: | Height: | Size: 1,018 B |
After Width: | Height: | Size: 449 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_save_white_24dp.png
Normal file
After Width: | Height: | Size: 489 B |