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