commit
c62d708088
47 changed files with 6070 additions and 0 deletions
36
.drone.yml
Normal file
36
.drone.yml
Normal file
|
@ -0,0 +1,36 @@
|
|||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: maven:3-jdk-11
|
||||
commands:
|
||||
- mvn -B -DskipTests clean package
|
||||
- mvn test -B
|
||||
volumes:
|
||||
- name: m2
|
||||
path: /root/.m2
|
||||
|
||||
- name: archive-artifact
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- mkdir -p /builds/libKonogonka
|
||||
- cp target/libKonogonka-*-jar-with-dependencies.jar /builds/libKonogonka/
|
||||
volumes:
|
||||
- name: builds
|
||||
path: /builds
|
||||
|
||||
- name: install-local-repo
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- mvn install:install-file -Dfile=./target/libKonogonka-*-jar-with-dependencies.jar -DgroupId=ru.redrise -DartifactId=libKonogonka -Dversion=`grep -m 1 '<version>' pom.xml| sed -e 's/\s*.\/\?version>//g'
|
||||
` -Dpackaging=jar -DgeneratePom=true;
|
||||
|
||||
volumes:
|
||||
- name: m2
|
||||
host:
|
||||
path: /home/docker/drone/files/m2
|
||||
- name: builds
|
||||
host:
|
||||
path: /home/www/builds
|
9
HOWTO.txt
Normal file
9
HOWTO.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
How to add library into the local maven repository:
|
||||
|
||||
mvn install:install-file \
|
||||
-Dfile=./libKonogonka/target/libKonogonka-0.1-jar-with-dependencies.jar \
|
||||
-DgroupId=ru.redrise \
|
||||
-DartifactId=libKonogonka \
|
||||
-Dversion=0.1 \
|
||||
-Dpackaging=jar \
|
||||
-DgeneratePom=true
|
674
LICENSE
Normal file
674
LICENSE
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:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
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>.
|
26
README.md
Normal file
26
README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# konogonka
|
||||
|
||||
[![Build Status](https://ci.redrise.ru/api/badges/desu/libKonogonka/status.svg)](https://ci.redrise.ru/desu/libKonogonka)
|
||||
|
||||
Library to work with NS-specific files / filesystem images. Dedicated back end part of [konogonka](https://github.com/developersu/konogonka) ([independent src location](https://git.redrise.ru/desu/konogonka))
|
||||
|
||||
### License
|
||||
|
||||
[GNU General Public License v3+](https://git.redrise.ru/desu/libKonogonka/LICENSE)
|
||||
|
||||
### Used libraries & resources
|
||||
* [Bouncy Castle](https://www.bouncycastle.org/) for Java.
|
||||
* [Java-XTS-AES](https://github.com/horrorho/Java-XTS-AES) by horrorho with minimal changes.
|
||||
|
||||
#### Thanks
|
||||
* Switch brew wiki
|
||||
* Original ScriesM software
|
||||
* roothorick, [shchmue](https://github.com/shchmue/), He, other Team AtlasNX discord members for their advices, notes and examples!
|
||||
|
||||
### System requirements
|
||||
|
||||
JRE/JDK 8u60 or higher.
|
||||
|
||||
### Build
|
||||
|
||||
See .drone.yml
|
140
pom.xml
Normal file
140
pom.xml
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ru.redrise</groupId>
|
||||
<artifactId>libKonogonka</artifactId>
|
||||
<version>0.1</version>
|
||||
|
||||
<url>https://git.redrise.ru/desu/${project.name}}/</url>
|
||||
<description>
|
||||
NS filesystem library
|
||||
</description>
|
||||
<inceptionYear>2022</inceptionYear>
|
||||
<organization>
|
||||
<name>Dmitry Isaenko</name>
|
||||
<url>https://developersu.blogspot.com/</url>
|
||||
</organization>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>GPLv3</name>
|
||||
<url>LICENSE</url>
|
||||
<distribution>manual</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>developer.su</id>
|
||||
<name>Dmitry Isaenko</name>
|
||||
<roles>
|
||||
<role>Developer</role>
|
||||
</roles>
|
||||
<timezone>+3</timezone>
|
||||
<url>https://developersu.blogspot.com/</url>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.build.timestamp.format>yyyyMMdd.HHmmss</maven.build.timestamp.format>
|
||||
</properties>
|
||||
|
||||
<issueManagement>
|
||||
<system>GitHub</system>
|
||||
<url>https://github.com/developer_su/${project.artifactId}/issues</url>
|
||||
</issueManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- For AES XTS we use bouncycastle -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.70</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.jcip</groupId>
|
||||
<artifactId>jcip-annotations</artifactId>
|
||||
<version>1.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<!-- <finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}</finalName> -->
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- UNCOMMENT WHEN LEARN HOW TO MAKE NORMAL DOCS
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
-->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.10.1</version>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Generate JAR with dependencies -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>nsusbloader.Main</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<!-- <appendAssemblyId>false</appendAssemblyId> -->
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id> <!-- this is used for inheritance merges -->
|
||||
<phase>package</phase> <!-- bind to the packaging phase -->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
64
src/main/java/libKonogonka/LoperConverter.java
Normal file
64
src/main/java/libKonogonka/LoperConverter.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class LoperConverter {
|
||||
public static int getLEint(byte[] bytes, int fromOffset){
|
||||
return ByteBuffer.wrap(bytes, fromOffset, 0x4).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||
}
|
||||
|
||||
public static long getLElong(byte[] bytes, int fromOffset){
|
||||
return ByteBuffer.wrap(bytes, fromOffset, 0x8).order(ByteOrder.LITTLE_ENDIAN).getLong();
|
||||
}
|
||||
/**
|
||||
* Convert int to long. Workaround to store unsigned int
|
||||
* @param bytes original array
|
||||
* @param fromOffset start position of the 4-bytes value
|
||||
* */
|
||||
public static long getLElongOfInt(byte[] bytes, int fromOffset){
|
||||
final byte[] holder = new byte[8];
|
||||
System.arraycopy(bytes, fromOffset, holder, 0, 4);
|
||||
return ByteBuffer.wrap(holder).order(ByteOrder.LITTLE_ENDIAN).getLong();
|
||||
}
|
||||
|
||||
public static String byteArrToHexString(byte[] bArr){
|
||||
if (bArr == null)
|
||||
return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b: bArr)
|
||||
sb.append(String.format("%02x", b));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String longToOctString(long value){
|
||||
return String.format("%64s", Long.toBinaryString( value )).replace(' ', '0');
|
||||
}
|
||||
|
||||
public static byte[] flip(byte[] bytes){
|
||||
int size = bytes.length;
|
||||
byte[] ret = new byte[size];
|
||||
for (int i = 0; i < size; i++){
|
||||
ret[size-i-1] = bytes[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
62
src/main/java/libKonogonka/RainbowDump.java
Normal file
62
src/main/java/libKonogonka/RainbowDump.java
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Debug tool like hexdump <3
|
||||
*/
|
||||
public class RainbowDump {
|
||||
private static final String ANSI_RESET = "\u001B[0m";
|
||||
private static final String ANSI_BLACK = "\u001B[30m";
|
||||
private static final String ANSI_RED = "\u001B[31m";
|
||||
private static final String ANSI_GREEN = "\u001B[32m";
|
||||
private static final String ANSI_YELLOW = "\u001B[33m";
|
||||
private static final String ANSI_BLUE = "\u001B[34m";
|
||||
private static final String ANSI_PURPLE = "\u001B[35m";
|
||||
private static final String ANSI_CYAN = "\u001B[36m";
|
||||
private static final String ANSI_WHITE = "\u001B[37m";
|
||||
|
||||
|
||||
public static void hexDumpUTF8(byte[] byteArray){
|
||||
if (byteArray == null || byteArray.length == 0)
|
||||
return;
|
||||
System.out.print(ANSI_BLUE);
|
||||
for (int i=0; i < byteArray.length; i++)
|
||||
System.out.print(String.format("%02d-", i%100));
|
||||
System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET);
|
||||
for (byte b: byteArray)
|
||||
System.out.print(String.format("%02x ", b));
|
||||
System.out.println();
|
||||
System.out.print(new String(byteArray, StandardCharsets.UTF_8)+"\n");
|
||||
}
|
||||
|
||||
public static void octDumpInt(int value){
|
||||
System.out.println(String.format("%32s", Integer.toBinaryString( value )).replace(' ', '0')+" | "+value);
|
||||
}
|
||||
|
||||
public static void octDumpLong(long value){
|
||||
System.out.println(String.format("%64s", Long.toBinaryString( value )).replace(' ', '0')+" | "+value);
|
||||
}
|
||||
|
||||
public static String formatDecHexString(long value){
|
||||
return String.format("%-20d 0x%x", value, value);
|
||||
}
|
||||
}
|
42
src/main/java/libKonogonka/Tools/ASuperInFileProvider.java
Normal file
42
src/main/java/libKonogonka/Tools/ASuperInFileProvider.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PipedInputStream;
|
||||
|
||||
/**
|
||||
* Any class of this type must be able to accept data from stream (and file as any other).
|
||||
* */
|
||||
|
||||
public abstract class ASuperInFileProvider {
|
||||
protected byte[] readFromStream(PipedInputStream pis, int size) throws IOException {
|
||||
byte[] buffer = new byte[size];
|
||||
int startingPos = 0;
|
||||
int readCnt;
|
||||
while (size > 0){
|
||||
readCnt = pis.read(buffer, startingPos, size);
|
||||
if (readCnt == -1)
|
||||
return null;
|
||||
startingPos += readCnt;
|
||||
size -= readCnt;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
32
src/main/java/libKonogonka/Tools/ISuperProvider.java
Normal file
32
src/main/java/libKonogonka/Tools/ISuperProvider.java
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PipedInputStream;
|
||||
/**
|
||||
* Any class of this type must provide streams
|
||||
* */
|
||||
public interface ISuperProvider {
|
||||
PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception;
|
||||
PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception;
|
||||
|
||||
File getFile();
|
||||
long getRawFileDataStart();
|
||||
}
|
443
src/main/java/libKonogonka/Tools/NCA/NCAContent.java
Normal file
443
src/main/java/libKonogonka/Tools/NCA/NCAContent.java
Normal file
|
@ -0,0 +1,443 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NCA;
|
||||
|
||||
import libKonogonka.LoperConverter;
|
||||
import libKonogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
||||
import libKonogonka.Tools.PFS0.IPFS0Provider;
|
||||
import libKonogonka.Tools.PFS0.PFS0EncryptedProvider;
|
||||
import libKonogonka.Tools.PFS0.PFS0Provider;
|
||||
import libKonogonka.Tools.RomFs.IRomFsProvider;
|
||||
import libKonogonka.Tools.RomFs.RomFsEncryptedProvider;
|
||||
import libKonogonka.ctraes.AesCtrDecryptSimple;
|
||||
import libKonogonka.exceptions.EmptySectionException;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.LinkedList;
|
||||
/**
|
||||
* THIS CLASS BECOMES MORE UGLY AFTER EACH ITERATION OF REFACTORING.
|
||||
* TODO: MAKE SOME DECOMPOSITION
|
||||
* */
|
||||
public class NCAContent {
|
||||
private File file;
|
||||
private long offsetPosition;
|
||||
private NCASectionBlock ncaSectionBlock;
|
||||
private NCAHeaderTableEntry ncaHeaderTableEntry;
|
||||
private byte[] decryptedKey;
|
||||
|
||||
private LinkedList<byte[]> Pfs0SHA256hashes;
|
||||
private IPFS0Provider pfs0;
|
||||
private IRomFsProvider romfs;
|
||||
|
||||
// TODO: if decryptedKey is empty, throw exception ??
|
||||
public NCAContent(File file,
|
||||
long offsetPosition,
|
||||
NCASectionBlock ncaSectionBlock,
|
||||
NCAHeaderTableEntry ncaHeaderTableEntry,
|
||||
byte[] decryptedKey) throws Exception
|
||||
{
|
||||
this.file = file;
|
||||
this.offsetPosition = offsetPosition;
|
||||
this.ncaSectionBlock = ncaSectionBlock;
|
||||
this.ncaHeaderTableEntry = ncaHeaderTableEntry;
|
||||
this.decryptedKey = decryptedKey;
|
||||
|
||||
Pfs0SHA256hashes = new LinkedList<>();
|
||||
// If nothing to do
|
||||
if (ncaHeaderTableEntry.getMediaEndOffset() == 0)
|
||||
throw new EmptySectionException("Empty section");
|
||||
// If it's PFS0Provider
|
||||
if (ncaSectionBlock.getSuperBlockPFS0() != null)
|
||||
this.proceedPFS0();
|
||||
else if (ncaSectionBlock.getSuperBlockIVFC() != null)
|
||||
this.proceedRomFs();
|
||||
else
|
||||
throw new Exception("NCAContent(): Not supported. PFS0 or RomFS supported only.");
|
||||
}
|
||||
|
||||
private void proceedPFS0() throws Exception {
|
||||
switch (ncaSectionBlock.getCryptoType()){
|
||||
case 0x01:
|
||||
proceedPFS0NotEncrypted(); // IF NO ENCRYPTION
|
||||
break;
|
||||
case 0x03:
|
||||
proceedPFS0Encrypted(); // If encrypted regular [ 0x03 ]
|
||||
break;
|
||||
default:
|
||||
throw new Exception("NCAContent() -> proceedPFS0(): Non-supported 'Crypto type'");
|
||||
}
|
||||
}
|
||||
private void proceedPFS0NotEncrypted() throws Exception{
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200);
|
||||
long hashTableLocation = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset();
|
||||
long pfs0Location = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset();
|
||||
|
||||
raf.seek(hashTableLocation);
|
||||
|
||||
byte[] rawData;
|
||||
long sha256recordsNumber = ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20;
|
||||
// Collect hashes
|
||||
for (int i = 0; i < sha256recordsNumber; i++){
|
||||
rawData = new byte[0x20]; // 32 bytes - size of SHA256 hash
|
||||
if (raf.read(rawData) != -1)
|
||||
Pfs0SHA256hashes.add(rawData);
|
||||
else {
|
||||
raf.close();
|
||||
return; // TODO: fix
|
||||
}
|
||||
}
|
||||
raf.close();
|
||||
// Get pfs0
|
||||
pfs0 = new PFS0Provider(file, pfs0Location);
|
||||
}
|
||||
private void proceedPFS0Encrypted() throws Exception{
|
||||
new CryptoSection03Pfs0(file,
|
||||
offsetPosition,
|
||||
decryptedKey,
|
||||
ncaSectionBlock,
|
||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||
ncaHeaderTableEntry.getMediaEndOffset());
|
||||
}
|
||||
|
||||
private void proceedRomFs() throws Exception{
|
||||
switch (ncaSectionBlock.getCryptoType()){
|
||||
case 0x01:
|
||||
proceedRomFsNotEncrypted(); // IF NO ENCRYPTION
|
||||
break;
|
||||
case 0x03:
|
||||
proceedRomFsEncrypted(); // If encrypted regular [ 0x03 ]
|
||||
break;
|
||||
default:
|
||||
throw new Exception("NCAContent() -> proceedRomFs(): Non-supported 'Crypto type'");
|
||||
}
|
||||
}
|
||||
private void proceedRomFsNotEncrypted(){
|
||||
// TODO: Clarify, implement if needed
|
||||
System.out.println("proceedRomFs() -> proceedRomFsNotEncrypted() is not implemented :(");
|
||||
}
|
||||
private void proceedRomFsEncrypted() throws Exception{
|
||||
if (decryptedKey == null)
|
||||
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
|
||||
|
||||
this.romfs = new RomFsEncryptedProvider(
|
||||
offsetPosition,
|
||||
ncaSectionBlock.getSuperBlockIVFC().getLvl6Offset(),
|
||||
file,
|
||||
decryptedKey,
|
||||
ncaSectionBlock.getSectionCTR(),
|
||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||
ncaHeaderTableEntry.getMediaEndOffset());
|
||||
}
|
||||
|
||||
public LinkedList<byte[]> getPfs0SHA256hashes() { return Pfs0SHA256hashes; }
|
||||
public IPFS0Provider getPfs0() { return pfs0; }
|
||||
public IRomFsProvider getRomfs() { return romfs; }
|
||||
|
||||
private class CryptoSection03Pfs0 {
|
||||
|
||||
CryptoSection03Pfs0(File file,
|
||||
long offsetPosition,
|
||||
byte[] decryptedKey,
|
||||
NCASectionBlock ncaSectionBlock,
|
||||
long mediaStartBlocksOffset,
|
||||
long mediaEndBlocksOffset) throws Exception
|
||||
{
|
||||
/*//--------------------------------------------------------------------------------------------------
|
||||
System.out.println("Media start location: " + mediaStartBlocksOffset);
|
||||
System.out.println("Media end location: " + mediaEndBlocksOffset);
|
||||
System.out.println("Media size : " + (mediaEndBlocksOffset-mediaStartBlocksOffset));
|
||||
System.out.println("Media act. location: " + (offsetPosition + (mediaStartBlocksOffset * 0x200)));
|
||||
System.out.println("SHA256 hash tbl size: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableSize());
|
||||
System.out.println("SHA256 hash tbl offs: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset());
|
||||
System.out.println("PFS0 Offs: " + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset());
|
||||
System.out.println("SHA256 records: " + (ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20));
|
||||
System.out.println("KEY: " + LoperConverter.byteArrToHexString(decryptedKey));
|
||||
System.out.println("CTR: " + LoperConverter.byteArrToHexString(ncaSectionBlock.getSectionCTR()));
|
||||
System.out.println();
|
||||
//--------------------------------------------------------------------------------------------------*/
|
||||
if (decryptedKey == null)
|
||||
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
long abosluteOffsetPosition = offsetPosition + (mediaStartBlocksOffset * 0x200);
|
||||
raf.seek(abosluteOffsetPosition);
|
||||
|
||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartBlocksOffset * 0x200);
|
||||
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
long mediaBlocksSize = mediaEndBlocksOffset - mediaStartBlocksOffset;
|
||||
// Prepare thread to parse encrypted data
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
PipedInputStream streamInp = new PipedInputStream(streamOut);
|
||||
|
||||
Thread pThread = new Thread(new ParseThread(
|
||||
streamInp,
|
||||
ncaSectionBlock.getSuperBlockPFS0().getPfs0offset(),
|
||||
ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(),
|
||||
ncaSectionBlock.getSuperBlockPFS0().getHashTableSize(),
|
||||
offsetPosition,
|
||||
file,
|
||||
decryptedKey,
|
||||
ncaSectionBlock.getSectionCTR(),
|
||||
mediaStartBlocksOffset,
|
||||
mediaEndBlocksOffset
|
||||
));
|
||||
pThread.start();
|
||||
// Decrypt data
|
||||
for (int i = 0; i < mediaBlocksSize; i++){
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) != -1){
|
||||
//dectyptedBlock = aesCtr.decrypt(encryptedBlock);
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
// Writing decrypted data to pipe
|
||||
try {
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
catch (IOException e){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pThread.join();
|
||||
streamOut.close();
|
||||
raf.close();
|
||||
}
|
||||
/*
|
||||
* Since we representing decrypted data as stream (it's easier to look on it this way),
|
||||
* this thread will be parsing it.
|
||||
* */
|
||||
private class ParseThread implements Runnable{
|
||||
|
||||
PipedInputStream pipedInputStream;
|
||||
|
||||
long hashTableOffset;
|
||||
long hashTableSize;
|
||||
long hashTableRecordsCount;
|
||||
long pfs0offset;
|
||||
|
||||
private long MetaOffsetPositionInFile;
|
||||
private File MetaFileWithEncPFS0;
|
||||
private byte[] MetaKey;
|
||||
private byte[] MetaSectionCTR;
|
||||
private long MetaMediaStartOffset;
|
||||
private long MetaMediaEndOffset;
|
||||
|
||||
|
||||
ParseThread(PipedInputStream pipedInputStream,
|
||||
long pfs0offset,
|
||||
long hashTableOffset,
|
||||
long hashTableSize,
|
||||
|
||||
long MetaOffsetPositionInFile,
|
||||
File MetaFileWithEncPFS0,
|
||||
byte[] MetaKey,
|
||||
byte[] MetaSectionCTR,
|
||||
long MetaMediaStartOffset,
|
||||
long MetaMediaEndOffset
|
||||
){
|
||||
this.pipedInputStream = pipedInputStream;
|
||||
this.hashTableOffset = hashTableOffset;
|
||||
this.hashTableSize = hashTableSize;
|
||||
this.hashTableRecordsCount = hashTableSize / 0x20;
|
||||
this.pfs0offset = pfs0offset;
|
||||
|
||||
this.MetaOffsetPositionInFile = MetaOffsetPositionInFile;
|
||||
this.MetaFileWithEncPFS0 = MetaFileWithEncPFS0;
|
||||
this.MetaKey = MetaKey;
|
||||
this.MetaSectionCTR = MetaSectionCTR;
|
||||
this.MetaMediaStartOffset = MetaMediaStartOffset;
|
||||
this.MetaMediaEndOffset = MetaMediaEndOffset;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long counter = 0; // How many bytes already read
|
||||
|
||||
try{
|
||||
if (hashTableOffset > 0){
|
||||
if (hashTableOffset != pipedInputStream.skip(hashTableOffset))
|
||||
return; // TODO: fix?
|
||||
counter = hashTableOffset;
|
||||
}
|
||||
// Loop for collecting all recrods from sha256 hash table
|
||||
while ((counter - hashTableOffset) < hashTableSize){
|
||||
int hashCounter = 0;
|
||||
byte[] sectionHash = new byte[0x20];
|
||||
// Loop for collecting bytes for every SINGLE records, where record size == 0x20
|
||||
while (hashCounter < 0x20){
|
||||
int currentByte = pipedInputStream.read();
|
||||
if (currentByte == -1)
|
||||
break;
|
||||
sectionHash[hashCounter] = (byte)currentByte;
|
||||
hashCounter++;
|
||||
counter++;
|
||||
}
|
||||
// Write after collecting
|
||||
Pfs0SHA256hashes.add(sectionHash); // From the NCAContentProvider obviously
|
||||
}
|
||||
// Skip padding and go to PFS0 location
|
||||
if (counter < pfs0offset){
|
||||
long toSkip = pfs0offset-counter;
|
||||
if (toSkip != pipedInputStream.skip(toSkip))
|
||||
return; // TODO: fix?
|
||||
counter += toSkip;
|
||||
}
|
||||
//---------------------------------------------------------
|
||||
pfs0 = new PFS0EncryptedProvider(pipedInputStream,
|
||||
counter,
|
||||
MetaOffsetPositionInFile,
|
||||
MetaFileWithEncPFS0,
|
||||
MetaKey,
|
||||
MetaSectionCTR,
|
||||
MetaMediaStartOffset,
|
||||
MetaMediaEndOffset);
|
||||
pipedInputStream.close();
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("'ParseThread' thread exception");
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
System.out.println("Thread dies");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export NCA content AS IS.
|
||||
* Not so good for PFS0 since there are SHAs list that discourages but good for 'romfs' and things like that
|
||||
* */
|
||||
public PipedInputStream getRawDataContentPipedInpStream() throws Exception {
|
||||
long mediaStartBlocksOffset = ncaHeaderTableEntry.getMediaStartOffset();
|
||||
long mediaEndBlocksOffset = ncaHeaderTableEntry.getMediaEndOffset();
|
||||
long mediaBlocksSize = mediaEndBlocksOffset - mediaStartBlocksOffset;
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
///--------------------------------------------------------------------------------------------------
|
||||
System.out.println("NCAContent() -> exportEncryptedSectionType03() Debug information");
|
||||
System.out.println("Media start location: " + mediaStartBlocksOffset);
|
||||
System.out.println("Media end location: " + mediaEndBlocksOffset);
|
||||
System.out.println("Media size : " + (mediaEndBlocksOffset-mediaStartBlocksOffset));
|
||||
System.out.println("Media act. location: " + (offsetPosition + (mediaStartBlocksOffset * 0x200)));
|
||||
System.out.println("KEY: " + LoperConverter.byteArrToHexString(decryptedKey));
|
||||
System.out.println("CTR: " + LoperConverter.byteArrToHexString(ncaSectionBlock.getSectionCTR()));
|
||||
System.out.println();
|
||||
//---------------------------------------------------------------------------------------------------/
|
||||
|
||||
if (ncaSectionBlock.getCryptoType() == 0x01){
|
||||
System.out.println("NCAContent -> getRawDataContentPipedInpStream (Zero encryption section type 01): Thread started");
|
||||
|
||||
Thread workerThread;
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
workerThread = new Thread(() -> {
|
||||
try {
|
||||
byte[] rawDataBlock;
|
||||
for (int i = 0; i < mediaBlocksSize; i++){
|
||||
rawDataBlock = new byte[0x200];
|
||||
if (raf.read(rawDataBlock) != -1)
|
||||
streamOut.write(rawDataBlock);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("NCAContent -> exportRawData(): "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
raf.close();
|
||||
}catch (Exception ignored) {}
|
||||
try {
|
||||
streamOut.close();
|
||||
}catch (Exception ignored) {}
|
||||
}
|
||||
System.out.println("NCAContent -> exportRawData(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
else if (ncaSectionBlock.getCryptoType() == 0x03){
|
||||
System.out.println("NCAContent -> getRawDataContentPipedInpStream (Encrypted Section Type 03): Thread started");
|
||||
|
||||
if (decryptedKey == null)
|
||||
throw new Exception("NCAContent -> exportRawData(): unable to proceed. No decrypted key provided.");
|
||||
|
||||
Thread workerThread;
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
workerThread = new Thread(() -> {
|
||||
try {
|
||||
//RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
long abosluteOffsetPosition = offsetPosition + (mediaStartBlocksOffset * 0x200);
|
||||
raf.seek(abosluteOffsetPosition);
|
||||
|
||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey,
|
||||
ncaSectionBlock.getSectionCTR(),
|
||||
mediaStartBlocksOffset * 0x200);
|
||||
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
|
||||
// Decrypt data
|
||||
for (int i = 0; i < mediaBlocksSize; i++){
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) != -1){
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
// Writing decrypted data to pipe
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("NCAContent -> exportRawData(): "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
raf.close();
|
||||
}catch (Exception ignored) {}
|
||||
try {
|
||||
streamOut.close();
|
||||
}catch (Exception ignored) {}
|
||||
}
|
||||
System.out.println("NCAContent -> exportRawData(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
public long getRawDataContentSize(){
|
||||
return (ncaHeaderTableEntry.getMediaEndOffset() - ncaHeaderTableEntry.getMediaStartOffset()) * 0x200;
|
||||
}
|
||||
public String getFileName(){
|
||||
return file.getName();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NCA;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class NCAHeaderTableEntry {
|
||||
|
||||
private long mediaStartOffset;
|
||||
private long mediaEndOffset;
|
||||
private byte[] unknwn1;
|
||||
private byte[] unknwn2;
|
||||
|
||||
public NCAHeaderTableEntry(byte[] table) throws Exception{
|
||||
if (table.length < 0x10)
|
||||
throw new Exception("Section Table size is too small.");
|
||||
|
||||
this.mediaStartOffset = convertUnsignedIntBytesToLong(Arrays.copyOfRange(table, 0x0, 0x4));
|
||||
this.mediaEndOffset = convertUnsignedIntBytesToLong(Arrays.copyOfRange(table, 0x4, 0x8));
|
||||
this.unknwn1 = Arrays.copyOfRange(table, 0x8, 0xC);
|
||||
this.unknwn2 = Arrays.copyOfRange(table, 0xC, 0x10);
|
||||
}
|
||||
|
||||
private long convertUnsignedIntBytesToLong(byte[] intBytes){
|
||||
if (intBytes.length == 4)
|
||||
return ByteBuffer.wrap(Arrays.copyOf(intBytes, 8)).order(ByteOrder.LITTLE_ENDIAN).getLong();
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
public long getMediaStartOffset() { return mediaStartOffset; }
|
||||
public long getMediaEndOffset() { return mediaEndOffset; }
|
||||
public byte[] getUnknwn1() { return unknwn1; }
|
||||
public byte[] getUnknwn2() { return unknwn2; }
|
||||
}
|
385
src/main/java/libKonogonka/Tools/NCA/NCAProvider.java
Normal file
385
src/main/java/libKonogonka/Tools/NCA/NCAProvider.java
Normal file
|
@ -0,0 +1,385 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NCA;
|
||||
|
||||
import libKonogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
||||
import libKonogonka.exceptions.EmptySectionException;
|
||||
import libKonogonka.xtsaes.XTSAESCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.File;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||
import static libKonogonka.LoperConverter.getLElong;
|
||||
|
||||
// TODO: check file size
|
||||
public class NCAProvider {
|
||||
private File file; // File that contains NCA
|
||||
private long offset; // Offset where NCA actually located
|
||||
private HashMap<String, String> keys; // hashmap with keys using _0x naming (where x number 0-N)
|
||||
// Header
|
||||
private byte[] rsa2048one;
|
||||
private byte[] rsa2048two;
|
||||
private String magicnum;
|
||||
private byte systemOrGcIndicator;
|
||||
private byte contentType;
|
||||
private byte cryptoType1; // keyblob index. Considering as number within application/ocean/system
|
||||
private byte keyIndex; // application/ocean/system (kaek index?)
|
||||
private long ncaSize; // Size of this NCA (bytes)
|
||||
private byte[] titleId;
|
||||
private byte[] contentIndx;
|
||||
private byte[] sdkVersion; // version ver_revision.ver_micro.vev_minor.ver_major
|
||||
private byte cryptoType2; // keyblob index. Considering as number within application/ocean/system | AKA KeyGeneration
|
||||
private byte Header1SignatureKeyGeneration;
|
||||
private byte[] keyGenerationReserved;
|
||||
private byte[] rightsId;
|
||||
|
||||
private byte cryptoTypeReal;
|
||||
|
||||
private byte[] sha256hash0;
|
||||
private byte[] sha256hash1;
|
||||
private byte[] sha256hash2;
|
||||
private byte[] sha256hash3;
|
||||
|
||||
private byte[] encryptedKey0;
|
||||
private byte[] encryptedKey1;
|
||||
private byte[] encryptedKey2;
|
||||
private byte[] encryptedKey3;
|
||||
|
||||
private byte[] decryptedKey0;
|
||||
private byte[] decryptedKey1;
|
||||
private byte[] decryptedKey2;
|
||||
private byte[] decryptedKey3;
|
||||
|
||||
private NCAHeaderTableEntry tableEntry0;
|
||||
private NCAHeaderTableEntry tableEntry1;
|
||||
private NCAHeaderTableEntry tableEntry2;
|
||||
private NCAHeaderTableEntry tableEntry3;
|
||||
|
||||
private NCASectionBlock sectionBlock0;
|
||||
private NCASectionBlock sectionBlock1;
|
||||
private NCASectionBlock sectionBlock2;
|
||||
private NCASectionBlock sectionBlock3;
|
||||
|
||||
private NCAContent ncaContent0;
|
||||
private NCAContent ncaContent1;
|
||||
private NCAContent ncaContent2;
|
||||
private NCAContent ncaContent3;
|
||||
|
||||
public NCAProvider(File file, HashMap<String, String> keys) throws Exception{
|
||||
this(file, keys, 0);
|
||||
}
|
||||
|
||||
public NCAProvider (File file, HashMap<String, String> keys, long offsetPosition) throws Exception{
|
||||
this.keys = keys;
|
||||
String header_key = keys.get("header_key");
|
||||
if (header_key == null )
|
||||
throw new Exception("header_key is not found within key set provided.");
|
||||
if (header_key.length() != 64)
|
||||
throw new Exception("header_key is too small or too big. Must be 64 symbols.");
|
||||
|
||||
this.file = file;
|
||||
this.offset = offsetPosition;
|
||||
|
||||
KeyParameter key1 = new KeyParameter(
|
||||
hexStrToByteArray(header_key.substring(0, 32))
|
||||
);
|
||||
KeyParameter key2 = new KeyParameter(
|
||||
hexStrToByteArray(header_key.substring(32, 64))
|
||||
);
|
||||
|
||||
XTSAESCipher xtsaesCipher = new XTSAESCipher(false);
|
||||
xtsaesCipher.init(false, key1, key2);
|
||||
//-------------------------------------------------------------------------------------------------------------------------
|
||||
byte[] decryptedHeader = new byte[0xC00];
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
byte[] encryptedSequence = new byte[0x200];
|
||||
byte[] decryptedSequence;
|
||||
|
||||
raf.seek(offsetPosition);
|
||||
|
||||
for (int i = 0; i < 6; i++){
|
||||
if (raf.read(encryptedSequence) != 0x200)
|
||||
throw new Exception("Read error "+i);
|
||||
decryptedSequence = new byte[0x200];
|
||||
xtsaesCipher.processDataUnit(encryptedSequence, 0, 0x200, decryptedSequence, 0, i);
|
||||
System.arraycopy(decryptedSequence, 0, decryptedHeader, i * 0x200, 0x200);
|
||||
}
|
||||
|
||||
getHeader(decryptedHeader);
|
||||
|
||||
raf.close();
|
||||
|
||||
getNCAContent();
|
||||
/*
|
||||
//---------------------------------------------------------------------
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("/tmp/decrypted.nca"));
|
||||
int i = 0;
|
||||
byte[] block = new byte[0x200];
|
||||
while (fis.read(block) != -1){
|
||||
decryptedSequence = new byte[0x200];
|
||||
xtsaesCipher.processDataUnit(block, 0, 0x200, decryptedSequence, 0, i++);
|
||||
bos.write(decryptedSequence);
|
||||
}
|
||||
bos.close();
|
||||
//---------------------------------------------------------------------*/
|
||||
}
|
||||
|
||||
private byte[] hexStrToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
private void getHeader(byte[] decryptedData) throws Exception{
|
||||
rsa2048one = Arrays.copyOfRange(decryptedData, 0, 0x100);
|
||||
rsa2048two = Arrays.copyOfRange(decryptedData, 0x100, 0x200);
|
||||
magicnum = new String(decryptedData, 0x200, 0x4, StandardCharsets.US_ASCII);
|
||||
systemOrGcIndicator = decryptedData[0x204];
|
||||
contentType = decryptedData[0x205];
|
||||
cryptoType1 = decryptedData[0x206];
|
||||
keyIndex = decryptedData[0x207];
|
||||
ncaSize = getLElong(decryptedData, 0x208);
|
||||
titleId = Arrays.copyOfRange(decryptedData, 0x210, 0x218);
|
||||
contentIndx = Arrays.copyOfRange(decryptedData, 0x218, 0x21C);
|
||||
sdkVersion = Arrays.copyOfRange(decryptedData, 0x21c, 0x220);
|
||||
cryptoType2 = decryptedData[0x220];
|
||||
Header1SignatureKeyGeneration = decryptedData[0x221];
|
||||
keyGenerationReserved = Arrays.copyOfRange(decryptedData, 0x222, 0x230);
|
||||
rightsId = Arrays.copyOfRange(decryptedData, 0x230, 0x240);
|
||||
byte[] tableBytes = Arrays.copyOfRange(decryptedData, 0x240, 0x280);
|
||||
byte[] sha256tableBytes = Arrays.copyOfRange(decryptedData, 0x280, 0x300);
|
||||
sha256hash0 = Arrays.copyOfRange(sha256tableBytes, 0, 0x20);
|
||||
sha256hash1 = Arrays.copyOfRange(sha256tableBytes, 0x20, 0x40);
|
||||
sha256hash2 = Arrays.copyOfRange(sha256tableBytes, 0x40, 0x60);
|
||||
sha256hash3 = Arrays.copyOfRange(sha256tableBytes, 0x60, 0x80);
|
||||
byte [] encryptedKeysArea = Arrays.copyOfRange(decryptedData, 0x300, 0x340);
|
||||
|
||||
encryptedKey0 = Arrays.copyOfRange(encryptedKeysArea, 0, 0x10);
|
||||
encryptedKey1 = Arrays.copyOfRange(encryptedKeysArea, 0x10, 0x20);
|
||||
encryptedKey2 = Arrays.copyOfRange(encryptedKeysArea, 0x20, 0x30);
|
||||
encryptedKey3 = Arrays.copyOfRange(encryptedKeysArea, 0x30, 0x40);
|
||||
|
||||
// Calculate real Crypto Type
|
||||
if (cryptoType1 < cryptoType2)
|
||||
cryptoTypeReal = cryptoType2;
|
||||
else
|
||||
cryptoTypeReal = cryptoType1;
|
||||
|
||||
if (cryptoTypeReal > 0) // TODO: CLARIFY WHY THE FUCK IS IT FAIR????
|
||||
cryptoTypeReal -= 1;
|
||||
|
||||
//todo: if nca3 proceed
|
||||
// Decrypt keys if encrypted
|
||||
if (Arrays.equals(rightsId, new byte[0x10])) {
|
||||
String keyAreaKey;
|
||||
switch (keyIndex){
|
||||
case 0:
|
||||
keyAreaKey = keys.get(String.format("key_area_key_application_%02x", cryptoTypeReal));
|
||||
break;
|
||||
case 1:
|
||||
keyAreaKey = keys.get(String.format("key_area_key_ocean_%02x", cryptoTypeReal));
|
||||
break;
|
||||
case 2:
|
||||
keyAreaKey = keys.get(String.format("key_area_key_system_%02x", cryptoTypeReal));
|
||||
break;
|
||||
default:
|
||||
keyAreaKey = null;
|
||||
}
|
||||
|
||||
if (keyAreaKey != null){
|
||||
SecretKeySpec skSpec = new SecretKeySpec(hexStrToByteArray(keyAreaKey), "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skSpec);
|
||||
decryptedKey0 = cipher.doFinal(encryptedKey0);
|
||||
decryptedKey1 = cipher.doFinal(encryptedKey1);
|
||||
decryptedKey2 = cipher.doFinal(encryptedKey2);
|
||||
decryptedKey3 = cipher.doFinal(encryptedKey3);
|
||||
}
|
||||
else
|
||||
keyAreaKeyNotSupportedOrFound();
|
||||
}
|
||||
|
||||
tableEntry0 = new NCAHeaderTableEntry(tableBytes);
|
||||
tableEntry1 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x10, 0x20));
|
||||
tableEntry2 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x20, 0x30));
|
||||
tableEntry3 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x30, 0x40));
|
||||
|
||||
sectionBlock0 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x400, 0x600));
|
||||
sectionBlock1 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x600, 0x800));
|
||||
sectionBlock2 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x800, 0xa00));
|
||||
sectionBlock3 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0xa00, 0xc00));
|
||||
}
|
||||
|
||||
private void keyAreaKeyNotSupportedOrFound() throws Exception{
|
||||
StringBuilder exceptionStringBuilder = new StringBuilder("key_area_key_");
|
||||
switch (keyIndex){
|
||||
case 0:
|
||||
exceptionStringBuilder.append("application_");
|
||||
break;
|
||||
case 1:
|
||||
exceptionStringBuilder.append("ocean_");
|
||||
break;
|
||||
case 2:
|
||||
exceptionStringBuilder.append("system_");
|
||||
break;
|
||||
default:
|
||||
exceptionStringBuilder.append(keyIndex);
|
||||
exceptionStringBuilder.append("[UNKNOWN]_");
|
||||
}
|
||||
exceptionStringBuilder.append(String.format("%02x", cryptoTypeReal));
|
||||
exceptionStringBuilder.append(" requested. Not supported or not found.");
|
||||
throw new Exception(exceptionStringBuilder.toString());
|
||||
}
|
||||
|
||||
private void getNCAContent(){
|
||||
byte[] key;
|
||||
|
||||
// If empty Rights ID
|
||||
if (Arrays.equals(rightsId, new byte[0x10])) {
|
||||
key = decryptedKey2; // TODO: Just remember this dumb hack
|
||||
}
|
||||
else {
|
||||
try {
|
||||
byte[] rightsIDkey = hexStrToByteArray(keys.get(byteArrToHexString(rightsId))); // throws NullPointerException
|
||||
|
||||
SecretKeySpec skSpec = new SecretKeySpec(
|
||||
hexStrToByteArray(keys.get(String.format("titlekek_%02x", cryptoTypeReal))
|
||||
), "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skSpec);
|
||||
key = cipher.doFinal(rightsIDkey);
|
||||
}
|
||||
catch (Exception e){
|
||||
e.printStackTrace();
|
||||
System.out.println("No title.keys loaded?");
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.ncaContent0 = new NCAContent(file, offset, sectionBlock0, tableEntry0, key);
|
||||
}
|
||||
catch (EmptySectionException ignored){}
|
||||
catch (Exception e){
|
||||
this.ncaContent0 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
try{
|
||||
this.ncaContent1 = new NCAContent(file, offset, sectionBlock1, tableEntry1, key);
|
||||
}
|
||||
catch (EmptySectionException ignored){}
|
||||
catch (Exception e){
|
||||
this.ncaContent1 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
try{
|
||||
this.ncaContent2 = new NCAContent(file, offset, sectionBlock2, tableEntry2, key);
|
||||
}
|
||||
catch (EmptySectionException ignored){}
|
||||
catch (Exception e){
|
||||
this.ncaContent2 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
try{
|
||||
this.ncaContent3 = new NCAContent(file, offset, sectionBlock3, tableEntry3, key);
|
||||
}
|
||||
catch (EmptySectionException ignored){}
|
||||
catch (Exception e){
|
||||
this.ncaContent3 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getRsa2048one() { return rsa2048one; }
|
||||
public byte[] getRsa2048two() { return rsa2048two; }
|
||||
public String getMagicnum() { return magicnum; }
|
||||
public byte getSystemOrGcIndicator() { return systemOrGcIndicator; }
|
||||
public byte getContentType() { return contentType; }
|
||||
public byte getCryptoType1() { return cryptoType1; }
|
||||
public byte getKeyIndex() { return keyIndex; }
|
||||
public long getNcaSize() { return ncaSize; }
|
||||
public byte[] getTitleId() { return titleId; }
|
||||
public byte[] getContentIndx() { return contentIndx; }
|
||||
public byte[] getSdkVersion() { return sdkVersion; }
|
||||
public byte getCryptoType2() { return cryptoType2; }
|
||||
public byte getHeader1SignatureKeyGeneration() { return Header1SignatureKeyGeneration; }
|
||||
public byte[] getKeyGenerationReserved() { return keyGenerationReserved; }
|
||||
public byte[] getRightsId() { return rightsId; }
|
||||
|
||||
public byte[] getSha256hash0() { return sha256hash0; }
|
||||
public byte[] getSha256hash1() { return sha256hash1; }
|
||||
public byte[] getSha256hash2() { return sha256hash2; }
|
||||
public byte[] getSha256hash3() { return sha256hash3; }
|
||||
|
||||
public byte[] getEncryptedKey0() { return encryptedKey0; }
|
||||
public byte[] getEncryptedKey1() { return encryptedKey1; }
|
||||
public byte[] getEncryptedKey2() { return encryptedKey2; }
|
||||
public byte[] getEncryptedKey3() { return encryptedKey3; }
|
||||
|
||||
public byte[] getDecryptedKey0() { return decryptedKey0; }
|
||||
public byte[] getDecryptedKey1() { return decryptedKey1; }
|
||||
public byte[] getDecryptedKey2() { return decryptedKey2; }
|
||||
public byte[] getDecryptedKey3() { return decryptedKey3; }
|
||||
|
||||
public NCAHeaderTableEntry getTableEntry0() { return tableEntry0; }
|
||||
public NCAHeaderTableEntry getTableEntry1() { return tableEntry1; }
|
||||
public NCAHeaderTableEntry getTableEntry2() { return tableEntry2; }
|
||||
public NCAHeaderTableEntry getTableEntry3() { return tableEntry3; }
|
||||
|
||||
public NCASectionBlock getSectionBlock0() { return sectionBlock0; }
|
||||
public NCASectionBlock getSectionBlock1() { return sectionBlock1; }
|
||||
public NCASectionBlock getSectionBlock2() { return sectionBlock2; }
|
||||
public NCASectionBlock getSectionBlock3() { return sectionBlock3; }
|
||||
|
||||
public boolean isKeyAvailable(){ // TODO: USE
|
||||
if (Arrays.equals(rightsId, new byte[0x10]))
|
||||
return true;
|
||||
else
|
||||
return keys.containsKey(byteArrToHexString(rightsId));
|
||||
}
|
||||
/**
|
||||
* Get content for the selected section
|
||||
* @param sectionNumber should be 0-3
|
||||
* */
|
||||
public NCAContent getNCAContentProvider(int sectionNumber){
|
||||
switch (sectionNumber) {
|
||||
case 0:
|
||||
return ncaContent0;
|
||||
case 1:
|
||||
return ncaContent1;
|
||||
case 2:
|
||||
return ncaContent2;
|
||||
case 3:
|
||||
return ncaContent3;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NCA.NCASectionTableBlock;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static libKonogonka.LoperConverter.getLEint;
|
||||
import static libKonogonka.LoperConverter.getLElong;
|
||||
|
||||
public class NCASectionBlock {
|
||||
private byte[] version;
|
||||
private byte fsType;
|
||||
private byte hashType;
|
||||
private byte cryptoType;
|
||||
private byte[] padding;
|
||||
private SuperBlockIVFC superBlockIVFC;
|
||||
private SuperBlockPFS0 superBlockPFS0;
|
||||
private byte[] BKTRfullHeader;
|
||||
// BKTR extended
|
||||
private long BKTRoffsetSection1;
|
||||
private long BKTRsizeSection1;
|
||||
private String BKTRmagicSection1;
|
||||
private int BKTRu32Section1;
|
||||
private int BKTRs32Section1;
|
||||
private byte[] BKTRunknownSection1;
|
||||
|
||||
private long BKTRoffsetSection2;
|
||||
private long BKTRsizeSection2;
|
||||
private String BKTRmagicSection2;
|
||||
private int BKTRu32Section2;
|
||||
private int BKTRs32Section2;
|
||||
private byte[] BKTRunknownSection2;
|
||||
|
||||
private byte[] sectionCTR;
|
||||
private byte[] unknownEndPadding;
|
||||
|
||||
public NCASectionBlock(byte[] tableBlockBytes) throws Exception{
|
||||
if (tableBlockBytes.length != 0x200)
|
||||
throw new Exception("Table Block Section size is incorrect.");
|
||||
version = Arrays.copyOfRange(tableBlockBytes, 0, 0x2);
|
||||
fsType = tableBlockBytes[0x2];
|
||||
hashType = tableBlockBytes[0x3];
|
||||
cryptoType = tableBlockBytes[0x4];
|
||||
padding = Arrays.copyOfRange(tableBlockBytes, 0x5, 0x8);
|
||||
byte[] superBlockBytes = Arrays.copyOfRange(tableBlockBytes, 0x8, 0xf8);
|
||||
|
||||
if ((fsType == 0) && (hashType == 0x3))
|
||||
superBlockIVFC = new SuperBlockIVFC(superBlockBytes);
|
||||
else if ((fsType == 0x1) && (hashType == 0x2))
|
||||
superBlockPFS0 = new SuperBlockPFS0(superBlockBytes);
|
||||
|
||||
BKTRfullHeader = Arrays.copyOfRange(tableBlockBytes, 0x100, 0x140);
|
||||
|
||||
BKTRoffsetSection1 = getLElong(BKTRfullHeader, 0);
|
||||
BKTRsizeSection1 = getLElong(BKTRfullHeader, 0x8);
|
||||
BKTRmagicSection1 = new String(Arrays.copyOfRange(BKTRfullHeader, 0x10, 0x14), StandardCharsets.US_ASCII);
|
||||
BKTRu32Section1 = getLEint(BKTRfullHeader, 0x14);
|
||||
BKTRs32Section1 = getLEint(BKTRfullHeader, 0x18);
|
||||
BKTRunknownSection1 = Arrays.copyOfRange(tableBlockBytes, 0x1c, 0x20);
|
||||
|
||||
BKTRoffsetSection2 = getLElong(BKTRfullHeader, 0x20);
|
||||
BKTRsizeSection2 = getLElong(BKTRfullHeader, 0x28);
|
||||
BKTRmagicSection2 = new String(Arrays.copyOfRange(BKTRfullHeader, 0x30, 0x34), StandardCharsets.US_ASCII);
|
||||
BKTRu32Section2 = getLEint(BKTRfullHeader, 0x34);
|
||||
BKTRs32Section2 = getLEint(BKTRfullHeader, 0x38);
|
||||
BKTRunknownSection2 = Arrays.copyOfRange(BKTRfullHeader, 0x3c, 0x40);
|
||||
|
||||
sectionCTR = Arrays.copyOfRange(tableBlockBytes, 0x140, 0x148);
|
||||
unknownEndPadding = Arrays.copyOfRange(tableBlockBytes, 0x148, 0x200);
|
||||
}
|
||||
|
||||
public byte[] getVersion() { return version; }
|
||||
public byte getFsType() { return fsType; }
|
||||
public byte getHashType() { return hashType; }
|
||||
public byte getCryptoType() { return cryptoType; }
|
||||
public byte[] getPadding() { return padding; }
|
||||
public SuperBlockIVFC getSuperBlockIVFC() { return superBlockIVFC; }
|
||||
public SuperBlockPFS0 getSuperBlockPFS0() { return superBlockPFS0; }
|
||||
public byte[] getBKTRfullHeader() { return BKTRfullHeader; }
|
||||
|
||||
public long getBKTRoffsetSection1() { return BKTRoffsetSection1; }
|
||||
public long getBKTRsizeSection1() { return BKTRsizeSection1; }
|
||||
public String getBKTRmagicSection1() { return BKTRmagicSection1; }
|
||||
public int getBKTRu32Section1() { return BKTRu32Section1; }
|
||||
public int getBKTRs32Section1() { return BKTRs32Section1; }
|
||||
public byte[] getBKTRunknownSection1() { return BKTRunknownSection1; }
|
||||
public long getBKTRoffsetSection2() { return BKTRoffsetSection2; }
|
||||
public long getBKTRsizeSection2() { return BKTRsizeSection2; }
|
||||
public String getBKTRmagicSection2() { return BKTRmagicSection2; }
|
||||
public int getBKTRu32Section2() { return BKTRu32Section2; }
|
||||
public int getBKTRs32Section2() { return BKTRs32Section2; }
|
||||
public byte[] getBKTRunknownSection2() { return BKTRunknownSection2; }
|
||||
public byte[] getSectionCTR() { return sectionCTR; }
|
||||
public byte[] getUnknownEndPadding() { return unknownEndPadding; }
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NCA.NCASectionTableBlock;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static libKonogonka.LoperConverter.getLEint;
|
||||
import static libKonogonka.LoperConverter.getLElong;
|
||||
|
||||
public class SuperBlockIVFC {
|
||||
private String magic;
|
||||
private int magicNumber;
|
||||
private int masterHashSize;
|
||||
private int totalNumberOfLevels;
|
||||
private long lvl1Offset;
|
||||
private long lvl1Size;
|
||||
private int lvl1SBlockSize;
|
||||
private byte[] reserved1;
|
||||
|
||||
private long lvl2Offset;
|
||||
private long lvl2Size;
|
||||
private int lvl2SBlockSize;
|
||||
private byte[] reserved2;
|
||||
|
||||
private long lvl3Offset;
|
||||
private long lvl3Size;
|
||||
private int lvl3SBlockSize;
|
||||
private byte[] reserved3;
|
||||
|
||||
private long lvl4Offset;
|
||||
private long lvl4Size;
|
||||
private int lvl4SBlockSize;
|
||||
private byte[] reserved4;
|
||||
|
||||
private long lvl5Offset;
|
||||
private long lvl5Size;
|
||||
private int lvl5SBlockSize;
|
||||
private byte[] reserved5;
|
||||
|
||||
private long lvl6Offset;
|
||||
private long lvl6Size;
|
||||
private int lvl6SBlockSize;
|
||||
private byte[] reserved6;
|
||||
|
||||
private byte[] unknown;
|
||||
private byte[] hash;
|
||||
|
||||
SuperBlockIVFC(byte[] sbBytes){
|
||||
this.magic = new String(Arrays.copyOfRange(sbBytes, 0, 4), StandardCharsets.US_ASCII);
|
||||
this.magicNumber = getLEint(sbBytes, 0x4);
|
||||
this.masterHashSize = getLEint(sbBytes, 0x8);
|
||||
this.totalNumberOfLevels = getLEint(sbBytes, 0xc);
|
||||
|
||||
this.lvl1Offset = getLElong(sbBytes, 0x10);
|
||||
this.lvl1Size = getLElong(sbBytes, 0x18);
|
||||
this.lvl1SBlockSize = getLEint(sbBytes, 0x20);
|
||||
this.reserved1 = Arrays.copyOfRange(sbBytes, 0x24, 0x28);
|
||||
|
||||
this.lvl2Offset = getLElong(sbBytes, 0x28);
|
||||
this.lvl2Size = getLElong(sbBytes, 0x30);
|
||||
this.lvl2SBlockSize = getLEint(sbBytes, 0x38);
|
||||
this.reserved2 = Arrays.copyOfRange(sbBytes, 0x3c, 0x40);
|
||||
|
||||
this.lvl3Offset = getLElong(sbBytes, 0x40);
|
||||
this.lvl3Size = getLElong(sbBytes, 0x48);
|
||||
this.lvl3SBlockSize = getLEint(sbBytes, 0x50);
|
||||
this.reserved3 = Arrays.copyOfRange(sbBytes, 0x54, 0x58);
|
||||
|
||||
this.lvl4Offset = getLElong(sbBytes, 0x58);
|
||||
this.lvl4Size = getLElong(sbBytes, 0x60);
|
||||
this.lvl4SBlockSize = getLEint(sbBytes, 0x68);
|
||||
this.reserved4 = Arrays.copyOfRange(sbBytes, 0x6c, 0x70);
|
||||
|
||||
this.lvl5Offset = getLElong(sbBytes, 0x70);
|
||||
this.lvl5Size = getLElong(sbBytes, 0x78);
|
||||
this.lvl5SBlockSize = getLEint(sbBytes, 0x80);
|
||||
this.reserved5 = Arrays.copyOfRange(sbBytes, 0x84, 0x88);
|
||||
|
||||
this.lvl6Offset = getLElong(sbBytes, 0x88);
|
||||
this.lvl6Size = getLElong(sbBytes, 0x90);
|
||||
this.lvl6SBlockSize = getLEint(sbBytes, 0x98);
|
||||
this.reserved6 = Arrays.copyOfRange(sbBytes, 0x9c, 0xa0);
|
||||
|
||||
this.unknown = Arrays.copyOfRange(sbBytes, 0xa0, 0xc0);
|
||||
this.hash = Arrays.copyOfRange(sbBytes, 0xc0, 0xe0);
|
||||
/*
|
||||
System.out.println(magic);
|
||||
System.out.println(magicNumber);
|
||||
System.out.println(masterHashSize);
|
||||
System.out.println(totalNumberOfLevels);
|
||||
System.out.println(lvl1Offset);
|
||||
System.out.println(lvl1Size);
|
||||
System.out.println(lvl1SBlockSize);
|
||||
RainbowHexDump.hexDumpUTF8(reserved1);
|
||||
|
||||
System.out.println(lvl2Offset);
|
||||
System.out.println(lvl2Size);
|
||||
System.out.println(lvl2SBlockSize);
|
||||
RainbowHexDump.hexDumpUTF8(reserved2);
|
||||
|
||||
System.out.println(lvl3Offset);
|
||||
System.out.println(lvl3Size);
|
||||
System.out.println(lvl3SBlockSize);
|
||||
RainbowHexDump.hexDumpUTF8(reserved3);
|
||||
|
||||
System.out.println(lvl4Offset);
|
||||
System.out.println(lvl4Size);
|
||||
System.out.println(lvl4SBlockSize);
|
||||
RainbowHexDump.hexDumpUTF8(reserved4);
|
||||
|
||||
System.out.println(lvl5Offset);
|
||||
System.out.println(lvl5Size);
|
||||
System.out.println(lvl5SBlockSize);
|
||||
RainbowHexDump.hexDumpUTF8(reserved5);
|
||||
|
||||
System.out.println(lvl6Offset);
|
||||
System.out.println(lvl6Size);
|
||||
System.out.println(lvl6SBlockSize);
|
||||
RainbowHexDump.hexDumpUTF8(reserved6);
|
||||
|
||||
RainbowHexDump.hexDumpUTF8(unknown);
|
||||
RainbowHexDump.hexDumpUTF8(hash);
|
||||
// */
|
||||
}
|
||||
|
||||
public String getMagic() { return magic; }
|
||||
public int getMagicNumber() { return magicNumber; }
|
||||
public int getMasterHashSize() { return masterHashSize; }
|
||||
public int getTotalNumberOfLevels() { return totalNumberOfLevels; }
|
||||
public long getLvl1Offset() { return lvl1Offset; }
|
||||
public long getLvl1Size() { return lvl1Size; }
|
||||
public int getLvl1SBlockSize() { return lvl1SBlockSize; }
|
||||
public byte[] getReserved1() { return reserved1; }
|
||||
public long getLvl2Offset() { return lvl2Offset; }
|
||||
public long getLvl2Size() { return lvl2Size; }
|
||||
public int getLvl2SBlockSize() { return lvl2SBlockSize; }
|
||||
public byte[] getReserved2() { return reserved2; }
|
||||
public long getLvl3Offset() { return lvl3Offset; }
|
||||
public long getLvl3Size() { return lvl3Size; }
|
||||
public int getLvl3SBlockSize() { return lvl3SBlockSize; }
|
||||
public byte[] getReserved3() { return reserved3; }
|
||||
public long getLvl4Offset() { return lvl4Offset; }
|
||||
public long getLvl4Size() { return lvl4Size; }
|
||||
public int getLvl4SBlockSize() { return lvl4SBlockSize; }
|
||||
public byte[] getReserved4() { return reserved4; }
|
||||
public long getLvl5Offset() { return lvl5Offset; }
|
||||
public long getLvl5Size() { return lvl5Size; }
|
||||
public int getLvl5SBlockSize() { return lvl5SBlockSize; }
|
||||
public byte[] getReserved5() { return reserved5; }
|
||||
public long getLvl6Offset() { return lvl6Offset; }
|
||||
public long getLvl6Size() { return lvl6Size; }
|
||||
public int getLvl6SBlockSize() { return lvl6SBlockSize; }
|
||||
public byte[] getReserved6() { return reserved6; }
|
||||
public byte[] getUnknown() { return unknown; }
|
||||
public byte[] getHash() { return hash; }
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NCA.NCASectionTableBlock;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static libKonogonka.LoperConverter.getLEint;
|
||||
import static libKonogonka.LoperConverter.getLElong;
|
||||
|
||||
public class SuperBlockPFS0 {
|
||||
private byte[] SHA256hash;
|
||||
private int blockSize;
|
||||
private int unknownNumberTwo;
|
||||
private long hashTableOffset;
|
||||
private long hashTableSize;
|
||||
private long pfs0offset;
|
||||
private long pfs0size;
|
||||
private byte[] zeroes;
|
||||
|
||||
SuperBlockPFS0(byte[] sbBytes){
|
||||
SHA256hash = Arrays.copyOfRange(sbBytes, 0, 0x20);
|
||||
blockSize = getLEint(sbBytes, 0x20);
|
||||
unknownNumberTwo = getLEint(sbBytes, 0x24);
|
||||
hashTableOffset = getLElong(sbBytes, 0x28);
|
||||
hashTableSize = getLElong(sbBytes, 0x30);
|
||||
pfs0offset = getLElong(sbBytes, 0x38);
|
||||
pfs0size = getLElong(sbBytes, 0x40);
|
||||
zeroes = Arrays.copyOfRange(sbBytes, 0x48, 0xf8);
|
||||
}
|
||||
|
||||
public byte[] getSHA256hash() { return SHA256hash; }
|
||||
public int getBlockSize() { return blockSize; }
|
||||
public int getUnknownNumberTwo() { return unknownNumberTwo; }
|
||||
public long getHashTableOffset() { return hashTableOffset; }
|
||||
public long getHashTableSize() { return hashTableSize; }
|
||||
public long getPfs0offset() { return pfs0offset; }
|
||||
public long getPfs0size() { return pfs0size; }
|
||||
public byte[] getZeroes() { return zeroes; }
|
||||
}
|
81
src/main/java/libKonogonka/Tools/NPDM/ACI0/ACI0Provider.java
Normal file
81
src/main/java/libKonogonka/Tools/NPDM/ACI0/ACI0Provider.java
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NPDM.ACI0;
|
||||
|
||||
import libKonogonka.Tools.NPDM.KernelAccessControlProvider;
|
||||
import libKonogonka.Tools.NPDM.ServiceAccessControlProvider;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static libKonogonka.LoperConverter.getLEint;
|
||||
|
||||
public class ACI0Provider {
|
||||
private String magicNum;
|
||||
private byte[] reserved1;
|
||||
private byte[] titleID;
|
||||
private byte[] reserved2;
|
||||
private int fsAccessHeaderOffset;
|
||||
private int fsAccessHeaderSize;
|
||||
private int serviceAccessControlOffset;
|
||||
private int serviceAccessControlSize;
|
||||
private int kernelAccessControlOffset;
|
||||
private int kernelAccessControlSize;
|
||||
private byte[] reserved3;
|
||||
|
||||
private FSAccessHeaderProvider fsAccessHeaderProvider;
|
||||
private ServiceAccessControlProvider serviceAccessControlProvider;
|
||||
private KernelAccessControlProvider kernelAccessControlProvider;
|
||||
|
||||
public ACI0Provider(byte[] aci0bytes) throws Exception {
|
||||
if (aci0bytes.length < 0x40)
|
||||
throw new Exception("ACI0 size is too short");
|
||||
magicNum = new String(aci0bytes, 0, 0x4, StandardCharsets.UTF_8);
|
||||
reserved1 = Arrays.copyOfRange(aci0bytes, 0x4, 0x10);
|
||||
titleID = Arrays.copyOfRange(aci0bytes, 0x10, 0x18);
|
||||
reserved2 = Arrays.copyOfRange(aci0bytes, 0x18, 0x20);
|
||||
fsAccessHeaderOffset = getLEint(aci0bytes, 0x20);
|
||||
fsAccessHeaderSize = getLEint(aci0bytes, 0x24);
|
||||
serviceAccessControlOffset = getLEint(aci0bytes, 0x28);
|
||||
serviceAccessControlSize = getLEint(aci0bytes, 0x2C);
|
||||
kernelAccessControlOffset = getLEint(aci0bytes, 0x30);
|
||||
kernelAccessControlSize = getLEint(aci0bytes, 0x34);
|
||||
reserved3 = Arrays.copyOfRange(aci0bytes, 0x38, 0x40);
|
||||
|
||||
fsAccessHeaderProvider = new FSAccessHeaderProvider(Arrays.copyOfRange(aci0bytes, fsAccessHeaderOffset, fsAccessHeaderOffset+fsAccessHeaderSize));
|
||||
serviceAccessControlProvider = new ServiceAccessControlProvider(Arrays.copyOfRange(aci0bytes, serviceAccessControlOffset, serviceAccessControlOffset+serviceAccessControlSize));
|
||||
kernelAccessControlProvider = new KernelAccessControlProvider(Arrays.copyOfRange(aci0bytes, kernelAccessControlOffset, kernelAccessControlOffset+kernelAccessControlSize));
|
||||
}
|
||||
|
||||
public String getMagicNum() { return magicNum; }
|
||||
public byte[] getReserved1() { return reserved1; }
|
||||
public byte[] getTitleID() { return titleID; }
|
||||
public byte[] getReserved2() { return reserved2; }
|
||||
public int getFsAccessHeaderOffset() { return fsAccessHeaderOffset; }
|
||||
public int getFsAccessHeaderSize() { return fsAccessHeaderSize; }
|
||||
public int getServiceAccessControlOffset() { return serviceAccessControlOffset; }
|
||||
public int getServiceAccessControlSize() { return serviceAccessControlSize; }
|
||||
public int getKernelAccessControlOffset() { return kernelAccessControlOffset; }
|
||||
public int getKernelAccessControlSize() { return kernelAccessControlSize; }
|
||||
public byte[] getReserved3() { return reserved3; }
|
||||
|
||||
public FSAccessHeaderProvider getFsAccessHeaderProvider() { return fsAccessHeaderProvider; }
|
||||
public ServiceAccessControlProvider getServiceAccessControlProvider() { return serviceAccessControlProvider; }
|
||||
public KernelAccessControlProvider getKernelAccessControlProvider() { return kernelAccessControlProvider; }
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NPDM.ACI0;
|
||||
|
||||
import libKonogonka.LoperConverter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* For ACI0 Provider
|
||||
* */
|
||||
public class FSAccessHeaderProvider {
|
||||
|
||||
private byte version;
|
||||
private byte[] padding;
|
||||
private long permissionsBitmask;
|
||||
private int dataSize;
|
||||
private int contentOwnIdSectionSize;
|
||||
private int dataNownerSizes;
|
||||
private int saveDataOwnSectionSize;
|
||||
private byte[] unknownData;
|
||||
|
||||
public FSAccessHeaderProvider(byte[] bytes) {
|
||||
version = bytes[0];
|
||||
padding = Arrays.copyOfRange(bytes, 1, 0x4);
|
||||
permissionsBitmask = LoperConverter.getLElong(bytes, 0x4);
|
||||
dataSize = LoperConverter.getLEint(bytes, 0xC);
|
||||
contentOwnIdSectionSize = LoperConverter.getLEint(bytes, 0x10);
|
||||
dataNownerSizes = LoperConverter.getLEint(bytes, 0x14);
|
||||
saveDataOwnSectionSize = LoperConverter.getLEint(bytes, 0x18);
|
||||
unknownData = Arrays.copyOfRange(bytes, 0x1C, bytes.length);
|
||||
}
|
||||
|
||||
public byte getVersion() { return version; }
|
||||
public byte[] getPadding() { return padding; }
|
||||
public long getPermissionsBitmask() { return permissionsBitmask; }
|
||||
public int getDataSize() { return dataSize; }
|
||||
public int getContentOwnIdSectionSize() { return contentOwnIdSectionSize; }
|
||||
public int getDataNownerSizes() { return dataNownerSizes; }
|
||||
public int getSaveDataOwnSectionSize() { return saveDataOwnSectionSize; }
|
||||
public byte[] getUnknownData() { return unknownData; }
|
||||
}
|
104
src/main/java/libKonogonka/Tools/NPDM/ACID/ACIDProvider.java
Normal file
104
src/main/java/libKonogonka/Tools/NPDM/ACID/ACIDProvider.java
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NPDM.ACID;
|
||||
|
||||
import libKonogonka.Tools.NPDM.KernelAccessControlProvider;
|
||||
import libKonogonka.Tools.NPDM.ServiceAccessControlProvider;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static libKonogonka.LoperConverter.*;
|
||||
|
||||
public class ACIDProvider {
|
||||
|
||||
private byte[] rsa2048signature;
|
||||
private byte[] rsa2048publicKey;
|
||||
private String magicNum;
|
||||
private int dataSize;
|
||||
private byte[] reserved1;
|
||||
private byte flag1;
|
||||
private byte flag2;
|
||||
private byte flag3;
|
||||
private byte flag4;
|
||||
private long titleRangeMin;
|
||||
private long titleRangeMax;
|
||||
private int fsAccessControlOffset;
|
||||
private int fsAccessControlSize;
|
||||
private int serviceAccessControlOffset;
|
||||
private int serviceAccessControlSize;
|
||||
private int kernelAccessControlOffset;
|
||||
private int kernelAccessControlSize;
|
||||
private byte[] reserved2;
|
||||
|
||||
private FSAccessControlProvider fsAccessControlProvider;
|
||||
private ServiceAccessControlProvider serviceAccessControlProvider;
|
||||
private KernelAccessControlProvider kernelAccessControlProvider;
|
||||
|
||||
public ACIDProvider(byte[] acidBytes) throws Exception{
|
||||
if (acidBytes.length < 0x240)
|
||||
throw new Exception("ACIDProvider -> ACI0 size is too short");
|
||||
rsa2048signature = Arrays.copyOfRange(acidBytes, 0, 0x100);
|
||||
rsa2048publicKey = Arrays.copyOfRange(acidBytes, 0x100, 0x200);
|
||||
magicNum = new String(acidBytes, 0x200, 0x4, StandardCharsets.UTF_8);
|
||||
dataSize = getLEint(acidBytes, 0x204);
|
||||
reserved1 = Arrays.copyOfRange(acidBytes, 0x208, 0x20C);
|
||||
flag1 = acidBytes[0x20C];
|
||||
flag2 = acidBytes[0x20D];
|
||||
flag3 = acidBytes[0x20E];
|
||||
flag4 = acidBytes[0x20F];
|
||||
titleRangeMin = getLElong(acidBytes, 0x210);
|
||||
titleRangeMax = getLElong(acidBytes, 0x218);
|
||||
fsAccessControlOffset = getLEint(acidBytes, 0x220);
|
||||
fsAccessControlSize = getLEint(acidBytes, 0x224);
|
||||
serviceAccessControlOffset = getLEint(acidBytes, 0x228);
|
||||
serviceAccessControlSize = getLEint(acidBytes, 0x22C);
|
||||
kernelAccessControlOffset = getLEint(acidBytes, 0x230);
|
||||
kernelAccessControlSize = getLEint(acidBytes, 0x234);
|
||||
reserved2 = Arrays.copyOfRange(acidBytes, 0x238, 0x240);
|
||||
if (fsAccessControlOffset > serviceAccessControlOffset || serviceAccessControlOffset > kernelAccessControlOffset )
|
||||
throw new Exception("ACIDProvider -> blocks inside the ACID are not sorted in ascending order. Only ascending order supported.");
|
||||
fsAccessControlProvider = new FSAccessControlProvider(Arrays.copyOfRange(acidBytes, fsAccessControlOffset, fsAccessControlOffset+fsAccessControlSize));
|
||||
serviceAccessControlProvider = new ServiceAccessControlProvider(Arrays.copyOfRange(acidBytes, serviceAccessControlOffset, serviceAccessControlOffset+serviceAccessControlSize));
|
||||
kernelAccessControlProvider = new KernelAccessControlProvider(Arrays.copyOfRange(acidBytes, kernelAccessControlOffset, kernelAccessControlOffset+kernelAccessControlSize));
|
||||
}
|
||||
|
||||
public byte[] getRsa2048signature() { return rsa2048signature; }
|
||||
public byte[] getRsa2048publicKey() { return rsa2048publicKey; }
|
||||
public String getMagicNum() { return magicNum; }
|
||||
public int getDataSize() { return dataSize; }
|
||||
public byte[] getReserved1() { return reserved1; }
|
||||
public byte getFlag1() { return flag1; }
|
||||
public byte getFlag2() { return flag2; }
|
||||
public byte getFlag3() { return flag3; }
|
||||
public byte getFlag4() { return flag4; }
|
||||
public long getTitleRangeMin() { return titleRangeMin; }
|
||||
public long getTitleRangeMax() { return titleRangeMax; }
|
||||
public int getFsAccessControlOffset() { return fsAccessControlOffset; }
|
||||
public int getFsAccessControlSize() { return fsAccessControlSize; }
|
||||
public int getServiceAccessControlOffset() { return serviceAccessControlOffset; }
|
||||
public int getServiceAccessControlSize() { return serviceAccessControlSize; }
|
||||
public int getKernelAccessControlOffset() { return kernelAccessControlOffset; }
|
||||
public int getKernelAccessControlSize() { return kernelAccessControlSize; }
|
||||
public byte[] getReserved2() { return reserved2; }
|
||||
|
||||
public FSAccessControlProvider getFsAccessControlProvider() { return fsAccessControlProvider; }
|
||||
public ServiceAccessControlProvider getServiceAccessControlProvider() { return serviceAccessControlProvider; }
|
||||
public KernelAccessControlProvider getKernelAccessControlProvider() { return kernelAccessControlProvider; }
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NPDM.ACID;
|
||||
|
||||
import libKonogonka.LoperConverter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* For ACID Provider
|
||||
* */
|
||||
public class FSAccessControlProvider {
|
||||
|
||||
private byte version;
|
||||
private byte[] padding;
|
||||
private long permissionsBitmask;
|
||||
private byte[] reserved;
|
||||
|
||||
public FSAccessControlProvider(byte[] bytes) {
|
||||
version = bytes[0];
|
||||
padding = Arrays.copyOfRange(bytes, 1, 0x4);
|
||||
permissionsBitmask = LoperConverter.getLElong(bytes, 0x4);
|
||||
reserved = Arrays.copyOfRange(bytes, 0xC, 0x2C);
|
||||
}
|
||||
|
||||
public byte getVersion() { return version; }
|
||||
public byte[] getPadding() { return padding; }
|
||||
public long getPermissionsBitmask() { return permissionsBitmask; }
|
||||
public byte[] getReserved() { return reserved; }
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NPDM;
|
||||
|
||||
import libKonogonka.LoperConverter;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/*
|
||||
NOTE: This implementation is extremely bad for using application as library. Use raw for own purposes.
|
||||
|
||||
NOTE:
|
||||
KAC is set of 4-byes blocks
|
||||
Consider them as uInt32 (Read as Little endian)
|
||||
Look on the tail of each block (low bits). If tail is equals to mask like 0111111 then such block is related to one of the possible sections (KernelFlags etc.)
|
||||
If it's related to the one of the blocks, then we could pick useful data from this block.
|
||||
Example:
|
||||
36 BYES on this section, then 9 blocks with len = 4-bytes each available
|
||||
1 00-01-02-03
|
||||
2 04-05-06-07
|
||||
3 08-09-10-11
|
||||
4 12-13-14-15
|
||||
5 16-17-18-19
|
||||
6 20-21-22-23
|
||||
7 24-25-26-27
|
||||
8 28-29-30-31
|
||||
9 32-33-34-35
|
||||
|
||||
Possible patterns are:
|
||||
Where '+' is useful data; '0' and '1' in low bytes are pattern.
|
||||
Octal | Decimal
|
||||
++++++++++++++++++++++++++++0111 | 7 <- KernelFlags
|
||||
+++++++++++++++++++++++++++01111 | 15 <- SyscallMask
|
||||
+++++++++++++++++++++++++0111111 | 63 <- MapIoOrNormalRange
|
||||
++++++++++++++++++++++++01111111 | 127 <- MapNormalPage (RW)
|
||||
++++++++++++++++++++011111111111 | 2+47 <- InterruptPair
|
||||
++++++++++++++++++01111111111111 | 8191 <- ApplicationType
|
||||
+++++++++++++++++011111111111111 | 16383 <- KernelReleaseVersion
|
||||
++++++++++++++++0111111111111111 | 32767 <- HandleTableSize
|
||||
+++++++++++++++01111111111111111 | 65535 <- DebugFlags
|
||||
Other masks could be implemented by N in future (?).
|
||||
|
||||
Calculation example:
|
||||
Dec 1 = 00000000000000000000000000000001
|
||||
00100000000000000000000000000111 & 1 = 1
|
||||
00010000000000000000000000000011 & 1 = 1
|
||||
00001000000000000000000000000001 & 1 = 1
|
||||
00000100000000000000000000000000 & 1 = 0
|
||||
|
||||
TIP: Generate
|
||||
int j = 0xFFFFFFFF;
|
||||
for (byte i = 0; i < 16; i++){
|
||||
j = (j << 1);
|
||||
RainbowHexDump.octDumpInt(~j);
|
||||
}
|
||||
*/
|
||||
|
||||
public class KernelAccessControlProvider {
|
||||
|
||||
private static final int KERNELFLAGS = 3,
|
||||
SYSCALLMASK = 4,
|
||||
MAPIOORNORMALRANGE = 6,
|
||||
MAPNORMALPAGE_RW = 7,
|
||||
INTERRUPTPAIR = 11,
|
||||
APPLICATIONTYPE = 13,
|
||||
KERNELRELEASEVERSION = 14,
|
||||
HANDLETABLESIZE = 15,
|
||||
DEBUGFLAGS = 16;
|
||||
|
||||
// RAW data
|
||||
private LinkedList<Integer> rawData;
|
||||
// Kernel flags
|
||||
private boolean kernelFlagsAvailable;
|
||||
private int kernelFlagCpuIdHi,
|
||||
kernelFlagCpuIdLo,
|
||||
kernelFlagThreadPrioHi,
|
||||
kernelFlagThreadPrioLo;
|
||||
// Syscall Masks as index | mask - order AS IS. [0] = bit5; [1] = bit6
|
||||
private LinkedHashMap<Byte, byte[]> syscallMasks; // Index, Mask
|
||||
// MapIoOrNormalRange
|
||||
private LinkedHashMap<byte[], Boolean> mapIoOrNormalRange; // alt page+num, RO flag
|
||||
// MapNormalPage (RW)
|
||||
private byte[] mapNormalPage; // TODO: clarify is possible to have multiple
|
||||
// InterruptPair
|
||||
private LinkedHashMap<Integer, byte[][]> interruptPairs; // Number; irq0, irq2
|
||||
// Application type
|
||||
private int applicationType;
|
||||
// KernelReleaseVersion
|
||||
private boolean isKernelRelVersionAvailable;
|
||||
private int kernelRelVersionMajor,
|
||||
kernelRelVersionMinor;
|
||||
// Handle Table Size
|
||||
private int handleTableSize;
|
||||
// Debug flags
|
||||
private boolean debugFlagsAvailable,
|
||||
canBeDebugged,
|
||||
canDebugOthers;
|
||||
|
||||
public KernelAccessControlProvider(byte[] bytes) throws Exception{
|
||||
if (bytes.length < 4)
|
||||
throw new Exception("ACID-> KernelAccessControlProvider: too small size of the Kernel Access Control");
|
||||
|
||||
rawData = new LinkedList<Integer>();
|
||||
|
||||
interruptPairs = new LinkedHashMap<>();
|
||||
syscallMasks = new LinkedHashMap<Byte, byte[]>();
|
||||
mapIoOrNormalRange = new LinkedHashMap<byte[], Boolean>();
|
||||
|
||||
int position = 0;
|
||||
// Collect all blocks
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
int block = LoperConverter.getLEint(bytes, position);
|
||||
position += 4;
|
||||
|
||||
rawData.add(block);
|
||||
|
||||
//RainbowHexDump.octDumpInt(block);
|
||||
|
||||
int type = getMinBitCnt(block);
|
||||
|
||||
switch (type){
|
||||
case KERNELFLAGS:
|
||||
kernelFlagsAvailable = true;
|
||||
kernelFlagCpuIdHi = block >> 24;
|
||||
kernelFlagCpuIdLo = block >> 16 & 0b11111111;
|
||||
kernelFlagThreadPrioHi = block >> 10 & 0b111111;
|
||||
kernelFlagThreadPrioLo = block >> 4 & 0b111111;
|
||||
//System.out.println("KERNELFLAGS "+kernelFlagCpuIdHi+" "+kernelFlagCpuIdLo+" "+kernelFlagThreadPrioHi+" "+kernelFlagThreadPrioLo);
|
||||
break;
|
||||
case SYSCALLMASK:
|
||||
byte maskTableIndex = (byte) (block >> 29 & 0b111); // declared as byte; max value could be 7; min - 0;
|
||||
byte[] mask = new byte[24]; // Consider as bit.
|
||||
//System.out.println("SYSCALLMASK ind: "+maskTableIndex);
|
||||
|
||||
for (int k = 28; k >= 5; k--) {
|
||||
mask[k-5] = (byte) (block >> k & 1); // Only 1 or 0 possible
|
||||
//System.out.print(mask[k-5]);
|
||||
}
|
||||
//System.out.println();
|
||||
syscallMasks.put(maskTableIndex, mask);
|
||||
break;
|
||||
case MAPIOORNORMALRANGE:
|
||||
byte[] altStPgNPgNum = new byte[24];
|
||||
//System.out.println("MAPIOORNORMALRANGE Flag: "+((block >> 31 & 1) != 0));
|
||||
|
||||
for (int k = 30; k >= 7; k--){
|
||||
altStPgNPgNum[k-7] = (byte) (block >> k & 1); // Only 1 or 0 possible
|
||||
//System.out.print(altStPgNPgNum[k-7]);
|
||||
}
|
||||
mapIoOrNormalRange.put(altStPgNPgNum, (block >> 31 & 1) != 0);
|
||||
//System.out.println();
|
||||
break;
|
||||
case MAPNORMALPAGE_RW:
|
||||
//System.out.println("MAPNORMALPAGE_RW\t");
|
||||
mapNormalPage = new byte[24];
|
||||
for (int k = 31; k >= 8; k--){
|
||||
mapNormalPage[k-8] = (byte) (block >> k & 1);
|
||||
//System.out.print(mapNormalPage[k-8]);
|
||||
}
|
||||
//System.out.println();
|
||||
break;
|
||||
case INTERRUPTPAIR:
|
||||
//System.out.println("INTERRUPTPAIR");
|
||||
//RainbowHexDump.octDumpInt(block);
|
||||
byte[][] pair = new byte[2][];
|
||||
byte[] irq0 = new byte[10];
|
||||
byte[] irq1 = new byte[10];
|
||||
|
||||
for (int k = 21; k >= 12; k--)
|
||||
irq0[k-12] = (byte) (block >> k & 1);
|
||||
for (int k = 31; k >= 22; k--)
|
||||
irq1[k-22] = (byte) (block >> k & 1);
|
||||
pair[0] = irq0;
|
||||
pair[1] = irq1;
|
||||
interruptPairs.put(interruptPairs.size(), pair);
|
||||
break;
|
||||
case APPLICATIONTYPE:
|
||||
applicationType = block >> 14 & 0b111;
|
||||
//System.out.println("APPLICATIONTYPE "+applicationType);
|
||||
break;
|
||||
case KERNELRELEASEVERSION:
|
||||
//System.out.println("KERNELRELEASEVERSION\t"+(block >> 19 & 0b111111111111)+"."+(block >> 15 & 0b1111)+".X");
|
||||
isKernelRelVersionAvailable = true;
|
||||
kernelRelVersionMajor = (block >> 19 & 0b111111111111);
|
||||
kernelRelVersionMinor = (block >> 15 & 0b1111);
|
||||
break;
|
||||
case HANDLETABLESIZE:
|
||||
handleTableSize = block >> 16 & 0b1111111111;
|
||||
//System.out.println("HANDLETABLESIZE "+handleTableSize);
|
||||
break;
|
||||
case DEBUGFLAGS:
|
||||
debugFlagsAvailable = true;
|
||||
canBeDebugged = (block >> 17 & 1) != 0;
|
||||
canDebugOthers = (block >> 18 & 1) != 0;
|
||||
//System.out.println("DEBUGFLAGS "+canBeDebugged+" "+canDebugOthers);
|
||||
break;
|
||||
default:
|
||||
System.out.println("UNKNOWN\t\t"+block+" "+type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getMinBitCnt(int value){
|
||||
int minBitCnt = 0;
|
||||
while ((value & 1) != 0){
|
||||
value >>= 1;
|
||||
minBitCnt++;
|
||||
}
|
||||
return minBitCnt;
|
||||
}
|
||||
public LinkedList<Integer> getRawData() { return rawData; }
|
||||
public boolean isKernelFlagsAvailable() { return kernelFlagsAvailable; }
|
||||
public int getKernelFlagCpuIdHi() { return kernelFlagCpuIdHi; }
|
||||
public int getKernelFlagCpuIdLo() { return kernelFlagCpuIdLo; }
|
||||
public int getKernelFlagThreadPrioHi() { return kernelFlagThreadPrioHi; }
|
||||
public int getKernelFlagThreadPrioLo() { return kernelFlagThreadPrioLo; }
|
||||
public LinkedHashMap<byte[], Boolean> getMapIoOrNormalRange() { return mapIoOrNormalRange; }
|
||||
public byte[] getMapNormalPage() { return mapNormalPage; }
|
||||
public LinkedHashMap<Integer, byte[][]> getInterruptPairs() { return interruptPairs; }
|
||||
public int getApplicationType() { return applicationType; }
|
||||
public boolean isKernelRelVersionAvailable() { return isKernelRelVersionAvailable; }
|
||||
public int getKernelRelVersionMajor() { return kernelRelVersionMajor; }
|
||||
public int getKernelRelVersionMinor() { return kernelRelVersionMinor;}
|
||||
public int getHandleTableSize() { return handleTableSize; }
|
||||
public boolean isDebugFlagsAvailable() { return debugFlagsAvailable; }
|
||||
public boolean isCanBeDebugged() { return canBeDebugged; }
|
||||
public boolean isCanDebugOthers() { return canDebugOthers; }
|
||||
public LinkedHashMap<Byte, byte[]> getSyscallMasks() { return syscallMasks; }
|
||||
}
|
167
src/main/java/libKonogonka/Tools/NPDM/NPDMProvider.java
Normal file
167
src/main/java/libKonogonka/Tools/NPDM/NPDMProvider.java
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NPDM;
|
||||
|
||||
import libKonogonka.Tools.ASuperInFileProvider;
|
||||
import libKonogonka.Tools.NPDM.ACI0.ACI0Provider;
|
||||
import libKonogonka.Tools.NPDM.ACID.ACIDProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static libKonogonka.LoperConverter.*;
|
||||
|
||||
public class NPDMProvider extends ASuperInFileProvider {
|
||||
|
||||
private String magicNum;
|
||||
private byte[] reserved1;
|
||||
private byte MMUFlags;
|
||||
private byte reserved2;
|
||||
private byte mainThreadPrio;
|
||||
private byte mainThreadCoreNum;
|
||||
private byte[] reserved3;
|
||||
private int personalMmHeapSize; // safe-to-store
|
||||
private int version; // safe?
|
||||
private long mainThreadStackSize; // TODO: check if safe
|
||||
private String titleName;
|
||||
private byte[] productCode;
|
||||
private byte[] reserved4;
|
||||
private int aci0offset; // originally 4-bytes (u-int)
|
||||
private int aci0size; // originally 4-bytes (u-int)
|
||||
private int acidOffset; // originally 4-bytes (u-int)
|
||||
private int acidSize; // originally 4-bytes (u-int)
|
||||
|
||||
private ACI0Provider aci0;
|
||||
private ACIDProvider acid;
|
||||
|
||||
public NPDMProvider(PipedInputStream pis) throws Exception{
|
||||
byte[] mainBuf = new byte[0x80];
|
||||
if(pis.read(mainBuf) != 0x80)
|
||||
throw new Exception("NPDMProvider: Failed to read 'META'");
|
||||
aci0offset = getLEint(mainBuf, 0x70);
|
||||
aci0size = getLEint(mainBuf, 0x74);
|
||||
acidOffset = getLEint(mainBuf, 0x78);
|
||||
acidSize = getLEint(mainBuf, 0x7C);
|
||||
byte[] aci0Buf;
|
||||
byte[] acidBuf;
|
||||
if (aci0offset < acidOffset){
|
||||
if (pis.skip(aci0offset - 0x80) != (aci0offset - 0x80))
|
||||
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACI0'");
|
||||
if ((aci0Buf = readFromStream(pis, aci0size)) == null)
|
||||
throw new Exception("NPDMProvider: Failed to read 'ACI0'");
|
||||
if (pis.skip(acidOffset - aci0offset - aci0size) != (acidOffset - aci0offset - aci0size))
|
||||
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACID'");
|
||||
if ((acidBuf = readFromStream(pis, acidSize)) == null)
|
||||
throw new Exception("NPDMProvider: Failed to read 'ACID'");
|
||||
}
|
||||
else {
|
||||
if (pis.skip(acidOffset - 0x80) != (acidOffset - 0x80))
|
||||
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACID'");
|
||||
if ((acidBuf = readFromStream(pis, acidSize)) == null)
|
||||
throw new Exception("NPDMProvider: Failed to read 'ACID'");
|
||||
if (pis.skip(aci0offset - acidOffset - acidSize) != (aci0offset - acidOffset - acidSize))
|
||||
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACI0'");
|
||||
if ((aci0Buf = readFromStream(pis, aci0size)) == null)
|
||||
throw new Exception("NPDMProvider: Failed to read 'ACI0'");
|
||||
}
|
||||
magicNum = new String(mainBuf, 0, 4, StandardCharsets.UTF_8);
|
||||
reserved1 = Arrays.copyOfRange(mainBuf, 0x4, 0xC);
|
||||
MMUFlags = mainBuf[0xC];
|
||||
reserved2 = mainBuf[0xD];
|
||||
mainThreadPrio = mainBuf[0xE];
|
||||
mainThreadCoreNum = mainBuf[0xF];
|
||||
reserved3 = Arrays.copyOfRange(mainBuf, 0x10, 0x14);
|
||||
personalMmHeapSize = getLEint(mainBuf, 0x14);
|
||||
version = getLEint(mainBuf, 0x18);
|
||||
mainThreadStackSize = getLElongOfInt(mainBuf, 0x1C);
|
||||
titleName = new String(mainBuf, 0x20, 0x10, StandardCharsets.UTF_8);
|
||||
productCode = Arrays.copyOfRange(mainBuf, 0x30, 0x40);
|
||||
reserved4 = Arrays.copyOfRange(mainBuf, 0x40, 0x70);
|
||||
|
||||
aci0 = new ACI0Provider(aci0Buf);
|
||||
acid = new ACIDProvider(acidBuf);
|
||||
}
|
||||
|
||||
public NPDMProvider(File file) throws Exception { this(file, 0); }
|
||||
|
||||
public NPDMProvider(File file, long offset) throws Exception {
|
||||
if (file.length() - offset < 0x80) // Header's size
|
||||
throw new Exception("NPDMProvider: File is too small.");
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
raf.seek(offset);
|
||||
// Get META
|
||||
byte[] metaBuf = new byte[0x80];
|
||||
if (raf.read(metaBuf) != 0x80)
|
||||
throw new Exception("NPDMProvider: Failed to read 'META'");
|
||||
magicNum = new String(metaBuf, 0, 4, StandardCharsets.UTF_8);
|
||||
reserved1 = Arrays.copyOfRange(metaBuf, 0x4, 0xC);
|
||||
MMUFlags = metaBuf[0xC];
|
||||
reserved2 = metaBuf[0xD];
|
||||
mainThreadPrio = metaBuf[0xE];
|
||||
mainThreadCoreNum = metaBuf[0xF];
|
||||
reserved3 = Arrays.copyOfRange(metaBuf, 0x10, 0x14);
|
||||
personalMmHeapSize = getLEint(metaBuf, 0x14);
|
||||
version = getLEint(metaBuf, 0x18);
|
||||
mainThreadStackSize = getLElongOfInt(metaBuf, 0x1C);
|
||||
titleName = new String(metaBuf, 0x20, 0x10, StandardCharsets.UTF_8);
|
||||
productCode = Arrays.copyOfRange(metaBuf, 0x30, 0x40);
|
||||
reserved4 = Arrays.copyOfRange(metaBuf, 0x40, 0x70);
|
||||
aci0offset = getLEint(metaBuf, 0x70);
|
||||
aci0size = getLEint(metaBuf, 0x74);
|
||||
acidOffset = getLEint(metaBuf, 0x78);
|
||||
acidSize = getLEint(metaBuf, 0x7C);
|
||||
// Get ACI0
|
||||
raf.seek(aci0offset);
|
||||
metaBuf = new byte[aci0size]; // TODO: NOTE: we read all size but it's memory consuming
|
||||
if (raf.read(metaBuf) != aci0size)
|
||||
throw new Exception("NPDMProvider: Failed to read 'ACI0'");
|
||||
aci0 = new ACI0Provider(metaBuf);
|
||||
// Get ACID
|
||||
raf.seek(acidOffset);
|
||||
metaBuf = new byte[acidSize]; // TODO: NOTE: we read all size but it's memory consuming
|
||||
if (raf.read(metaBuf) != acidSize)
|
||||
throw new Exception("NPDMProvider: Failed to read 'ACID'");
|
||||
acid = new ACIDProvider(metaBuf);
|
||||
raf.close();
|
||||
}
|
||||
|
||||
public String getMagicNum() { return magicNum; }
|
||||
public byte[] getReserved1() { return reserved1; }
|
||||
public byte getMMUFlags() { return MMUFlags; }
|
||||
public byte getReserved2() { return reserved2; }
|
||||
public byte getMainThreadPrio() { return mainThreadPrio; }
|
||||
public byte getMainThreadCoreNum() { return mainThreadCoreNum; }
|
||||
public byte[] getReserved3() { return reserved3; }
|
||||
public int getPersonalMmHeapSize() { return personalMmHeapSize; }
|
||||
public int getVersion() { return version; }
|
||||
public long getMainThreadStackSize() { return mainThreadStackSize; }
|
||||
public String getTitleName() { return titleName; }
|
||||
public byte[] getProductCode() { return productCode; }
|
||||
public byte[] getReserved4() { return reserved4; }
|
||||
public int getAci0offset() { return aci0offset; }
|
||||
public int getAci0size() { return aci0size; }
|
||||
public int getAcidOffset() { return acidOffset; }
|
||||
public int getAcidSize() { return acidSize; }
|
||||
|
||||
public ACI0Provider getAci0() { return aci0; }
|
||||
public ACIDProvider getAcid() { return acid; }
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.NPDM;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public class ServiceAccessControlProvider {
|
||||
|
||||
private LinkedHashMap<String, Byte> collection;
|
||||
|
||||
public ServiceAccessControlProvider(byte[] bytes){
|
||||
collection = new LinkedHashMap<>();
|
||||
byte key;
|
||||
String value;
|
||||
|
||||
int i = 0;
|
||||
|
||||
while (i < bytes.length){
|
||||
key = bytes[i];
|
||||
value = new String(bytes, i+1, getSize(key), StandardCharsets.UTF_8);
|
||||
collection.put(value, key);
|
||||
i += getSize(key)+1;
|
||||
}
|
||||
}
|
||||
|
||||
private int getSize(byte control) {
|
||||
return ((byte) 0x7 & control) + (byte) 0x01;
|
||||
}
|
||||
|
||||
public LinkedHashMap<String, Byte> getCollection() { return collection; }
|
||||
}
|
31
src/main/java/libKonogonka/Tools/PFS0/IPFS0Provider.java
Normal file
31
src/main/java/libKonogonka/Tools/PFS0/IPFS0Provider.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.PFS0;
|
||||
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
|
||||
public interface IPFS0Provider extends ISuperProvider {
|
||||
boolean isEncrypted();
|
||||
String getMagic();
|
||||
int getFilesCount();
|
||||
int getStringTableSize();
|
||||
byte[] getPadding();
|
||||
|
||||
PFS0subFile[] getPfs0subFiles();
|
||||
}
|
304
src/main/java/libKonogonka/Tools/PFS0/PFS0EncryptedProvider.java
Normal file
304
src/main/java/libKonogonka/Tools/PFS0/PFS0EncryptedProvider.java
Normal file
|
@ -0,0 +1,304 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.PFS0;
|
||||
|
||||
import libKonogonka.ctraes.AesCtrDecryptSimple;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static libKonogonka.LoperConverter.*;
|
||||
|
||||
public class PFS0EncryptedProvider implements IPFS0Provider{
|
||||
private long rawFileDataStart; // Always -1 @ PFS0EncryptedProvider
|
||||
|
||||
private String magic;
|
||||
private int filesCount;
|
||||
private int stringTableSize;
|
||||
private byte[] padding;
|
||||
private PFS0subFile[] pfs0subFiles;
|
||||
|
||||
//---------------------------------------
|
||||
|
||||
private long rawBlockDataStart;
|
||||
|
||||
private long offsetPositionInFile;
|
||||
private File file;
|
||||
private byte[] key;
|
||||
private byte[] sectionCTR;
|
||||
private long mediaStartOffset; // In 512-blocks
|
||||
private long mediaEndOffset; // In 512-blocks
|
||||
|
||||
public PFS0EncryptedProvider(PipedInputStream pipedInputStream,
|
||||
long pfs0offsetPosition,
|
||||
long offsetPositionInFile,
|
||||
File fileWithEncPFS0,
|
||||
byte[] key,
|
||||
byte[] sectionCTR,
|
||||
long mediaStartOffset,
|
||||
long mediaEndOffset
|
||||
) throws Exception{
|
||||
// Populate 'meta' data that is needed for getProviderSubFilePipedInpStream()
|
||||
this.offsetPositionInFile = offsetPositionInFile;
|
||||
this.file = fileWithEncPFS0;
|
||||
this.key = key;
|
||||
this.sectionCTR = sectionCTR;
|
||||
this.mediaStartOffset = mediaStartOffset;
|
||||
this.mediaEndOffset = mediaEndOffset;
|
||||
// pfs0offsetPosition is a position relative to Media block. Lets add pfs0 'header's' bytes count and get raw data start position in media block
|
||||
rawFileDataStart = -1; // Set -1 for PFS0EncryptedProvider
|
||||
// Detect raw data start position using next var
|
||||
rawBlockDataStart = pfs0offsetPosition;
|
||||
|
||||
byte[] fileStartingBytes = new byte[0x10];
|
||||
// Read PFS0Provider, files count, header, padding (4 zero bytes)
|
||||
|
||||
for (int i = 0; i < 0x10; i++){
|
||||
int currentByte = pipedInputStream.read();
|
||||
if (currentByte == -1) {
|
||||
throw new Exception("PFS0EncryptedProvider: Reading stream suddenly ended while trying to read starting 0x10 bytes");
|
||||
}
|
||||
fileStartingBytes[i] = (byte)currentByte;
|
||||
}
|
||||
// Update position
|
||||
rawBlockDataStart += 0x10;
|
||||
// Check PFS0Provider
|
||||
magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII);
|
||||
if (! magic.equals("PFS0")){
|
||||
throw new Exception("PFS0EncryptedProvider: Bad magic");
|
||||
}
|
||||
// Get files count
|
||||
filesCount = getLEint(fileStartingBytes, 0x4);
|
||||
if (filesCount <= 0 ) {
|
||||
throw new Exception("PFS0EncryptedProvider: Files count is too small");
|
||||
}
|
||||
// Get string table
|
||||
stringTableSize = getLEint(fileStartingBytes, 0x8);
|
||||
if (stringTableSize <= 0 ){
|
||||
throw new Exception("PFS0EncryptedProvider: String table is too small");
|
||||
}
|
||||
padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10);
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
pfs0subFiles = new PFS0subFile[filesCount];
|
||||
|
||||
long[] offsetsSubFiles = new long[filesCount];
|
||||
long[] sizesSubFiles = new long[filesCount];
|
||||
int[] strTableOffsets = new int[filesCount];
|
||||
byte[][] zeroBytes = new byte[filesCount][];
|
||||
|
||||
byte[] fileEntryTable = new byte[0x18];
|
||||
for (int i=0; i < filesCount; i++){
|
||||
for (int j = 0; j < 0x18; j++){
|
||||
int currentByte = pipedInputStream.read();
|
||||
if (currentByte == -1) {
|
||||
throw new Exception("PFS0EncryptedProvider: Reading stream suddenly ended while trying to read File Entry Table #"+i);
|
||||
}
|
||||
fileEntryTable[j] = (byte)currentByte;
|
||||
}
|
||||
offsetsSubFiles[i] = getLElong(fileEntryTable, 0);
|
||||
sizesSubFiles[i] = getLElong(fileEntryTable, 0x8);
|
||||
strTableOffsets[i] = getLEint(fileEntryTable, 0x10);
|
||||
zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18);
|
||||
// Update position
|
||||
rawBlockDataStart += 0x18;
|
||||
}
|
||||
//**********************************************************************************************************
|
||||
// In here pointer in front of String table
|
||||
String[] subFileNames = new String[filesCount];
|
||||
byte[] stringTbl = new byte[stringTableSize];
|
||||
|
||||
for (int i = 0; i < stringTableSize; i++){
|
||||
int currentByte = pipedInputStream.read();
|
||||
if (currentByte == -1) {
|
||||
throw new Exception("PFS0EncryptedProvider: Reading stream suddenly ended while trying to read string table");
|
||||
}
|
||||
stringTbl[i] = (byte)currentByte;
|
||||
}
|
||||
// Update position
|
||||
rawBlockDataStart += stringTableSize;
|
||||
|
||||
for (int i=0; i < filesCount; i++){
|
||||
int j = 0;
|
||||
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
|
||||
j++;
|
||||
subFileNames[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8);
|
||||
}
|
||||
for (int i = 0; i < filesCount; i++){
|
||||
pfs0subFiles[i] = new PFS0subFile(
|
||||
subFileNames[i],
|
||||
offsetsSubFiles[i],
|
||||
sizesSubFiles[i],
|
||||
zeroBytes[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncrypted() { return true; }
|
||||
@Override
|
||||
public String getMagic() { return magic; }
|
||||
@Override
|
||||
public int getFilesCount() { return filesCount; }
|
||||
@Override
|
||||
public int getStringTableSize() { return stringTableSize; }
|
||||
@Override
|
||||
public byte[] getPadding() { return padding; }
|
||||
@Override
|
||||
public long getRawFileDataStart() { return rawFileDataStart; }
|
||||
@Override
|
||||
public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; }
|
||||
@Override
|
||||
public File getFile(){ return file; }
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception { // TODO: rewrite
|
||||
if (subFileNumber >= pfs0subFiles.length)
|
||||
throw new Exception("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists");
|
||||
|
||||
Thread workerThread;
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Executing thread");
|
||||
try {
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
// Let's store what we're about to skip
|
||||
long skipInitL = offsetPositionInFile + (mediaStartOffset * 0x200); // NOTE: NEVER cast to int.
|
||||
// Check if skip was successful
|
||||
if (bis.skip(skipInitL) != skipInitL) {
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Failed to skip range "+skipInitL);
|
||||
return;
|
||||
}
|
||||
|
||||
AesCtrDecryptSimple aesCtrDecryptSimple = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
|
||||
//----------------------------- Pre-set: skip non-necessary data --------------------------------
|
||||
|
||||
long startBlock = (rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) / 0x200; // <- pointing to place where actual data starts
|
||||
int skipBytes;
|
||||
|
||||
if (startBlock > 0) {
|
||||
aesCtrDecryptSimple.skipNext(startBlock);
|
||||
skipBytes = (int)(startBlock * 0x200);
|
||||
if (bis.skip(skipBytes) != skipBytes) {
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Failed to skip range "+skipBytes);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------- Step 1: get starting bytes from the end of the junk block --------------------------------
|
||||
|
||||
// Since our data could be located in position with some offset from the decrypted block, let's skip bytes left. Considering the case when data is not aligned to block
|
||||
skipBytes = (int) ( (rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) - startBlock * 0x200); // <- How much bytes shall we skip to reach requested data start of sub-file
|
||||
|
||||
if (skipBytes > 0) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
// If we have extra-small file that is less then a block and even more
|
||||
if ((0x200 - skipBytes) > pfs0subFiles[subFileNumber].getSize()){
|
||||
streamOut.write(dectyptedBlock, skipBytes, (int) pfs0subFiles[subFileNumber].getSize()); // safe cast
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
return;
|
||||
}
|
||||
else
|
||||
streamOut.write(dectyptedBlock, skipBytes, 0x200 - skipBytes);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from 1st bock");
|
||||
return;
|
||||
}
|
||||
startBlock++;
|
||||
}
|
||||
long endBlock = pfs0subFiles[subFileNumber].getSize() / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
||||
|
||||
//----------------------------- Step 2: Detect if we have junk data on the end of the final block --------------------------------
|
||||
int extraData = (int)(rawBlockDataStart+pfs0subFiles[subFileNumber].getOffset()+pfs0subFiles[subFileNumber].getSize() - (endBlock*0x200)); // safe cast
|
||||
if (extraData < 0){
|
||||
endBlock--;
|
||||
}
|
||||
//----------------------------- Step 3: Read main part of data --------------------------------
|
||||
// Here we're reading main amount of bytes. We can read only less bytes.
|
||||
while ( startBlock < endBlock) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
//dectyptedBlock = aesCtr.decrypt(encryptedBlock);
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
// Writing decrypted data to pipe
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from bock");
|
||||
return;
|
||||
}
|
||||
startBlock++;
|
||||
}
|
||||
//----------------------------- Step 4: Read what's left --------------------------------
|
||||
// Now we have to find out if data overlaps to one more extra block
|
||||
if (extraData > 0){ // In case we didn't get what we want
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
streamOut.write(dectyptedBlock, 0, extraData);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from bock");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (extraData < 0){ // In case we can get more than we need
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
streamOut.write(dectyptedBlock, 0, 0x200 + extraData); // WTF ??? THIS LOOKS INCORRECT
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from last bock");
|
||||
return;
|
||||
}
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Thread died");
|
||||
|
||||
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception{
|
||||
for (int i = 0; i < pfs0subFiles.length; i++){
|
||||
if (pfs0subFiles[i].getName().equals(subFileName))
|
||||
return getProviderSubFilePipedInpStream(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
189
src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java
Normal file
189
src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java
Normal file
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.PFS0;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static libKonogonka.LoperConverter.*;
|
||||
|
||||
public class PFS0Provider implements IPFS0Provider{
|
||||
private long rawFileDataStart; // Where data starts, excluding header, string table etc.
|
||||
|
||||
private String magic;
|
||||
private int filesCount;
|
||||
private int stringTableSize;
|
||||
private byte[] padding;
|
||||
private PFS0subFile[] pfs0subFiles;
|
||||
|
||||
private File file;
|
||||
|
||||
public PFS0Provider(File fileWithPfs0) throws Exception{ this(fileWithPfs0, 0); }
|
||||
|
||||
public PFS0Provider(File fileWithPfs0, long pfs0offsetPosition) throws Exception{
|
||||
file = fileWithPfs0;
|
||||
RandomAccessFile raf = new RandomAccessFile(fileWithPfs0, "r"); // TODO: replace to bufferedInputStream
|
||||
|
||||
raf.seek(pfs0offsetPosition);
|
||||
byte[] fileStartingBytes = new byte[0x10];
|
||||
// Read PFS0Provider, files count, header, padding (4 zero bytes)
|
||||
if (raf.read(fileStartingBytes) != 0x10){
|
||||
raf.close();
|
||||
throw new Exception("PFS0Provider: Unable to read starting bytes");
|
||||
}
|
||||
// Check PFS0Provider
|
||||
magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII);
|
||||
if (! magic.equals("PFS0")){
|
||||
raf.close();
|
||||
throw new Exception("PFS0Provider: Bad magic");
|
||||
}
|
||||
// Get files count
|
||||
filesCount = getLEint(fileStartingBytes, 0x4);
|
||||
if (filesCount <= 0 ) {
|
||||
raf.close();
|
||||
throw new Exception("PFS0Provider: Files count is too small");
|
||||
}
|
||||
// Get string table
|
||||
stringTableSize = getLEint(fileStartingBytes, 0x8);
|
||||
if (stringTableSize <= 0 ){
|
||||
raf.close();
|
||||
throw new Exception("PFS0Provider: String table is too small");
|
||||
}
|
||||
padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10);
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
pfs0subFiles = new PFS0subFile[filesCount];
|
||||
|
||||
long[] offsetsSubFiles = new long[filesCount];
|
||||
long[] sizesSubFiles = new long[filesCount];
|
||||
int[] strTableOffsets = new int[filesCount];
|
||||
byte[][] zeroBytes = new byte[filesCount][];
|
||||
|
||||
byte[] fileEntryTable = new byte[0x18];
|
||||
for (int i=0; i<filesCount; i++){
|
||||
if (raf.read(fileEntryTable) != 0x18)
|
||||
throw new Exception("PFS0Provider: String table is too small");
|
||||
offsetsSubFiles[i] = getLElong(fileEntryTable, 0);
|
||||
sizesSubFiles[i] = getLElong(fileEntryTable, 0x8);
|
||||
strTableOffsets[i] = getLEint(fileEntryTable, 0x10);
|
||||
zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18);
|
||||
}
|
||||
//**********************************************************************************************************
|
||||
// In here pointer in front of String table
|
||||
String[] subFileNames = new String[filesCount];
|
||||
byte[] stringTbl = new byte[stringTableSize];
|
||||
if (raf.read(stringTbl) != stringTableSize){
|
||||
throw new Exception("Read PFS0Provider String table failure. Can't read requested string table size ("+stringTableSize+")");
|
||||
}
|
||||
|
||||
for (int i=0; i < filesCount; i++){
|
||||
int j = 0;
|
||||
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
|
||||
j++;
|
||||
subFileNames[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8);
|
||||
}
|
||||
for (int i = 0; i < filesCount; i++){
|
||||
pfs0subFiles[i] = new PFS0subFile(
|
||||
subFileNames[i],
|
||||
offsetsSubFiles[i],
|
||||
sizesSubFiles[i],
|
||||
zeroBytes[i]
|
||||
);
|
||||
}
|
||||
rawFileDataStart = raf.getFilePointer();
|
||||
raf.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncrypted() { return false; }
|
||||
@Override
|
||||
public String getMagic() { return magic; }
|
||||
@Override
|
||||
public int getFilesCount() { return filesCount; }
|
||||
@Override
|
||||
public int getStringTableSize() { return stringTableSize; }
|
||||
@Override
|
||||
public byte[] getPadding() { return padding; }
|
||||
@Override
|
||||
public long getRawFileDataStart() { return rawFileDataStart; }
|
||||
@Override
|
||||
public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; }
|
||||
@Override
|
||||
public File getFile(){ return file; }
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{ // TODO: Throw exceptions?
|
||||
if (subFileNumber >= pfs0subFiles.length) {
|
||||
throw new Exception("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists");
|
||||
}
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
Thread workerThread;
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Executing thread");
|
||||
try {
|
||||
long subFileRealPosition = rawFileDataStart + pfs0subFiles[subFileNumber].getOffset();
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
if (bis.skip(subFileRealPosition) != subFileRealPosition) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to skip requested offset");
|
||||
return;
|
||||
}
|
||||
|
||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
|
||||
long readFrom = 0;
|
||||
long realFileSize = pfs0subFiles[subFileNumber].getSize();
|
||||
|
||||
byte[] readBuf;
|
||||
|
||||
while (readFrom < realFileSize) {
|
||||
if (realFileSize - readFrom < readPice)
|
||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||
readBuf = new byte[readPice];
|
||||
if (bis.read(readBuf) != readPice) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to read requested size from file.");
|
||||
return;
|
||||
}
|
||||
streamOut.write(readBuf);
|
||||
readFrom += readPice;
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to provide stream");
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
/**
|
||||
* Some sugar
|
||||
* */
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {
|
||||
for (int i = 0; i < pfs0subFiles.length; i++){
|
||||
if (pfs0subFiles[i].getName().equals(subFileName))
|
||||
return getProviderSubFilePipedInpStream(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
38
src/main/java/libKonogonka/Tools/PFS0/PFS0subFile.java
Normal file
38
src/main/java/libKonogonka/Tools/PFS0/PFS0subFile.java
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.PFS0;
|
||||
|
||||
public class PFS0subFile {
|
||||
private String name;
|
||||
private long offset; // REAL in file (including offset in NCA/NSP file)
|
||||
private long size;
|
||||
private byte[] zeroes;
|
||||
|
||||
public PFS0subFile(String name, long offset, long size, byte[] zeroesInTable){
|
||||
this.name = name;
|
||||
this.offset = offset;
|
||||
this.size = size;
|
||||
this.zeroes = zeroesInTable;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public long getOffset() { return offset; }
|
||||
public long getSize() { return size; }
|
||||
public byte[] getZeroes() { return zeroes; }
|
||||
}
|
87
src/main/java/libKonogonka/Tools/RomFs/FileMeta4Debug.java
Normal file
87
src/main/java/libKonogonka/Tools/RomFs/FileMeta4Debug.java
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.RomFs;
|
||||
|
||||
import libKonogonka.LoperConverter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static libKonogonka.RainbowDump.formatDecHexString;
|
||||
|
||||
public class FileMeta4Debug {
|
||||
|
||||
List<FileMeta> allFiles;
|
||||
|
||||
FileMeta4Debug(long fileMetadataTableLength, byte[] fileMetadataTable) {
|
||||
allFiles = new ArrayList<>();
|
||||
int i = 0;
|
||||
while (i < fileMetadataTableLength) {
|
||||
FileMeta fileMeta = new FileMeta();
|
||||
fileMeta.containingDirectoryOffset = LoperConverter.getLEint(fileMetadataTable, i);
|
||||
i += 4;
|
||||
fileMeta.nextSiblingFileOffset = LoperConverter.getLEint(fileMetadataTable, i);
|
||||
i += 4;
|
||||
fileMeta.fileDataOffset = LoperConverter.getLElong(fileMetadataTable, i);
|
||||
i += 8;
|
||||
fileMeta.fileDataLength = LoperConverter.getLElong(fileMetadataTable, i);
|
||||
i += 8;
|
||||
fileMeta.nextFileOffset = LoperConverter.getLEint(fileMetadataTable, i);
|
||||
i += 4;
|
||||
fileMeta.fileNameLength = LoperConverter.getLEint(fileMetadataTable, i);
|
||||
i += 4;
|
||||
fileMeta.fileName = new String(Arrays.copyOfRange(fileMetadataTable, i, i + fileMeta.fileNameLength), StandardCharsets.UTF_8);
|
||||
;
|
||||
i += getRealNameSize(fileMeta.fileNameLength);
|
||||
|
||||
allFiles.add(fileMeta);
|
||||
}
|
||||
|
||||
for (FileMeta fileMeta : allFiles){
|
||||
System.out.println(
|
||||
"-------------------------FILE--------------------------------\n" +
|
||||
"Offset of Containing Directory " + formatDecHexString(fileMeta.containingDirectoryOffset) + "\n" +
|
||||
"Offset of next Sibling File " + formatDecHexString(fileMeta.nextSiblingFileOffset) + "\n" +
|
||||
"Offset of File's Data " + formatDecHexString(fileMeta.fileDataOffset) + "\n" +
|
||||
"Length of File's Data " + formatDecHexString(fileMeta.fileDataLength) + "\n" +
|
||||
"Offset of next File in the same Hash Table bucket " + formatDecHexString(fileMeta.nextFileOffset) + "\n" +
|
||||
"Name Length " + formatDecHexString(fileMeta.fileNameLength) + "\n" +
|
||||
"Name Length (rounded up to multiple of 4) " + fileMeta.fileName + "\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private int getRealNameSize(int value){
|
||||
if (value % 4 == 0)
|
||||
return value;
|
||||
return value + 4 - value % 4;
|
||||
}
|
||||
|
||||
private static class FileMeta{
|
||||
int containingDirectoryOffset;
|
||||
int nextSiblingFileOffset;
|
||||
long fileDataOffset;
|
||||
long fileDataLength;
|
||||
int nextFileOffset;
|
||||
int fileNameLength;
|
||||
String fileName;
|
||||
}
|
||||
}
|
192
src/main/java/libKonogonka/Tools/RomFs/FileSystemEntry.java
Normal file
192
src/main/java/libKonogonka/Tools/RomFs/FileSystemEntry.java
Normal file
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Copyright 2019-2020 Dmitry Isaenko
|
||||
*
|
||||
* This file is part of Konogonka.
|
||||
*
|
||||
* Konogonka 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.
|
||||
*
|
||||
* Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package libKonogonka.Tools.RomFs;
|
||||
|
||||
import libKonogonka.LoperConverter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class FileSystemEntry {
|
||||
private boolean directoryFlag;
|
||||
private String name;
|
||||
private List<FileSystemEntry> content;
|
||||
|
||||
private static byte[] dirsMetadataTable;
|
||||
private static byte[] filesMetadataTable;
|
||||
|
||||
private long fileOffset;
|
||||
private long fileSize;
|
||||
|
||||
public FileSystemEntry(byte[] dirsMetadataTable, byte[] filesMetadataTable) throws Exception{
|
||||
FileSystemEntry.dirsMetadataTable = dirsMetadataTable;
|
||||
FileSystemEntry.filesMetadataTable = filesMetadataTable;
|
||||
this.content = new ArrayList<>();
|
||||
this.directoryFlag = true;
|
||||
DirectoryMetaData rootDirectoryMetaData = new DirectoryMetaData();
|
||||
if (rootDirectoryMetaData.dirName.isEmpty())
|
||||
this.name = "ROOT";
|
||||
else
|
||||
this.name = rootDirectoryMetaData.dirName;
|
||||
if (rootDirectoryMetaData.parentDirectoryOffset != 0)
|
||||
throw new Exception("Offset of Parent Directory is incorrect. Expected 0 for root, received value is "+ rootDirectoryMetaData.parentDirectoryOffset);
|
||||
if (rootDirectoryMetaData.nextSiblingDirectoryOffset != -1)
|
||||
throw new Exception("Offset of next Sibling Directory is incorrect. Expected -1 for root, received value is "+ rootDirectoryMetaData.nextSiblingDirectoryOffset);
|
||||
if (rootDirectoryMetaData.firstSubdirectoryOffset != -1)
|
||||
content.add(getDirectory(rootDirectoryMetaData.firstSubdirectoryOffset));
|
||||
if (rootDirectoryMetaData.firstFileOffset != -1)
|
||||
content.add(getFile(this, rootDirectoryMetaData.firstFileOffset));
|
||||
content.sort(Comparator.comparingLong(FileSystemEntry::getFileOffset));
|
||||
}
|
||||
|
||||
private FileSystemEntry(){
|
||||
this.content = new ArrayList<>();
|
||||
}
|
||||
|
||||
private FileSystemEntry getDirectory(int childDirMetaPosition){
|
||||
FileSystemEntry fileSystemEntry = new FileSystemEntry();
|
||||
fileSystemEntry.directoryFlag = true;
|
||||
|
||||
DirectoryMetaData directoryMetaData = new DirectoryMetaData(childDirMetaPosition);
|
||||
fileSystemEntry.name = directoryMetaData.dirName;
|
||||
|
||||
if (directoryMetaData.nextSiblingDirectoryOffset != -1)
|
||||
this.content.add(getDirectory(directoryMetaData.nextSiblingDirectoryOffset));
|
||||
|
||||
if (directoryMetaData.firstSubdirectoryOffset != -1)
|
||||
fileSystemEntry.content.add(getDirectory(directoryMetaData.firstSubdirectoryOffset));
|
||||
|
||||
if (directoryMetaData.firstFileOffset != -1)
|
||||
fileSystemEntry.content.add(getFile(fileSystemEntry, directoryMetaData.firstFileOffset));
|
||||
|
||||
fileSystemEntry.content.sort(Comparator.comparingLong(FileSystemEntry::getFileOffset));
|
||||
|
||||
return fileSystemEntry;
|
||||
}
|
||||
|
||||
private FileSystemEntry getFile(FileSystemEntry directoryContainer, int childFileMetaPosition){
|
||||
FileSystemEntry fileSystemEntry = new FileSystemEntry();
|
||||
fileSystemEntry.directoryFlag = false;
|
||||
|
||||
FileMetaData fileMetaData = new FileMetaData(childFileMetaPosition);
|
||||
fileSystemEntry.name = fileMetaData.fileName;
|
||||
fileSystemEntry.fileOffset = fileMetaData.fileDataRealOffset;
|
||||
fileSystemEntry.fileSize = fileMetaData.fileDataRealLength;
|
||||
if (fileMetaData.nextSiblingFileOffset != -1)
|
||||
directoryContainer.content.add( getFile(directoryContainer, fileMetaData.nextSiblingFileOffset) );
|
||||
|
||||
return fileSystemEntry;
|
||||
}
|
||||
|
||||
public boolean isDirectory() { return directoryFlag; }
|
||||
public boolean isFile() { return ! directoryFlag; }
|
||||
public long getFileOffset() { return fileOffset; }
|
||||
public long getFileSize() { return fileSize; }
|
||||
public List<FileSystemEntry> getContent() { return content; }
|
||||
public String getName(){ return name; }
|
||||
|
||||
|
||||
private static class DirectoryMetaData {
|
||||
private int parentDirectoryOffset;
|
||||
private int nextSiblingDirectoryOffset;
|
||||
private int firstSubdirectoryOffset;
|
||||
private int firstFileOffset;
|
||||
|
||||
private String dirName;
|
||||
|
||||
private DirectoryMetaData(){
|
||||
this(0);
|
||||
}
|
||||
private DirectoryMetaData(int childDirMetaPosition){
|
||||
int i = childDirMetaPosition;
|
||||
parentDirectoryOffset = LoperConverter.getLEint(dirsMetadataTable, i);
|
||||
i += 4;
|
||||
nextSiblingDirectoryOffset = LoperConverter.getLEint(dirsMetadataTable, i);
|
||||
i += 4;
|
||||
firstSubdirectoryOffset = LoperConverter.getLEint(dirsMetadataTable, i);
|
||||
i += 4;
|
||||
firstFileOffset = LoperConverter.getLEint(dirsMetadataTable, i);
|
||||
i += 4;
|
||||
// int nextHashTableBucketDirectoryOffset = LoperConverter.getLEint(dirsMetadataTable, i);
|
||||
i += 4;
|
||||
int dirNameLength = LoperConverter.getLEint(dirsMetadataTable, i);
|
||||
i += 4;
|
||||
dirName = new String(Arrays.copyOfRange(dirsMetadataTable, i, i + dirNameLength), StandardCharsets.UTF_8);
|
||||
//i += getRealNameSize(dirNameLength);
|
||||
}
|
||||
|
||||
private int getRealNameSize(int value){
|
||||
if (value % 4 == 0)
|
||||
return value;
|
||||
return value + 4 - value % 4;
|
||||
}
|
||||
}
|
||||
private static class FileMetaData {
|
||||
|
||||
private int nextSiblingFileOffset;
|
||||
private long fileDataRealOffset;
|
||||
private long fileDataRealLength;
|
||||
|
||||
private String fileName;
|
||||
|
||||
private FileMetaData(){
|
||||
this(0);
|
||||
}
|
||||
|
||||
private FileMetaData(int childFileMetaPosition){
|
||||
int i = childFileMetaPosition;
|
||||
// int containingDirectoryOffset = LoperConverter.getLEint(filesMetadataTable, i); // never used
|
||||
i += 4;
|
||||
nextSiblingFileOffset = LoperConverter.getLEint(filesMetadataTable, i);
|
||||
i += 4;
|
||||
fileDataRealOffset = LoperConverter.getLElong(filesMetadataTable, i);
|
||||
i += 8;
|
||||
fileDataRealLength = LoperConverter.getLElong(filesMetadataTable, i);
|
||||
i += 8;
|
||||
//int nextHashTableBucketFileOffset = LoperConverter.getLEint(filesMetadataTable, i);
|
||||
i += 4;
|
||||
int fileNameLength = LoperConverter.getLEint(filesMetadataTable, i);
|
||||
i += 4;
|
||||
fileName = new String(Arrays.copyOfRange(filesMetadataTable, i, i + fileNameLength), StandardCharsets.UTF_8);;
|
||||
//i += getRealNameSize(fileNameLength);
|
||||
}
|
||||
}
|
||||
|
||||
public void printTreeForDebug(){
|
||||
System.out.println("/");
|
||||
for (FileSystemEntry entry: content)
|
||||
printEntry(2, entry);
|
||||
}
|
||||
private void printEntry(int cnt, FileSystemEntry entry) {
|
||||
for (int i = 0; i < cnt; i++)
|
||||
System.out.print(" ");
|
||||
|
||||
if (entry.isDirectory()){
|
||||
System.out.println("|-" + entry.getName());
|
||||
for (FileSystemEntry e : entry.content)
|
||||
printEntry(cnt+2, e);
|
||||
}
|
||||
else
|
||||
System.out.println("|-" + entry.getName() + String.format(" 0x%-10x 0x%-10x", entry.fileOffset, entry.fileSize));
|
||||
}
|
||||
}
|
84
src/main/java/libKonogonka/Tools/RomFs/FolderMeta4Debug.java
Normal file
84
src/main/java/libKonogonka/Tools/RomFs/FolderMeta4Debug.java
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.RomFs;
|
||||
|
||||
import libKonogonka.LoperConverter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static libKonogonka.RainbowDump.formatDecHexString;
|
||||
|
||||
public class FolderMeta4Debug {
|
||||
|
||||
List<FolderMeta> allFolders;
|
||||
|
||||
FolderMeta4Debug(long directoryMetadataTableLength, byte[] directoryMetadataTable){
|
||||
allFolders = new ArrayList<>();
|
||||
int i = 0;
|
||||
while (i < directoryMetadataTableLength){
|
||||
FolderMeta folderMeta = new FolderMeta();
|
||||
folderMeta.parentDirectoryOffset = LoperConverter.getLEint(directoryMetadataTable, i);
|
||||
i += 4;
|
||||
folderMeta.nextSiblingDirectoryOffset = LoperConverter.getLEint(directoryMetadataTable, i);
|
||||
i += 4;
|
||||
folderMeta.firstSubdirectoryOffset = LoperConverter.getLEint(directoryMetadataTable, i);
|
||||
i += 4;
|
||||
folderMeta.firstFileOffset = LoperConverter.getLEint(directoryMetadataTable, i);
|
||||
i += 4;
|
||||
folderMeta.nextDirectoryOffset = LoperConverter.getLEint(directoryMetadataTable, i);
|
||||
i += 4;
|
||||
folderMeta.dirNameLength = LoperConverter.getLEint(directoryMetadataTable, i);
|
||||
i += 4;
|
||||
folderMeta.dirName = new String(Arrays.copyOfRange(directoryMetadataTable, i, i + folderMeta.dirNameLength), StandardCharsets.UTF_8);
|
||||
i += getRealNameSize(folderMeta.dirNameLength);
|
||||
|
||||
System.out.println(
|
||||
"---------------------------DIRECTORY------------------------\n" +
|
||||
"Offset of Parent Directory (self if Root) " + formatDecHexString(folderMeta.parentDirectoryOffset ) +"\n" +
|
||||
"Offset of next Sibling Directory " + formatDecHexString(folderMeta.nextSiblingDirectoryOffset) +"\n" +
|
||||
"Offset of first Child Directory (Subdirectory) " + formatDecHexString(folderMeta.firstSubdirectoryOffset ) +"\n" +
|
||||
"Offset of first File (in File Metadata Table) " + formatDecHexString(folderMeta.firstFileOffset ) +"\n" +
|
||||
"Offset of next Directory in the same Hash Table bucket " + formatDecHexString(folderMeta.nextDirectoryOffset ) +"\n" +
|
||||
"Name Length " + formatDecHexString(folderMeta.dirNameLength ) +"\n" +
|
||||
"Name Length (rounded up to multiple of 4) " + folderMeta.dirName + "\n"
|
||||
);
|
||||
|
||||
allFolders.add(folderMeta);
|
||||
}
|
||||
}
|
||||
|
||||
private int getRealNameSize(int value){
|
||||
if (value % 4 == 0)
|
||||
return value;
|
||||
return value + 4 - value % 4;
|
||||
}
|
||||
|
||||
private static class FolderMeta {
|
||||
int parentDirectoryOffset;
|
||||
int nextSiblingDirectoryOffset;
|
||||
int firstSubdirectoryOffset;
|
||||
int firstFileOffset;
|
||||
int nextDirectoryOffset;
|
||||
int dirNameLength;
|
||||
String dirName;
|
||||
}
|
||||
}
|
31
src/main/java/libKonogonka/Tools/RomFs/IRomFsProvider.java
Normal file
31
src/main/java/libKonogonka/Tools/RomFs/IRomFsProvider.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2019-2020 Dmitry Isaenko
|
||||
*
|
||||
* This file is part of Konogonka.
|
||||
*
|
||||
* Konogonka 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.
|
||||
*
|
||||
* Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package libKonogonka.Tools.RomFs;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PipedInputStream;
|
||||
|
||||
public interface IRomFsProvider {
|
||||
long getLevel6Offset();
|
||||
Level6Header getHeader();
|
||||
FileSystemEntry getRootEntry();
|
||||
PipedInputStream getContent(FileSystemEntry entry) throws Exception;
|
||||
File getFile();
|
||||
}
|
92
src/main/java/libKonogonka/Tools/RomFs/Level6Header.java
Normal file
92
src/main/java/libKonogonka/Tools/RomFs/Level6Header.java
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright 2019-2020 Dmitry Isaenko
|
||||
*
|
||||
* This file is part of Konogonka.
|
||||
*
|
||||
* Konogonka 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.
|
||||
*
|
||||
* Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package libKonogonka.Tools.RomFs;
|
||||
|
||||
import libKonogonka.LoperConverter;
|
||||
import libKonogonka.RainbowDump;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Level6Header {
|
||||
private long headerLength;
|
||||
private long directoryHashTableOffset;
|
||||
private long directoryHashTableLength;
|
||||
private long directoryMetadataTableOffset;
|
||||
private long directoryMetadataTableLength;
|
||||
private long fileHashTableOffset;
|
||||
private long fileHashTableLength;
|
||||
private long fileMetadataTableOffset;
|
||||
private long fileMetadataTableLength;
|
||||
private long fileDataOffset;
|
||||
|
||||
private byte[] headerBytes;
|
||||
private int i;
|
||||
|
||||
Level6Header(byte[] headerBytes) throws Exception{
|
||||
this.headerBytes = headerBytes;
|
||||
if (headerBytes.length < 0x50)
|
||||
throw new Exception("Level 6 Header section is too small");
|
||||
headerLength = getNext();
|
||||
directoryHashTableOffset = getNext();
|
||||
directoryHashTableOffset <<= 32;
|
||||
directoryHashTableLength = getNext();
|
||||
directoryMetadataTableOffset = getNext();
|
||||
directoryMetadataTableLength = getNext();
|
||||
fileHashTableOffset = getNext();
|
||||
fileHashTableLength = getNext();
|
||||
fileMetadataTableOffset = getNext();
|
||||
fileMetadataTableLength = getNext();
|
||||
fileDataOffset = getNext();
|
||||
RainbowDump.hexDumpUTF8(Arrays.copyOfRange(headerBytes, 0, 0x50));
|
||||
}
|
||||
|
||||
private long getNext(){
|
||||
final long result = LoperConverter.getLEint(headerBytes, i);
|
||||
i += 0x8;
|
||||
return result;
|
||||
}
|
||||
|
||||
public long getHeaderLength() { return headerLength; }
|
||||
public long getDirectoryHashTableOffset() { return directoryHashTableOffset; }
|
||||
public long getDirectoryHashTableLength() { return directoryHashTableLength; }
|
||||
public long getDirectoryMetadataTableOffset() { return directoryMetadataTableOffset; }
|
||||
public long getDirectoryMetadataTableLength() { return directoryMetadataTableLength; }
|
||||
public long getFileHashTableOffset() { return fileHashTableOffset; }
|
||||
public long getFileHashTableLength() { return fileHashTableLength; }
|
||||
public long getFileMetadataTableOffset() { return fileMetadataTableOffset; }
|
||||
public long getFileMetadataTableLength() { return fileMetadataTableLength; }
|
||||
public long getFileDataOffset() { return fileDataOffset; }
|
||||
|
||||
public void printDebugInfo(){
|
||||
System.out.println("== Level 6 Header ==\n" +
|
||||
"Header Length (always 0x50 ?) "+ RainbowDump.formatDecHexString(headerLength)+" (size of this structure within first 0x200 block of LEVEL 6 part)\n" +
|
||||
"Directory Hash Table Offset "+ RainbowDump.formatDecHexString(directoryHashTableOffset)+" (against THIS block where HEADER contains)\n" +
|
||||
"Directory Hash Table Length "+ RainbowDump.formatDecHexString(directoryHashTableLength) + "\n" +
|
||||
"Directory Metadata Table Offset "+ RainbowDump.formatDecHexString(directoryMetadataTableOffset) + "\n" +
|
||||
"Directory Metadata Table Length "+ RainbowDump.formatDecHexString(directoryMetadataTableLength) + "\n" +
|
||||
"File Hash Table Offset "+ RainbowDump.formatDecHexString(fileHashTableOffset) + "\n" +
|
||||
"File Hash Table Length "+ RainbowDump.formatDecHexString(fileHashTableLength) + "\n" +
|
||||
"File Metadata Table Offset "+ RainbowDump.formatDecHexString(fileMetadataTableOffset) + "\n" +
|
||||
"File Metadata Table Length "+ RainbowDump.formatDecHexString(fileMetadataTableLength) + "\n" +
|
||||
"File Data Offset "+ RainbowDump.formatDecHexString(fileDataOffset) + "\n" +
|
||||
"-------------------------------------------------------------"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright 2019-2020 Dmitry Isaenko
|
||||
*
|
||||
* This file is part of Konogonka.
|
||||
*
|
||||
* Konogonka 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.
|
||||
*
|
||||
* Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package libKonogonka.Tools.RomFs;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class RomFsDecryptedProvider implements IRomFsProvider{
|
||||
|
||||
private long level6Offset;
|
||||
|
||||
private File file;
|
||||
private Level6Header header;
|
||||
|
||||
private FileSystemEntry rootEntry;
|
||||
|
||||
public RomFsDecryptedProvider(File decryptedFsImageFile, long level6Offset) throws Exception{
|
||||
if (level6Offset < 0)
|
||||
throw new Exception("Incorrect Level 6 Offset");
|
||||
|
||||
this.file = decryptedFsImageFile;
|
||||
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(decryptedFsImageFile));
|
||||
|
||||
this.level6Offset = level6Offset;
|
||||
|
||||
skipBytes(bis, level6Offset);
|
||||
|
||||
byte[] rawDataChunk = new byte[0x50];
|
||||
|
||||
if (bis.read(rawDataChunk) != 0x50)
|
||||
throw new Exception("Failed to read header (0x50)");
|
||||
|
||||
this.header = new Level6Header(rawDataChunk);
|
||||
/*
|
||||
// Print Dir Hash table as is:
|
||||
long seekTo = header.getDirectoryHashTableOffset() - 0x50;
|
||||
rawDataChunk = new byte[(int) header.getDirectoryHashTableLength()];
|
||||
skipTo(bis, seekTo);
|
||||
if (bis.read(rawDataChunk) != (int) header.getDirectoryHashTableLength())
|
||||
throw new Exception("Failed to read Dir hash table");
|
||||
RainbowDump.hexDumpUTF8(rawDataChunk);
|
||||
// Print Files Hash table as is:
|
||||
seekTo = header.getFileHashTableOffset() - header.getDirectoryMetadataTableOffset();
|
||||
rawDataChunk = new byte[(int) header.getFileHashTableLength()];
|
||||
skipTo(bis, seekTo);
|
||||
if (bis.read(rawDataChunk) != (int) header.getFileHashTableLength())
|
||||
throw new Exception("Failed to read Files hash table");
|
||||
RainbowDump.hexDumpUTF8(rawDataChunk);
|
||||
*/
|
||||
// Read directories metadata
|
||||
long locationInFile = header.getDirectoryMetadataTableOffset() - 0x50;
|
||||
|
||||
skipBytes(bis, locationInFile);
|
||||
|
||||
if (header.getDirectoryMetadataTableLength() < 0)
|
||||
throw new Exception("Not supported operation.");
|
||||
|
||||
byte[] directoryMetadataTable = new byte[(int) header.getDirectoryMetadataTableLength()];
|
||||
|
||||
if (bis.read(directoryMetadataTable) != (int) header.getDirectoryMetadataTableLength())
|
||||
throw new Exception("Failed to read "+header.getDirectoryMetadataTableLength());
|
||||
// Read files metadata
|
||||
locationInFile = header.getFileMetadataTableOffset() - header.getFileHashTableOffset(); // TODO: replace to 'CurrentPosition'?
|
||||
|
||||
skipBytes(bis, locationInFile);
|
||||
|
||||
if (header.getFileMetadataTableLength() < 0)
|
||||
throw new Exception("Not supported operation.");
|
||||
|
||||
byte[] fileMetadataTable = new byte[(int) header.getFileMetadataTableLength()];
|
||||
|
||||
if (bis.read(fileMetadataTable) != (int) header.getFileMetadataTableLength())
|
||||
throw new Exception("Failed to read "+header.getFileMetadataTableLength());
|
||||
|
||||
rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable);
|
||||
//printDebug(directoryMetadataTable, fileMetadataTable);
|
||||
bis.close();
|
||||
}
|
||||
private void skipBytes(BufferedInputStream bis, long size) throws Exception{
|
||||
long mustSkip = size;
|
||||
long skipped = 0;
|
||||
while (mustSkip > 0){
|
||||
skipped += bis.skip(mustSkip);
|
||||
mustSkip = size - skipped;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public long getLevel6Offset() { return level6Offset; }
|
||||
@Override
|
||||
public Level6Header getHeader() { return header; }
|
||||
@Override
|
||||
public FileSystemEntry getRootEntry() { return rootEntry; }
|
||||
@Override
|
||||
public PipedInputStream getContent(FileSystemEntry entry) throws Exception{
|
||||
if (entry.isDirectory())
|
||||
throw new Exception("Request of the binary stream for the folder entry doesn't make sense.");
|
||||
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
Thread workerThread;
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Executing thread");
|
||||
try {
|
||||
long subFileRealPosition = level6Offset + header.getFileDataOffset() + entry.getFileOffset();
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
skipBytes(bis, subFileRealPosition);
|
||||
|
||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
|
||||
long readFrom = 0;
|
||||
long realFileSize = entry.getFileSize();
|
||||
|
||||
byte[] readBuf;
|
||||
|
||||
while (readFrom < realFileSize) {
|
||||
if (realFileSize - readFrom < readPice)
|
||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||
readBuf = new byte[readPice];
|
||||
if (bis.read(readBuf) != readPice) {
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to read requested size from file.");
|
||||
return;
|
||||
}
|
||||
streamOut.write(readBuf);
|
||||
readFrom += readPice;
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
} catch (Exception e) {
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to provide stream");
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Thread is dead");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
@Override
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
private void printDebug(byte[] directoryMetadataTable, byte[] fileMetadataTable){
|
||||
new FolderMeta4Debug(header.getDirectoryMetadataTableLength(), directoryMetadataTable);
|
||||
new FileMeta4Debug(header.getFileMetadataTableLength(), fileMetadataTable);
|
||||
rootEntry.printTreeForDebug();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
* Copyright 2019-2020 Dmitry Isaenko
|
||||
*
|
||||
* This file is part of Konogonka.
|
||||
*
|
||||
* Konogonka 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.
|
||||
*
|
||||
* Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package libKonogonka.Tools.RomFs;
|
||||
|
||||
import libKonogonka.ctraes.AesCtrDecryptSimple;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class RomFsEncryptedProvider implements IRomFsProvider{
|
||||
|
||||
private long level6Offset;
|
||||
|
||||
private File file;
|
||||
private Level6Header header;
|
||||
|
||||
private FileSystemEntry rootEntry;
|
||||
|
||||
//--------------------------------
|
||||
|
||||
private long romFSoffsetPosition;
|
||||
private byte[] key;
|
||||
private byte[] sectionCTR;
|
||||
private long mediaStartOffset;
|
||||
private long mediaEndOffset;
|
||||
|
||||
public RomFsEncryptedProvider(long romFSoffsetPosition,
|
||||
long level6Offset,
|
||||
File fileWithEncPFS0,
|
||||
byte[] key,
|
||||
byte[] sectionCTR,
|
||||
long mediaStartOffset,
|
||||
long mediaEndOffset
|
||||
) throws Exception{
|
||||
this.file = fileWithEncPFS0;
|
||||
this.level6Offset = level6Offset;
|
||||
this.romFSoffsetPosition = romFSoffsetPosition;
|
||||
this.key = key;
|
||||
this.sectionCTR = sectionCTR;
|
||||
this.mediaStartOffset = mediaStartOffset;
|
||||
this.mediaEndOffset = mediaEndOffset;
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
long abosluteOffsetPosition = romFSoffsetPosition + (mediaStartOffset * 0x200);
|
||||
raf.seek(abosluteOffsetPosition + level6Offset);
|
||||
|
||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||
//Go to Level 6 header
|
||||
decryptor.skipNext(level6Offset / 0x200);
|
||||
|
||||
// Decrypt data
|
||||
byte[] encryptedBlock = new byte[0x200];
|
||||
byte[] dectyptedBlock;
|
||||
if (raf.read(encryptedBlock) == 0x200)
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
else
|
||||
throw new Exception("Failed to read header header (0x200 - block)");
|
||||
|
||||
this.header = new Level6Header(dectyptedBlock);
|
||||
|
||||
header.printDebugInfo();
|
||||
|
||||
if (header.getDirectoryMetadataTableLength() < 0)
|
||||
throw new Exception("Not supported: DirectoryMetadataTableLength < 0");
|
||||
|
||||
if (header.getFileMetadataTableLength() < 0)
|
||||
throw new Exception("Not supported: FileMetadataTableLength < 0");
|
||||
|
||||
/*---------------------------------*/
|
||||
|
||||
// Read directories metadata
|
||||
byte[] directoryMetadataTable = readMetaTable(abosluteOffsetPosition,
|
||||
header.getDirectoryMetadataTableOffset(),
|
||||
header.getDirectoryMetadataTableLength(),
|
||||
raf);
|
||||
|
||||
// Read files metadata
|
||||
byte[] fileMetadataTable = readMetaTable(abosluteOffsetPosition,
|
||||
header.getFileMetadataTableOffset(),
|
||||
header.getFileMetadataTableLength(),
|
||||
raf);
|
||||
|
||||
rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable);
|
||||
|
||||
raf.close();
|
||||
}
|
||||
|
||||
private byte[] readMetaTable(long abosluteOffsetPosition,
|
||||
long metaOffset,
|
||||
long metaSize,
|
||||
RandomAccessFile raf) throws Exception{
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
byte[] metadataTable = new byte[(int) metaSize];
|
||||
//0
|
||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||
|
||||
long startBlock = metaOffset / 0x200;
|
||||
|
||||
decryptor.skipNext(level6Offset / 0x200 + startBlock);
|
||||
|
||||
raf.seek(abosluteOffsetPosition + level6Offset + startBlock * 0x200);
|
||||
|
||||
//1
|
||||
long ignoreBytes = metaOffset - startBlock * 0x200;
|
||||
long currentPosition = 0;
|
||||
|
||||
if (ignoreBytes > 0) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
// If we have extra-small file that is less then a block and even more
|
||||
if ((0x200 - ignoreBytes) > metaSize){
|
||||
metadataTable = Arrays.copyOfRange(dectyptedBlock, (int)ignoreBytes, 0x200);
|
||||
return metadataTable;
|
||||
}
|
||||
else {
|
||||
System.arraycopy(dectyptedBlock, (int) ignoreBytes, metadataTable, 0, 0x200 - (int) ignoreBytes);
|
||||
currentPosition = 0x200 - ignoreBytes;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from 1st bock for Directory Metadata Table");
|
||||
}
|
||||
startBlock++;
|
||||
}
|
||||
long endBlock = (metaSize + ignoreBytes) / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
||||
|
||||
//2
|
||||
int extraData = (int) ((endBlock - startBlock)*0x200 - (metaSize + ignoreBytes));
|
||||
|
||||
if (extraData < 0)
|
||||
endBlock--;
|
||||
//3
|
||||
while ( startBlock < endBlock ) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
System.arraycopy(dectyptedBlock, 0, metadataTable, (int) currentPosition, 0x200);
|
||||
}
|
||||
else
|
||||
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
||||
|
||||
startBlock++;
|
||||
currentPosition += 0x200;
|
||||
}
|
||||
|
||||
//4
|
||||
if (extraData != 0){ // In case we didn't get what we want
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
System.arraycopy(dectyptedBlock, 0, metadataTable, (int) currentPosition, Math.abs(extraData));
|
||||
}
|
||||
else
|
||||
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
||||
}
|
||||
|
||||
return metadataTable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLevel6Offset() { return level6Offset; }
|
||||
@Override
|
||||
public Level6Header getHeader() { return header; }
|
||||
@Override
|
||||
public FileSystemEntry getRootEntry() { return rootEntry; }
|
||||
@Override
|
||||
public PipedInputStream getContent(FileSystemEntry entry) throws Exception{
|
||||
if (entry.isDirectory())
|
||||
throw new Exception("Request of the binary stream for the folder entry doesn't make sense.");
|
||||
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
Thread workerThread;
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Executing thread");
|
||||
try {
|
||||
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
|
||||
//0
|
||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||
|
||||
long startBlock = (entry.getFileOffset() + header.getFileDataOffset()) / 0x200;
|
||||
|
||||
decryptor.skipNext(level6Offset / 0x200 + startBlock);
|
||||
|
||||
long abosluteOffsetPosition = romFSoffsetPosition + (mediaStartOffset * 0x200);
|
||||
|
||||
raf.seek(abosluteOffsetPosition + level6Offset + startBlock * 0x200);
|
||||
|
||||
//1
|
||||
long ignoreBytes = (entry.getFileOffset() + header.getFileDataOffset()) - startBlock * 0x200;
|
||||
|
||||
if (ignoreBytes > 0) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
// If we have extra-small file that is less then a block and even more
|
||||
if ((0x200 - ignoreBytes) > entry.getFileSize()){
|
||||
streamOut.write(dectyptedBlock, (int)ignoreBytes, (int) entry.getFileSize()); // safe cast
|
||||
raf.close();
|
||||
streamOut.close();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
streamOut.write(dectyptedBlock, (int) ignoreBytes, 0x200 - (int) ignoreBytes);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from 1st bock for Directory Metadata Table");
|
||||
}
|
||||
startBlock++;
|
||||
}
|
||||
long endBlock = (entry.getFileSize() + ignoreBytes) / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
||||
|
||||
//2
|
||||
int extraData = (int) ((endBlock - startBlock)*0x200 - (entry.getFileSize() + ignoreBytes));
|
||||
|
||||
if (extraData < 0)
|
||||
endBlock--;
|
||||
//3
|
||||
while ( startBlock < endBlock ) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
else
|
||||
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
||||
|
||||
startBlock++;
|
||||
}
|
||||
|
||||
//4
|
||||
if (extraData != 0){ // In case we didn't get what we want
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
streamOut.write(dectyptedBlock, 0, Math.abs(extraData));
|
||||
}
|
||||
else
|
||||
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
||||
}
|
||||
raf.close();
|
||||
streamOut.close();
|
||||
} catch (Exception e) {
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to provide stream");
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Thread is dead");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
private void printDebug(byte[] directoryMetadataTable, byte[] fileMetadataTable){
|
||||
new FolderMeta4Debug(header.getDirectoryMetadataTableLength(), directoryMetadataTable);
|
||||
new FileMeta4Debug(header.getFileMetadataTableLength(), fileMetadataTable);
|
||||
rootEntry.printTreeForDebug();
|
||||
}
|
||||
}
|
181
src/main/java/libKonogonka/Tools/TIK/TIKProvider.java
Normal file
181
src/main/java/libKonogonka/Tools/TIK/TIKProvider.java
Normal file
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.TIK;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static libKonogonka.LoperConverter.*;
|
||||
/*
|
||||
|
||||
DON'T TRUST WIKI. Ticket size always (?) equal 0x02c0 (704 bytes)
|
||||
|
||||
File structure byte-by-byte parsing
|
||||
|
||||
Starting:
|
||||
0x4 - Signature type
|
||||
Signature type == 00 00 01 00 ?
|
||||
Next:
|
||||
0x200 - Signature size
|
||||
Signature type == 01 00 01 00 ?
|
||||
0x100 - Signature size
|
||||
Signature type == 02 00 01 00 ?
|
||||
0x3c - Signature size
|
||||
Signature type == 03 00 01 00 ?
|
||||
0x200 - Signature size
|
||||
Signature type == 04 00 01 00 ?
|
||||
0x100 - Signature size
|
||||
Signature type == 05 00 01 00 ?
|
||||
0x3c - Signature size
|
||||
Next:
|
||||
Signature type == 01 00 01 00 ?
|
||||
0x3c - padding
|
||||
Signature type == 01 00 01 00 ?
|
||||
0x3c - padding
|
||||
Signature type == 02 00 01 00 ?
|
||||
0x40 - padding
|
||||
Signature type == 03 00 01 00 ?
|
||||
0c3c - padding
|
||||
Signature type == 04 00 01 00 ?
|
||||
0c3c - padding
|
||||
Signature type == 05 00 01 00 ?
|
||||
0x40 - padding
|
||||
Next:
|
||||
0x02c0 - Signature data ????? WTF? MUST BE AND IMPLEMENTED AS 0x180
|
||||
*/
|
||||
/**
|
||||
* TIKProvider is not a container, thus not a content-provider but provider of values-only
|
||||
* */
|
||||
public class TIKProvider {
|
||||
// Signature-related
|
||||
private byte[] sigType;
|
||||
private byte[] signature;
|
||||
// Ticket
|
||||
private String Issuer;
|
||||
private byte[] TitleKeyBlockStartingBytes; // Actually 32 bytes.
|
||||
private byte[] TitleKeyBlockEndingBytes; // Anything else
|
||||
private byte Unknown1;
|
||||
private byte TitleKeyType;
|
||||
private byte[] Unknown2;
|
||||
private byte MasterKeyRevision;
|
||||
private byte[] Unknown3;
|
||||
private byte[] TicketId;
|
||||
private byte[] DeviceId;
|
||||
private byte[] RightsId;
|
||||
private byte[] RightsIdEndingBytes;
|
||||
private byte[] AccountId;
|
||||
private byte[] Unknown4;
|
||||
|
||||
public TIKProvider(File file) throws Exception{ this(file, 0); }
|
||||
|
||||
public TIKProvider(File file, long offset) throws Exception {
|
||||
|
||||
if (file.length() - offset < 0x02c0)
|
||||
throw new Exception("TIKProvider: File is too small.");
|
||||
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
if (bis.skip(offset) != offset) {
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unable to skip requested range - " + offset);
|
||||
}
|
||||
|
||||
sigType = new byte[0x4];
|
||||
if (bis.read(sigType) != 4) {
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unable to read requested range - " + offset);
|
||||
}
|
||||
|
||||
byte[] readChunk;
|
||||
|
||||
switch (getLEint(sigType, 0)){
|
||||
case 65536: // RSA_4096 SHA1
|
||||
case 65539: // RSA_4096 SHA256
|
||||
readChunk = new byte[0x23c];
|
||||
if (bis.read(readChunk) != 0x23c) {
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unable to read requested range - 0x23c");
|
||||
}
|
||||
signature = Arrays.copyOfRange(readChunk, 0, 0x200);
|
||||
break;
|
||||
case 65537: // RSA_2048 SHA1
|
||||
case 65540: // RSA_2048 SHA256
|
||||
readChunk = new byte[0x13c];
|
||||
if (bis.read(readChunk) != 0x13c) {
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unable to read requested range - 0x13c");
|
||||
}
|
||||
signature = Arrays.copyOfRange(readChunk, 0, 0x100);
|
||||
break;
|
||||
case 65538: // ECDSA SHA1
|
||||
case 65541: // ECDSA SHA256
|
||||
readChunk = new byte[0x7c];
|
||||
if (bis.read(readChunk) != 0x7c) {
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unable to read requested range - 0x7c");
|
||||
}
|
||||
signature = Arrays.copyOfRange(readChunk, 0, 0x3c);
|
||||
break;
|
||||
default:
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unknown ticket (Signature) type. Aborting.");
|
||||
}
|
||||
// Let's read ticket body itself
|
||||
readChunk = new byte[0x180];
|
||||
|
||||
if (bis.read(readChunk) != 0x180) {
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unable to read requested range - Ticket data");
|
||||
}
|
||||
bis.close();
|
||||
|
||||
Issuer = new String(readChunk, 0, 0x40, StandardCharsets.UTF_8);
|
||||
TitleKeyBlockStartingBytes = Arrays.copyOfRange(readChunk, 0x40, 0x50);
|
||||
TitleKeyBlockEndingBytes = Arrays.copyOfRange(readChunk, 0x50, 0x140);
|
||||
Unknown1 = readChunk[0x140];
|
||||
TitleKeyType = readChunk[0x141];
|
||||
Unknown2 = Arrays.copyOfRange(readChunk, 0x142, 0x145);
|
||||
MasterKeyRevision = readChunk[0x145];
|
||||
Unknown3 = Arrays.copyOfRange(readChunk, 0x146, 0x150);
|
||||
TicketId = Arrays.copyOfRange(readChunk, 0x150, 0x158);
|
||||
DeviceId = Arrays.copyOfRange(readChunk, 0x158, 0x160);
|
||||
RightsId = Arrays.copyOfRange(readChunk, 0x160, 0x170);
|
||||
AccountId = Arrays.copyOfRange(readChunk,0x170, 0x174);
|
||||
Unknown4 = Arrays.copyOfRange(readChunk, 0x174, 0x180);
|
||||
}
|
||||
|
||||
public byte[] getSigType() { return sigType; }
|
||||
public byte[] getSignature() { return signature; }
|
||||
|
||||
public String getIssuer() { return Issuer; }
|
||||
public byte[] getTitleKeyBlockStartingBytes() { return TitleKeyBlockStartingBytes; }
|
||||
public byte[] getTitleKeyBlockEndingBytes() { return TitleKeyBlockEndingBytes; }
|
||||
public byte getUnknown1() { return Unknown1; }
|
||||
public byte getTitleKeyType() { return TitleKeyType; }
|
||||
public byte[] getUnknown2() { return Unknown2; }
|
||||
public byte getMasterKeyRevision() { return MasterKeyRevision; }
|
||||
public byte[] getUnknown3() { return Unknown3; }
|
||||
public byte[] getTicketId() { return TicketId; }
|
||||
public byte[] getDeviceId() { return DeviceId; }
|
||||
public byte[] getRightsId() { return RightsId; }
|
||||
public byte[] getAccountId() { return AccountId; }
|
||||
public byte[] getUnknown4() { return Unknown4; }
|
||||
}
|
44
src/main/java/libKonogonka/Tools/XCI/HFS0File.java
Normal file
44
src/main/java/libKonogonka/Tools/XCI/HFS0File.java
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.XCI;
|
||||
|
||||
public class HFS0File {
|
||||
private String name;
|
||||
private long offset;
|
||||
private long size;
|
||||
private long hashedRegionSize;
|
||||
private boolean padding;
|
||||
private byte[] SHA256Hash;
|
||||
|
||||
public HFS0File(String name, long offset, long size, long hashedRegionSize, boolean padding, byte[] SHA256Hash){
|
||||
this.name = name;
|
||||
this.offset = offset;
|
||||
this.size = size;
|
||||
this.hashedRegionSize = hashedRegionSize;
|
||||
this.padding = padding;
|
||||
this.SHA256Hash = SHA256Hash;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public long getOffset() { return offset; }
|
||||
public long getSize() { return size; }
|
||||
public long getHashedRegionSize() { return hashedRegionSize; }
|
||||
public boolean isPadding() { return padding; }
|
||||
public byte[] getSHA256Hash() { return SHA256Hash; }
|
||||
}
|
190
src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java
Normal file
190
src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java
Normal file
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.XCI;
|
||||
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static libKonogonka.LoperConverter.*;
|
||||
|
||||
/**
|
||||
* HFS0
|
||||
* */
|
||||
public class HFS0Provider implements ISuperProvider {
|
||||
|
||||
private boolean magicHFS0;
|
||||
private int filesCnt;
|
||||
private boolean paddingHfs0;
|
||||
private int stringTableSize;
|
||||
private long rawFileDataStart;
|
||||
|
||||
private HFS0File[] hfs0Files;
|
||||
|
||||
private File file;
|
||||
|
||||
HFS0Provider(long hfsOffsetPosition, RandomAccessFile raf, File file) throws Exception{
|
||||
this.file = file; // Will be used @ getHfs0FilePipedInpStream. It's a bad implementation.
|
||||
byte[] hfs0bytes = new byte[16];
|
||||
try{
|
||||
raf.seek(hfsOffsetPosition);
|
||||
if (raf.read(hfs0bytes) != 16){
|
||||
throw new Exception("Read HFS0 structure failure. Can't read first 16 bytes on requested offset.");
|
||||
}
|
||||
}
|
||||
catch (IOException ioe){
|
||||
throw new Exception("Read HFS0 structure failure. Can't read first 16 bytes on requested offset: "+ioe.getMessage());
|
||||
}
|
||||
magicHFS0 = Arrays.equals(Arrays.copyOfRange(hfs0bytes, 0, 4),new byte[]{0x48, 0x46, 0x53, 0x30});
|
||||
filesCnt = getLEint(hfs0bytes, 0x4);
|
||||
stringTableSize = getLEint(hfs0bytes, 8);
|
||||
paddingHfs0 = Arrays.equals(Arrays.copyOfRange(hfs0bytes, 12, 16),new byte[4]);
|
||||
|
||||
hfs0Files = new HFS0File[filesCnt];
|
||||
|
||||
// TODO: IF NOT EMPTY TABLE:
|
||||
|
||||
long[] offsetHfs0files = new long[filesCnt];
|
||||
long[] sizeHfs0files = new long[filesCnt];
|
||||
int[] hashedRegionSizeHfs0Files = new int[filesCnt];
|
||||
boolean[] paddingHfs0Files = new boolean[filesCnt];
|
||||
byte[][] SHA256HashHfs0Files = new byte[filesCnt][];
|
||||
int[] strTableOffsets = new int[filesCnt];
|
||||
|
||||
try {
|
||||
// Populate meta information regarding each file inside (?) HFS0
|
||||
byte[] metaInfoBytes = new byte[64];
|
||||
for (int i=0; i < filesCnt; i++){
|
||||
if (raf.read(metaInfoBytes) != 64) {
|
||||
throw new Exception("Read HFS0 File Entry Table failure for file # "+i);
|
||||
}
|
||||
offsetHfs0files[i] = getLElong(metaInfoBytes, 0);
|
||||
sizeHfs0files[i] = getLElong(metaInfoBytes, 8);
|
||||
hashedRegionSizeHfs0Files[i] = getLEint(metaInfoBytes, 20);
|
||||
paddingHfs0Files[i] = Arrays.equals(Arrays.copyOfRange(metaInfoBytes, 24, 32), new byte[8]);
|
||||
SHA256HashHfs0Files[i] = Arrays.copyOfRange(metaInfoBytes, 32, 64);
|
||||
|
||||
strTableOffsets[i] = getLEint(metaInfoBytes, 16);
|
||||
}
|
||||
// Define location of actual data for this HFS0
|
||||
rawFileDataStart = raf.getFilePointer()+stringTableSize;
|
||||
if (stringTableSize <= 0)
|
||||
throw new Exception("String table size of HFS0 less or equal to zero");
|
||||
byte[] stringTbl = new byte[stringTableSize];
|
||||
if (raf.read(stringTbl) != stringTableSize){
|
||||
throw new Exception("Read HFS0 String table failure. Can't read requested string table size ("+stringTableSize+")");
|
||||
}
|
||||
String[] namesHfs0files = new String[filesCnt];
|
||||
// Parse string table
|
||||
for (int i=0; i < filesCnt; i++){
|
||||
int j = 0;
|
||||
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
|
||||
j++;
|
||||
namesHfs0files[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
// Set files
|
||||
for (int i=0; i < filesCnt; i++){
|
||||
hfs0Files[i] = new HFS0File(
|
||||
namesHfs0files[i],
|
||||
offsetHfs0files[i],
|
||||
sizeHfs0files[i],
|
||||
hashedRegionSizeHfs0Files[i],
|
||||
paddingHfs0Files[i],
|
||||
SHA256HashHfs0Files[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (IOException ioe){
|
||||
throw new Exception("Read HFS0 structure failure: "+ioe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMagicHFS0() { return magicHFS0; }
|
||||
public int getFilesCnt() { return filesCnt; }
|
||||
public boolean isPaddingHfs0() { return paddingHfs0; }
|
||||
public int getStringTableSize() { return stringTableSize; }
|
||||
@Override
|
||||
public long getRawFileDataStart() { return rawFileDataStart; }
|
||||
public HFS0File[] getHfs0Files() { return hfs0Files; }
|
||||
@Override
|
||||
public File getFile(){ return file; }
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
Thread workerThread;
|
||||
if (subFileNumber >= hfs0Files.length) {
|
||||
throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Requested sub file doesn't exists");
|
||||
}
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Executing thread");
|
||||
try{
|
||||
long subFileRealPosition = rawFileDataStart + hfs0Files[subFileNumber].getOffset();
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
if (bis.skip(subFileRealPosition) != subFileRealPosition) {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to skip requested offset");
|
||||
return;
|
||||
}
|
||||
|
||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
|
||||
long readFrom = 0;
|
||||
long realFileSize = hfs0Files[subFileNumber].getSize();
|
||||
|
||||
byte[] readBuf;
|
||||
|
||||
while (readFrom < realFileSize){
|
||||
if (realFileSize - readFrom < readPice)
|
||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||
readBuf = new byte[readPice];
|
||||
if (bis.read(readBuf) != readPice) {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to read requested size from file.");
|
||||
return;
|
||||
}
|
||||
streamOut.write(readBuf, 0, readPice);
|
||||
readFrom += readPice;
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
}
|
||||
catch (IOException ioe){
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to provide stream");
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
/**
|
||||
* Sugar
|
||||
* */
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {
|
||||
for (int i = 0; i < hfs0Files.length; i++){
|
||||
if (hfs0Files[i].getName().equals(subFileName))
|
||||
return getProviderSubFilePipedInpStream(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
67
src/main/java/libKonogonka/Tools/XCI/XCIGamecardCert.java
Normal file
67
src/main/java/libKonogonka/Tools/XCI/XCIGamecardCert.java
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.XCI;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Gamecard Cert
|
||||
* */
|
||||
public class XCIGamecardCert {
|
||||
private byte[] rsa2048PKCS1sig;
|
||||
private byte[] magicCert;
|
||||
private byte[] unknown1;
|
||||
private byte kekIndex;
|
||||
private byte[] unknown2;
|
||||
private byte[] deviceID;
|
||||
private byte[] unknown3;
|
||||
private byte[] encryptedData;
|
||||
|
||||
XCIGamecardCert(byte[] certBytes) throws Exception{
|
||||
if (certBytes.length != 512)
|
||||
throw new Exception("XCIGamecardCert Incorrect array size. Expected 512 bytes while received "+certBytes.length);
|
||||
rsa2048PKCS1sig = Arrays.copyOfRange(certBytes, 0, 256);
|
||||
magicCert = Arrays.copyOfRange(certBytes, 256, 260);
|
||||
unknown1 = Arrays.copyOfRange(certBytes, 260, 264);
|
||||
kekIndex = certBytes[264];
|
||||
unknown2 = Arrays.copyOfRange(certBytes, 265, 272);
|
||||
deviceID = Arrays.copyOfRange(certBytes, 272, 288);
|
||||
unknown3 = Arrays.copyOfRange(certBytes, 288, 298);
|
||||
encryptedData = Arrays.copyOfRange(certBytes, 298, 512);
|
||||
/*
|
||||
RainbowHexDump.hexDumpUTF8(rsa2048PKCS1sig);
|
||||
RainbowHexDump.hexDumpUTF8(magicCert);
|
||||
RainbowHexDump.hexDumpUTF8(unknown1);
|
||||
System.out.println(kekIndex);
|
||||
RainbowHexDump.hexDumpUTF8(unknown2);
|
||||
RainbowHexDump.hexDumpUTF8(deviceID);
|
||||
RainbowHexDump.hexDumpUTF8(unknown3);
|
||||
RainbowHexDump.hexDumpUTF8(encryptedData);
|
||||
*/
|
||||
}
|
||||
public byte[] getRsa2048PKCS1sig() { return rsa2048PKCS1sig; }
|
||||
public byte[] getMagicCert() { return magicCert; }
|
||||
public boolean isMagicCertOk(){ return Arrays.equals(magicCert, new byte[]{0x48, 0x45, 0x41, 0x44}); }
|
||||
public byte[] getUnknown1() { return unknown1; }
|
||||
public byte getKekIndex() { return kekIndex; }
|
||||
public byte[] getUnknown2() { return unknown2; }
|
||||
public byte[] getDeviceID() { return deviceID; }
|
||||
public byte[] getUnknown3() { return unknown3; }
|
||||
public byte[] getEncryptedData() { return encryptedData; }
|
||||
}
|
149
src/main/java/libKonogonka/Tools/XCI/XCIGamecardHeader.java
Normal file
149
src/main/java/libKonogonka/Tools/XCI/XCIGamecardHeader.java
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.XCI;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static libKonogonka.LoperConverter.getLEint;
|
||||
import static libKonogonka.LoperConverter.getLElong;
|
||||
/**
|
||||
* Header information
|
||||
* */
|
||||
public class XCIGamecardHeader{
|
||||
private byte[] rsa2048PKCS1sig;
|
||||
private boolean magicHead;
|
||||
private byte[] SecureAreaStartAddr;
|
||||
private boolean bkupAreaStartAddr;
|
||||
private byte titleKEKIndexBoth;
|
||||
private byte titleKEKIndex;
|
||||
private byte KEKIndex;
|
||||
private byte gcSize;
|
||||
private byte gcVersion;
|
||||
private byte gcFlags;
|
||||
private byte[] pkgID;
|
||||
private long valDataEndAddr;
|
||||
private byte[] gcInfoIV;
|
||||
private long hfs0partOffset;
|
||||
private long hfs0headerSize;
|
||||
private byte[] hfs0headerSHA256;
|
||||
private byte[] hfs0initDataSHA256 ;
|
||||
private int secureModeFlag;
|
||||
private int titleKeyFlag;
|
||||
private int keyFlag;
|
||||
private byte[] normAreaEndAddr;
|
||||
|
||||
XCIGamecardHeader(byte[] headerBytes) throws Exception{
|
||||
if (headerBytes.length != 400)
|
||||
throw new Exception("XCIGamecardHeader Incorrect array size. Expected 400 bytes while received "+headerBytes.length);
|
||||
rsa2048PKCS1sig = Arrays.copyOfRange(headerBytes, 0, 256);
|
||||
magicHead = Arrays.equals(Arrays.copyOfRange(headerBytes, 256, 260), new byte[]{0x48, 0x45, 0x41, 0x44});
|
||||
SecureAreaStartAddr = Arrays.copyOfRange(headerBytes, 260, 264);
|
||||
bkupAreaStartAddr = Arrays.equals(Arrays.copyOfRange(headerBytes, 264, 268), new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff});
|
||||
titleKEKIndexBoth = headerBytes[268];
|
||||
titleKEKIndex = (byte) ((titleKEKIndexBoth >> 4) & (byte) 0x0F);
|
||||
KEKIndex = (byte) (titleKEKIndexBoth & 0x0F);
|
||||
gcSize = headerBytes[269];
|
||||
gcVersion = headerBytes[270];
|
||||
gcFlags = headerBytes[271];
|
||||
pkgID = Arrays.copyOfRange(headerBytes, 272, 280);
|
||||
valDataEndAddr = getLElong(headerBytes, 280); //TODO: FIX/simplify //
|
||||
gcInfoIV = reverseBytes(Arrays.copyOfRange(headerBytes, 288, 304));
|
||||
hfs0partOffset = getLElong(headerBytes, 304);
|
||||
hfs0headerSize = getLElong(headerBytes, 312);
|
||||
hfs0headerSHA256 = Arrays.copyOfRange(headerBytes, 320, 352);
|
||||
hfs0initDataSHA256 = Arrays.copyOfRange(headerBytes, 352, 384);
|
||||
secureModeFlag = getLEint(headerBytes, 384);
|
||||
titleKeyFlag = getLEint(headerBytes, 388);
|
||||
keyFlag = getLEint(headerBytes, 392);
|
||||
normAreaEndAddr = Arrays.copyOfRange(headerBytes, 396, 400);
|
||||
}
|
||||
|
||||
public byte[] getRsa2048PKCS1sig() { return rsa2048PKCS1sig; }
|
||||
public boolean isMagicHeadOk() { return magicHead; }
|
||||
public byte[] getSecureAreaStartAddr() { return SecureAreaStartAddr; }
|
||||
public boolean isBkupAreaStartAddrOk() { return bkupAreaStartAddr; }
|
||||
public byte getTitleKEKIndexBoth() { return titleKEKIndexBoth; }
|
||||
public byte getTitleKEKIndex() { return titleKEKIndex; }
|
||||
public byte getKEKIndex() { return KEKIndex; }
|
||||
|
||||
public byte getGcSize() {
|
||||
return gcSize;
|
||||
}
|
||||
public byte getGcVersion() {
|
||||
return gcVersion;
|
||||
}
|
||||
public byte getGcFlags() {
|
||||
return gcFlags;
|
||||
}
|
||||
public byte[] getPkgID() {
|
||||
return pkgID;
|
||||
}
|
||||
public long getValDataEndAddr() {
|
||||
return valDataEndAddr;
|
||||
}
|
||||
public byte[] getGcInfoIV() {
|
||||
return gcInfoIV;
|
||||
}
|
||||
public long getHfs0partOffset() {
|
||||
return hfs0partOffset;
|
||||
}
|
||||
public long getHfs0headerSize() {
|
||||
return hfs0headerSize;
|
||||
}
|
||||
public byte[] getHfs0headerSHA256() {
|
||||
return hfs0headerSHA256;
|
||||
}
|
||||
public byte[] getHfs0initDataSHA256() {
|
||||
return hfs0initDataSHA256;
|
||||
}
|
||||
public int getSecureModeFlag() {
|
||||
return secureModeFlag;
|
||||
}
|
||||
public boolean isSecureModeFlagOk(){
|
||||
return secureModeFlag == 1;
|
||||
}
|
||||
public int getTitleKeyFlag() {
|
||||
return titleKeyFlag;
|
||||
}
|
||||
public boolean istitleKeyFlagOk(){
|
||||
return titleKeyFlag == 2;
|
||||
}
|
||||
public int getKeyFlag() {
|
||||
return keyFlag;
|
||||
}
|
||||
public boolean iskeyFlagOk(){
|
||||
return keyFlag == 0;
|
||||
}
|
||||
public byte[] getNormAreaEndAddr() {
|
||||
return normAreaEndAddr;
|
||||
}
|
||||
|
||||
private byte[] reverseBytes(byte[] bArr){
|
||||
Byte[] objArr = new Byte[bArr.length];
|
||||
for (int i=0;i < bArr.length; i++)
|
||||
objArr[i] = bArr[i];
|
||||
List<Byte> bytesList = Arrays.asList(objArr);
|
||||
Collections.reverse(bytesList);
|
||||
for (int i=0;i < bArr.length; i++)
|
||||
bArr[i] = objArr[i];
|
||||
return bArr;
|
||||
}
|
||||
}
|
119
src/main/java/libKonogonka/Tools/XCI/XCIGamecardInfo.java
Normal file
119
src/main/java/libKonogonka/Tools/XCI/XCIGamecardInfo.java
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.XCI;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static libKonogonka.LoperConverter.getLEint;
|
||||
import static libKonogonka.LoperConverter.getLElong;
|
||||
|
||||
/**
|
||||
* Gamecard Info
|
||||
* */
|
||||
public class XCIGamecardInfo{
|
||||
|
||||
private long fwVersion;
|
||||
private byte[] accessCtrlFlags; // 0x00A10011 for 25MHz access or 0x00A10010 for 50MHz access
|
||||
private int readWaitTime1;
|
||||
private int readWaitTime2;
|
||||
private int writeWaitTime1;
|
||||
private int writeWaitTime2;
|
||||
private byte[] fwMode;
|
||||
private byte[] cupVersion;
|
||||
private byte[] emptyPadding1;
|
||||
private byte[] updPartHash;
|
||||
private byte[] cupID;
|
||||
private byte[] emptyPadding2;
|
||||
// todo: Add factory function instead
|
||||
XCIGamecardInfo(byte[] infoBytes, byte[] IV, String XCI_HEADER_KEY) throws Exception {
|
||||
if (XCI_HEADER_KEY.trim().isEmpty())
|
||||
return;
|
||||
if (infoBytes.length != 112)
|
||||
throw new Exception("XCIGamecardInfo Incorrect array size. Expected 112 bytes while received "+infoBytes.length);
|
||||
|
||||
IvParameterSpec gciIV = new IvParameterSpec(IV);
|
||||
SecretKeySpec skSpec = new SecretKeySpec(hexStrToByteArray(XCI_HEADER_KEY), "AES");
|
||||
|
||||
try {
|
||||
// NOTE: CBC
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skSpec, gciIV);
|
||||
|
||||
byte[] decrypted = cipher.doFinal(infoBytes);
|
||||
|
||||
fwVersion = getLElong(decrypted, 0);
|
||||
accessCtrlFlags = Arrays.copyOfRange(decrypted, 8, 12);
|
||||
readWaitTime1 = getLEint(decrypted, 12);
|
||||
readWaitTime2 = getLEint(decrypted, 16);
|
||||
writeWaitTime1 = getLEint(decrypted, 20);
|
||||
writeWaitTime2 = getLEint(decrypted, 24);
|
||||
fwMode = Arrays.copyOfRange(decrypted, 28, 32);
|
||||
cupVersion = Arrays.copyOfRange(decrypted, 32, 36);
|
||||
emptyPadding1 = Arrays.copyOfRange(decrypted, 36, 40);
|
||||
updPartHash = Arrays.copyOfRange(decrypted, 40, 48);
|
||||
cupID = Arrays.copyOfRange(decrypted, 48, 56);
|
||||
emptyPadding2 = Arrays.copyOfRange(decrypted, 56, 112);
|
||||
/*
|
||||
System.out.println(fwVersion);
|
||||
RainbowHexDump.hexDumpUTF8(accessCtrlFlags);
|
||||
System.out.println(readWaitTime1);
|
||||
System.out.println(readWaitTime2);
|
||||
System.out.println(writeWaitTime1);
|
||||
System.out.println(writeWaitTime2);
|
||||
RainbowHexDump.hexDumpUTF8(fwMode);
|
||||
RainbowHexDump.hexDumpUTF8(cupVersion);
|
||||
RainbowHexDump.hexDumpUTF8(emptyPadding1);
|
||||
RainbowHexDump.hexDumpUTF8(updPartHash);
|
||||
RainbowHexDump.hexDumpUTF8(cupID);
|
||||
RainbowHexDump.hexDumpUTF8(emptyPadding2);
|
||||
*/
|
||||
} catch (Exception e) {
|
||||
throw new Exception("XCIGamecardInfo Decryption failed: \n "+e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
private byte[] hexStrToByteArray(String s) { // thanks stackoverflow
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public long getFwVersion() { return fwVersion; }
|
||||
public byte[] getAccessCtrlFlags() { return accessCtrlFlags; }
|
||||
public int getReadWaitTime1() { return readWaitTime1; }
|
||||
public int getReadWaitTime2() { return readWaitTime2; }
|
||||
public int getWriteWaitTime1() { return writeWaitTime1; }
|
||||
public int getWriteWaitTime2() { return writeWaitTime2; }
|
||||
|
||||
public byte[] getFwMode() { return fwMode; }
|
||||
public byte[] getCupVersion() { return cupVersion; }
|
||||
public boolean isEmptyPadding1() { return Arrays.equals(emptyPadding1, new byte[4]); }
|
||||
public byte[] getEmptyPadding1() { return emptyPadding1; }
|
||||
public byte[] getUpdPartHash() { return updPartHash; }
|
||||
public byte[] getCupID() { return cupID; }
|
||||
public boolean isEmptyPadding2() { return Arrays.equals(emptyPadding2, new byte[56]); }
|
||||
public byte[] getEmptyPadding2() { return emptyPadding2; }
|
||||
}
|
118
src/main/java/libKonogonka/Tools/XCI/XCIProvider.java
Normal file
118
src/main/java/libKonogonka/Tools/XCI/XCIProvider.java
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.Tools.XCI;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class XCIProvider{
|
||||
// TODO: Since LOGO partition added, we have to handle it properly. Is it works??
|
||||
|
||||
//private BufferedInputStream xciBIS;
|
||||
private XCIGamecardHeader xciGamecardHeader;
|
||||
private XCIGamecardInfo xciGamecardInfo;
|
||||
private XCIGamecardCert xciGamecardCert;
|
||||
private HFS0Provider hfs0ProviderMain,
|
||||
hfs0ProviderUpdate,
|
||||
hfs0ProviderNormal,
|
||||
hfs0ProviderSecure,
|
||||
hfs0ProviderLogo;
|
||||
|
||||
public XCIProvider(File file, String XCI_HEADER_KEY) throws Exception{ // TODO: ADD FILE SIZE CHECK !!! Check xciHdrKey
|
||||
RandomAccessFile raf;
|
||||
|
||||
try {
|
||||
//xciBIS = new BufferedInputStream(new FileInputStream(file));
|
||||
raf = new RandomAccessFile(file, "r");
|
||||
}
|
||||
catch (FileNotFoundException fnfe){
|
||||
throw new Exception("XCI File not found: \n "+fnfe.getMessage());
|
||||
}
|
||||
|
||||
if (file.length() < 0xf010)
|
||||
throw new Exception("XCI File is too small.");
|
||||
|
||||
try{
|
||||
byte[] gamecardHeaderBytes = new byte[400];
|
||||
byte[] gamecardInfoBytes = new byte[112];
|
||||
byte[] gamecardCertBytes = new byte[512];
|
||||
|
||||
// Creating GC Header class
|
||||
if (raf.read(gamecardHeaderBytes) != 400) {
|
||||
raf.close();
|
||||
throw new Exception("XCI Can't read Gamecard Header bytes.");
|
||||
}
|
||||
xciGamecardHeader = new XCIGamecardHeader(gamecardHeaderBytes); // throws exception
|
||||
// Creating GC Info class
|
||||
if (raf.read(gamecardInfoBytes) != 112) {
|
||||
raf.close();
|
||||
throw new Exception("XCI Can't read Gamecard Header bytes.");
|
||||
}
|
||||
xciGamecardInfo = new XCIGamecardInfo(gamecardInfoBytes, xciGamecardHeader.getGcInfoIV(), XCI_HEADER_KEY);
|
||||
// Creating GC Cerfificate class
|
||||
raf.seek(0x7000);
|
||||
if (raf.read(gamecardCertBytes) != 512) {
|
||||
raf.close();
|
||||
throw new Exception("XCI Can't read Gamecard certificate bytes.");
|
||||
}
|
||||
xciGamecardCert = new XCIGamecardCert(gamecardCertBytes);
|
||||
|
||||
hfs0ProviderMain = new HFS0Provider(0xf000, raf, file);
|
||||
if (hfs0ProviderMain.getFilesCnt() < 3){
|
||||
raf.close();
|
||||
throw new Exception("XCI Can't read Gamecard certificate bytes.");
|
||||
}
|
||||
// Get all partitions from the main HFS0 file
|
||||
String partition;
|
||||
for (HFS0File hfs0File: hfs0ProviderMain.getHfs0Files()){
|
||||
partition = hfs0File.getName();
|
||||
if (partition.equals("update")) {
|
||||
hfs0ProviderUpdate = new HFS0Provider(hfs0ProviderMain.getRawFileDataStart() + hfs0File.getOffset(), raf, file);
|
||||
continue;
|
||||
}
|
||||
if (partition.equals("normal")) {
|
||||
hfs0ProviderNormal = new HFS0Provider(hfs0ProviderMain.getRawFileDataStart() + hfs0File.getOffset(), raf, file);
|
||||
continue;
|
||||
}
|
||||
if (partition.equals("secure")) {
|
||||
hfs0ProviderSecure = new HFS0Provider(hfs0ProviderMain.getRawFileDataStart() + hfs0File.getOffset(), raf, file);
|
||||
continue;
|
||||
}
|
||||
if (partition.equals("logo")) {
|
||||
hfs0ProviderLogo = new HFS0Provider(hfs0ProviderMain.getRawFileDataStart() + hfs0File.getOffset(), raf, file);
|
||||
}
|
||||
}
|
||||
raf.close();
|
||||
}
|
||||
catch (IOException ioe){
|
||||
throw new Exception("XCI Failed file analyze for ["+file.getName()+"]\n "+ioe.getMessage());
|
||||
}
|
||||
}
|
||||
/* Getters */
|
||||
public XCIGamecardHeader getGCHeader(){ return this.xciGamecardHeader; }
|
||||
public XCIGamecardInfo getGCInfo(){ return this.xciGamecardInfo; }
|
||||
public XCIGamecardCert getGCCert(){ return this.xciGamecardCert; }
|
||||
public HFS0Provider getHfs0ProviderMain() { return this.hfs0ProviderMain; }
|
||||
public HFS0Provider getHfs0ProviderUpdate() { return this.hfs0ProviderUpdate; }
|
||||
public HFS0Provider getHfs0ProviderNormal() { return this.hfs0ProviderNormal; }
|
||||
public HFS0Provider getHfs0ProviderSecure() { return this.hfs0ProviderSecure; }
|
||||
public HFS0Provider getHfs0ProviderLogo() { return this.hfs0ProviderLogo; }
|
||||
}
|
53
src/main/java/libKonogonka/ctraes/AesCtr.java
Normal file
53
src/main/java/libKonogonka/ctraes/AesCtr.java
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.ctraes;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.Security;
|
||||
|
||||
public class AesCtr {
|
||||
|
||||
private static boolean BCinitialized = false;
|
||||
|
||||
private void initBCProvider(){
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
BCinitialized = true;
|
||||
}
|
||||
|
||||
private Cipher cipher;
|
||||
private SecretKeySpec key;
|
||||
|
||||
public AesCtr(byte[] keyArray) throws Exception{
|
||||
if ( ! BCinitialized)
|
||||
initBCProvider();
|
||||
|
||||
key = new SecretKeySpec(keyArray, "AES");
|
||||
cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
|
||||
}
|
||||
|
||||
public byte[] decrypt(byte[] encryptedData, byte[] IVarray) throws Exception{
|
||||
IvParameterSpec iv = new IvParameterSpec(IVarray);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, iv);
|
||||
return cipher.doFinal(encryptedData);
|
||||
}
|
||||
}
|
63
src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java
Normal file
63
src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package libKonogonka.ctraes;
|
||||
|
||||
import libKonogonka.LoperConverter;
|
||||
/**
|
||||
* Simplify decryption of the CTR
|
||||
*/
|
||||
public class AesCtrDecryptSimple {
|
||||
|
||||
private long realMediaOffset;
|
||||
private byte[] IVarray;
|
||||
private AesCtr aesCtr;
|
||||
|
||||
public AesCtrDecryptSimple(byte[] key, byte[] sectionCTR, long realMediaOffset) throws Exception{
|
||||
this.realMediaOffset = realMediaOffset;
|
||||
aesCtr = new AesCtr(key);
|
||||
// IV for CTR == 16 bytes
|
||||
IVarray = new byte[0x10];
|
||||
// Populate first 8 bytes taken from Header's section Block CTR
|
||||
System.arraycopy(LoperConverter.flip(sectionCTR), 0x0, IVarray, 0x0, 0x8);
|
||||
}
|
||||
|
||||
public void skipNext(){
|
||||
realMediaOffset += 0x200;
|
||||
}
|
||||
|
||||
public void skipNext(long blocksNum){
|
||||
if (blocksNum > 0)
|
||||
realMediaOffset += blocksNum * 0x200;
|
||||
}
|
||||
|
||||
public byte[] dectyptNext(byte[] enctyptedBlock) throws Exception{
|
||||
updateIV(realMediaOffset);
|
||||
byte[] decryptedBlock = aesCtr.decrypt(enctyptedBlock, IVarray);
|
||||
realMediaOffset += 0x200;
|
||||
return decryptedBlock;
|
||||
}
|
||||
// Populate last 8 bytes calculated. Thanks hactool project!
|
||||
private void updateIV(long offset){
|
||||
offset >>= 4;
|
||||
for (int i = 0; i < 0x8; i++){
|
||||
IVarray[0x10-i-1] = (byte)(offset & 0xff); // Note: issues could be here
|
||||
offset >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package libKonogonka.exceptions;
|
||||
|
||||
public class EmptySectionException extends NullPointerException {
|
||||
public EmptySectionException(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
116
src/main/java/libKonogonka/xtsaes/XTSAESBlockCipher.java
Normal file
116
src/main/java/libKonogonka/xtsaes/XTSAESBlockCipher.java
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2016 Ahseya.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package libKonogonka.xtsaes;
|
||||
|
||||
import net.jcip.annotations.NotThreadSafe;
|
||||
import org.bouncycastle.crypto.BlockCipher;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
/**
|
||||
* XTS-AES implemented as a BlockCipher.
|
||||
*
|
||||
* @author Ahseya
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public class XTSAESBlockCipher implements BlockCipher {
|
||||
|
||||
private final XTSCore core;
|
||||
private final int blockSize;
|
||||
private final int dataUnitSize;
|
||||
private long dataUnit;
|
||||
private int index;
|
||||
|
||||
XTSAESBlockCipher(
|
||||
XTSCore core,
|
||||
int blockSize,
|
||||
int dataUnitSize,
|
||||
long dataUnit,
|
||||
int index) {
|
||||
|
||||
this.core = Objects.requireNonNull(core, "core");
|
||||
this.blockSize = blockSize;
|
||||
this.dataUnitSize = dataUnitSize;
|
||||
this.dataUnit = dataUnit;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
XTSAESBlockCipher(XTSCore core, LongFunction<byte[]> tweakValueFunction, int dataUnitSize) {
|
||||
this(new XTSCore(new XTSTweak(tweakValueFunction)), core.getBlockSize(), dataUnitSize, 0, 0);
|
||||
}
|
||||
|
||||
public XTSAESBlockCipher(boolean isDefault, LongFunction<byte[]> tweakValueFunction, int dataUnitSize) {
|
||||
this(new XTSCore(isDefault), tweakValueFunction, dataUnitSize);
|
||||
}
|
||||
|
||||
public XTSAESBlockCipher(boolean isDefault, int dataUnitSize) {
|
||||
this(new XTSCore(isDefault), isDefault?XTSTweak::defaultTweakFunction:XTSTweak::nintTweakFunction, dataUnitSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException {
|
||||
if (params instanceof KeyParameter) {
|
||||
core.init(forEncryption, (KeyParameter) params);
|
||||
return;
|
||||
}
|
||||
throw new IllegalArgumentException("invalid params: " + params.getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgorithmName() {
|
||||
return core.getAlgorithmName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockSize() {
|
||||
return blockSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processBlock(byte[] in, int inOff, byte[] out, int outOff)
|
||||
throws DataLengthException, IllegalStateException {
|
||||
|
||||
if (index == 0) {
|
||||
core.reset(dataUnit);
|
||||
}
|
||||
|
||||
if ((index += blockSize) == dataUnitSize) {
|
||||
dataUnit++;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
return core.processBlock(in, inOff, out, outOff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
index = 0;
|
||||
dataUnit = 0;
|
||||
}
|
||||
}
|
||||
// TODO consider passing initial data unit sequence number in addition to key via CipherParameters subclass
|
117
src/main/java/libKonogonka/xtsaes/XTSAESCipher.java
Normal file
117
src/main/java/libKonogonka/xtsaes/XTSAESCipher.java
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2016 Ahseya.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package libKonogonka.xtsaes;
|
||||
|
||||
import net.jcip.annotations.NotThreadSafe;
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
/**
|
||||
* XTS-AES cipher with arbitrary (non 128-bit aligned) data unit lengths.
|
||||
*
|
||||
* @author Ahseya
|
||||
*/
|
||||
|
||||
/**
|
||||
* Updated for special usage by Dmitry Isaenko.
|
||||
* */
|
||||
@NotThreadSafe
|
||||
public class XTSAESCipher {
|
||||
|
||||
private final XTSCore core;
|
||||
private final int blockSize;
|
||||
|
||||
XTSAESCipher(XTSCore core) {
|
||||
this.core = Objects.requireNonNull(core, "core");
|
||||
blockSize = core.getBlockSize();
|
||||
}
|
||||
|
||||
public XTSAESCipher(LongFunction<byte[]> tweakFunction) {
|
||||
this(new XTSCore(new XTSTweak(tweakFunction)));
|
||||
}
|
||||
|
||||
public XTSAESCipher(boolean isDefault) {
|
||||
this(new XTSCore(isDefault));
|
||||
}
|
||||
|
||||
String getAlgorithmName() {
|
||||
return core.getAlgorithmName();
|
||||
}
|
||||
|
||||
int getBlockSize() {
|
||||
return blockSize;
|
||||
}
|
||||
|
||||
public XTSAESCipher init(boolean forEncryption, KeyParameter key) throws IllegalArgumentException {
|
||||
core.init(forEncryption, key);
|
||||
return this;
|
||||
}
|
||||
|
||||
public XTSAESCipher init(boolean forEncryption, KeyParameter key1, KeyParameter key2)
|
||||
throws IllegalArgumentException {
|
||||
|
||||
core.init(forEncryption, key1, key2);
|
||||
return this;
|
||||
}
|
||||
|
||||
public int processDataUnit(byte[] in, int inOff, int length, byte[] out, int outOff, long sequenceNumber)
|
||||
throws DataLengthException, IllegalStateException {
|
||||
|
||||
core.reset(sequenceNumber);
|
||||
return process(in, inOff, length, out, outOff);
|
||||
}
|
||||
|
||||
int process(byte[] in, int inOff, int length, byte[] out, int outOff)
|
||||
throws DataLengthException, IllegalStateException {
|
||||
|
||||
if (length < blockSize) {
|
||||
throw new DataLengthException("data unit size too small: " + length);
|
||||
}
|
||||
|
||||
if (inOff + length > in.length) {
|
||||
throw new DataLengthException("input buffer too small for data unit size: " + length);
|
||||
}
|
||||
|
||||
if (outOff + length > out.length) {
|
||||
throw new DataLengthException("output buffer too small for data unit size: " + length);
|
||||
}
|
||||
|
||||
int to = length % blockSize == 0
|
||||
? length
|
||||
: length - (blockSize * 2);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < to; i += blockSize) {
|
||||
core.processBlock(in, inOff + i, out, outOff + i);
|
||||
}
|
||||
|
||||
if (length > i) {
|
||||
core.processPartial(in, inOff + i, out, outOff + i, length - i);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
}
|
154
src/main/java/libKonogonka/xtsaes/XTSCore.java
Normal file
154
src/main/java/libKonogonka/xtsaes/XTSCore.java
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2016 Ahseya.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package libKonogonka.xtsaes;
|
||||
|
||||
import net.jcip.annotations.NotThreadSafe;
|
||||
import org.bouncycastle.crypto.BlockCipher;
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.engines.AESFastEngine;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* XTS core functions.
|
||||
*
|
||||
* @author Ahseya
|
||||
*/
|
||||
|
||||
/**
|
||||
* Updated for special usage by Dmitry Isaenko.
|
||||
* */
|
||||
@NotThreadSafe
|
||||
class XTSCore {
|
||||
|
||||
private static final int BLOCK_SIZE = 16;
|
||||
|
||||
private final BlockCipher cipher;
|
||||
private final XTSTweak tweak;
|
||||
private boolean forEncryption;
|
||||
|
||||
XTSCore(BlockCipher cipher, XTSTweak tweak) {
|
||||
this.cipher = Objects.requireNonNull(cipher, "cipher");
|
||||
this.tweak = Objects.requireNonNull(tweak, "tweak");
|
||||
}
|
||||
|
||||
XTSCore(XTSTweak tweak) {
|
||||
this(new AESFastEngine(), tweak);
|
||||
}
|
||||
|
||||
XTSCore(boolean isDefault) {
|
||||
this(new XTSTweak(isDefault));
|
||||
}
|
||||
|
||||
XTSCore init(boolean forEncryption, KeyParameter key) throws IllegalArgumentException {
|
||||
byte[] k = ((KeyParameter) key).getKey();
|
||||
if (k.length != 32 && k.length != 64) {
|
||||
throw new IllegalArgumentException("bad key length: " + k.length);
|
||||
}
|
||||
|
||||
byte[] key1 = Arrays.copyOfRange(k, 0, k.length / 2);
|
||||
byte[] key2 = Arrays.copyOfRange(k, k.length / 2, k.length);
|
||||
|
||||
return init(forEncryption, new KeyParameter(key1), new KeyParameter(key2));
|
||||
}
|
||||
|
||||
XTSCore init(boolean forEncryption, KeyParameter key1, KeyParameter key2) throws IllegalArgumentException {
|
||||
cipher.init(forEncryption, key1);
|
||||
tweak.init(key2);
|
||||
this.forEncryption = forEncryption;
|
||||
return this;
|
||||
}
|
||||
|
||||
XTSCore reset(long tweakValue) throws DataLengthException, IllegalStateException {
|
||||
tweak.reset(tweakValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
String getAlgorithmName() {
|
||||
return cipher.getAlgorithmName();
|
||||
}
|
||||
|
||||
int getBlockSize() {
|
||||
return BLOCK_SIZE;
|
||||
}
|
||||
|
||||
int processBlock(byte[] in, int inOff, byte[] out, int outOff) throws DataLengthException, IllegalStateException {
|
||||
byte[] tweakValue = tweak.value();
|
||||
doProcessBlock(in, inOff, out, outOff, tweakValue);
|
||||
tweak.next();
|
||||
return BLOCK_SIZE;
|
||||
}
|
||||
|
||||
int doProcessBlock(byte[] in, int inOff, byte[] out, int outOff, byte[] tweakValue)
|
||||
throws DataLengthException, IllegalStateException {
|
||||
|
||||
merge(in, inOff, out, outOff, tweakValue);
|
||||
cipher.processBlock(out, outOff, out, outOff);
|
||||
merge(out, outOff, out, outOff, tweakValue);
|
||||
return BLOCK_SIZE;
|
||||
}
|
||||
|
||||
void merge(byte[] in, int inOff, byte[] out, int outOff, byte[] tweak) {
|
||||
for (int i = 0; i < BLOCK_SIZE; i++) {
|
||||
out[i + outOff] = (byte) (in[i + inOff] ^ tweak[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int processPartial(byte[] in, int inOff, byte[] out, int outOff, int length) {
|
||||
if (length <= BLOCK_SIZE) {
|
||||
throw new DataLengthException("input buffer too small/ missing last two blocks: " + length);
|
||||
}
|
||||
|
||||
if (length >= BLOCK_SIZE * 2) {
|
||||
throw new DataLengthException("input buffer too large/ non-partial final block: " + length);
|
||||
}
|
||||
|
||||
byte[] tweakA = tweak.value();
|
||||
byte[] tweakB = tweak.next().value();
|
||||
|
||||
return forEncryption
|
||||
? XTSCore.this.doProcessPartial(in, inOff, out, outOff, length, tweakA, tweakB)
|
||||
: XTSCore.this.doProcessPartial(in, inOff, out, outOff, length, tweakB, tweakA);
|
||||
}
|
||||
|
||||
int doProcessPartial(byte[] in, int inOff, byte[] out, int outOff, int length, byte[] tweakA, byte[] tweakB)
|
||||
throws DataLengthException, IllegalStateException {
|
||||
// M-1 block
|
||||
doProcessBlock(in, inOff, out, outOff, tweakA);
|
||||
|
||||
// Cipher stealing
|
||||
byte[] buffer = Arrays.copyOfRange(out, outOff, outOff + BLOCK_SIZE);
|
||||
System.arraycopy(in, inOff + BLOCK_SIZE, buffer, 0, length - BLOCK_SIZE);
|
||||
|
||||
// M block
|
||||
doProcessBlock(buffer, 0, buffer, 0, tweakB);
|
||||
|
||||
// Copy over final block pair
|
||||
System.arraycopy(out, outOff, out, outOff + BLOCK_SIZE, length - BLOCK_SIZE);
|
||||
System.arraycopy(buffer, 0, out, outOff, BLOCK_SIZE);
|
||||
return length;
|
||||
}
|
||||
}
|
129
src/main/java/libKonogonka/xtsaes/XTSTweak.java
Normal file
129
src/main/java/libKonogonka/xtsaes/XTSTweak.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2016 Ahseya.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package libKonogonka.xtsaes;
|
||||
|
||||
import net.jcip.annotations.NotThreadSafe;
|
||||
import org.bouncycastle.crypto.BlockCipher;
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.engines.AESFastEngine;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.util.Pack;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
/**
|
||||
* XTS tweak with pluggable tweak function.
|
||||
*
|
||||
* @author Ahseya
|
||||
*/
|
||||
|
||||
/**
|
||||
* Updated for special usage by Dmitry Isaenko.
|
||||
* */
|
||||
@NotThreadSafe
|
||||
class XTSTweak {
|
||||
static byte[] nintTweakFunction(long tweakValue) {
|
||||
byte[] bs = new byte[BLOCK_SIZE];
|
||||
byte[] twk = Pack.longToBigEndian(tweakValue);
|
||||
int j = BLOCK_SIZE - twk.length;
|
||||
for (byte b: twk){
|
||||
bs[j++] = b;
|
||||
}
|
||||
return bs;
|
||||
}
|
||||
|
||||
static byte[] defaultTweakFunction(long tweakValue) {
|
||||
byte[] bs = Pack.longToLittleEndian(tweakValue);
|
||||
bs = Arrays.copyOfRange(bs, 0, BLOCK_SIZE);
|
||||
return bs;
|
||||
}
|
||||
|
||||
private static final long FDBK = 0x87;
|
||||
private static final long MSB = 0x8000000000000000L;
|
||||
private static final int BLOCK_SIZE = 16;
|
||||
|
||||
private final BlockCipher cipher;
|
||||
private final LongFunction<byte[]> tweakFunction;
|
||||
private final byte[] tweak;
|
||||
|
||||
XTSTweak(BlockCipher cipher, LongFunction<byte[]> tweakFunction, byte[] tweak) {
|
||||
this.cipher = Objects.requireNonNull(cipher, "cipher");
|
||||
this.tweakFunction = Objects.requireNonNull(tweakFunction, "tweakFunction");
|
||||
this.tweak = Objects.requireNonNull(tweak, "tweak");
|
||||
|
||||
if (cipher.getBlockSize() != BLOCK_SIZE) {
|
||||
throw new IllegalArgumentException("bad block size: " + cipher.getBlockSize());
|
||||
}
|
||||
}
|
||||
|
||||
XTSTweak(BlockCipher cipher, LongFunction<byte[]> tweakFunction) {
|
||||
this(cipher, tweakFunction, new byte[cipher.getBlockSize()]);
|
||||
}
|
||||
|
||||
XTSTweak(LongFunction<byte[]> tweakFunction) {
|
||||
this(new AESFastEngine(), tweakFunction);
|
||||
}
|
||||
|
||||
XTSTweak(boolean isDefault) {
|
||||
this(isDefault
|
||||
? XTSTweak::defaultTweakFunction
|
||||
: XTSTweak::nintTweakFunction);
|
||||
}
|
||||
|
||||
XTSTweak init(KeyParameter key) throws IllegalArgumentException {
|
||||
cipher.init(true, key);
|
||||
return this;
|
||||
}
|
||||
|
||||
XTSTweak reset(long tweakValue) throws DataLengthException, IllegalStateException {
|
||||
return reset(tweakFunction.apply(tweakValue));
|
||||
}
|
||||
|
||||
XTSTweak reset(byte[] tweakBytes) throws DataLengthException, IllegalStateException {
|
||||
cipher.processBlock(tweakBytes, 0, tweak, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
byte[] value() {
|
||||
return Arrays.copyOf(tweak, tweak.length);
|
||||
}
|
||||
|
||||
XTSTweak next() {
|
||||
long lo = Pack.littleEndianToLong(tweak, 0);
|
||||
long hi = Pack.littleEndianToLong(tweak, 8);
|
||||
|
||||
long fdbk = (hi & MSB) == 0
|
||||
? 0L
|
||||
: FDBK;
|
||||
|
||||
hi = (hi << 1) | (lo >>> 63);
|
||||
lo = (lo << 1) ^ fdbk;
|
||||
|
||||
Pack.longToLittleEndian(lo, tweak, 0);
|
||||
Pack.longToLittleEndian(hi, tweak, 8);
|
||||
return this;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue