--- /dev/null
+ GNU GENERAL PUBLIC LICENSE\r
+ Version 2, June 1991\r
+\r
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.\r
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA\r
+ Everyone is permitted to copy and distribute verbatim copies\r
+ of this license document, but changing it is not allowed.\r
+\r
+ Preamble\r
+\r
+ The licenses for most software are designed to take away your\r
+freedom to share and change it. By contrast, the GNU General Public\r
+License is intended to guarantee your freedom to share and change free\r
+software--to make sure the software is free for all its users. This\r
+General Public License applies to most of the Free Software\r
+Foundation's software and to any other program whose authors commit to\r
+using it. (Some other Free Software Foundation software is covered by\r
+the GNU Library General Public License instead.) You can apply it to\r
+your programs, too.\r
+\r
+ When we speak of free software, we are referring to freedom, not\r
+price. Our General Public Licenses are designed to make sure that you\r
+have the freedom to distribute copies of free software (and charge for\r
+this service if you wish), that you receive source code or can get it\r
+if you want it, that you can change the software or use pieces of it\r
+in new free programs; and that you know you can do these things.\r
+\r
+ To protect your rights, we need to make restrictions that forbid\r
+anyone to deny you these rights or to ask you to surrender the rights.\r
+These restrictions translate to certain responsibilities for you if you\r
+distribute copies of the software, or if you modify it.\r
+\r
+ For example, if you distribute copies of such a program, whether\r
+gratis or for a fee, you must give the recipients all the rights that\r
+you have. You must make sure that they, too, receive or can get the\r
+source code. And you must show them these terms so they know their\r
+rights.\r
+\r
+ We protect your rights with two steps: (1) copyright the software, and\r
+(2) offer you this license which gives you legal permission to copy,\r
+distribute and/or modify the software.\r
+\r
+ Also, for each author's protection and ours, we want to make certain\r
+that everyone understands that there is no warranty for this free\r
+software. If the software is modified by someone else and passed on, we\r
+want its recipients to know that what they have is not the original, so\r
+that any problems introduced by others will not reflect on the original\r
+authors' reputations.\r
+\r
+ Finally, any free program is threatened constantly by software\r
+patents. We wish to avoid the danger that redistributors of a free\r
+program will individually obtain patent licenses, in effect making the\r
+program proprietary. To prevent this, we have made it clear that any\r
+patent must be licensed for everyone's free use or not licensed at all.\r
+\r
+ The precise terms and conditions for copying, distribution and\r
+modification follow.\r
+\r
+ GNU GENERAL PUBLIC LICENSE\r
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\r
+\r
+ 0. This License applies to any program or other work which contains\r
+a notice placed by the copyright holder saying it may be distributed\r
+under the terms of this General Public License. The "Program", below,\r
+refers to any such program or work, and a "work based on the Program"\r
+means either the Program or any derivative work under copyright law:\r
+that is to say, a work containing the Program or a portion of it,\r
+either verbatim or with modifications and/or translated into another\r
+language. (Hereinafter, translation is included without limitation in\r
+the term "modification".) Each licensee is addressed as "you".\r
+\r
+Activities other than copying, distribution and modification are not\r
+covered by this License; they are outside its scope. The act of\r
+running the Program is not restricted, and the output from the Program\r
+is covered only if its contents constitute a work based on the\r
+Program (independent of having been made by running the Program).\r
+Whether that is true depends on what the Program does.\r
+\r
+ 1. You may copy and distribute verbatim copies of the Program's\r
+source code as you receive it, in any medium, provided that you\r
+conspicuously and appropriately publish on each copy an appropriate\r
+copyright notice and disclaimer of warranty; keep intact all the\r
+notices that refer to this License and to the absence of any warranty;\r
+and give any other recipients of the Program a copy of this License\r
+along with the Program.\r
+\r
+You may charge a fee for the physical act of transferring a copy, and\r
+you may at your option offer warranty protection in exchange for a fee.\r
+\r
+ 2. You may modify your copy or copies of the Program or any portion\r
+of it, thus forming a work based on the Program, and copy and\r
+distribute such modifications or work under the terms of Section 1\r
+above, provided that you also meet all of these conditions:\r
+\r
+ a) You must cause the modified files to carry prominent notices\r
+ stating that you changed the files and the date of any change.\r
+\r
+ b) You must cause any work that you distribute or publish, that in\r
+ whole or in part contains or is derived from the Program or any\r
+ part thereof, to be licensed as a whole at no charge to all third\r
+ parties under the terms of this License.\r
+\r
+ c) If the modified program normally reads commands interactively\r
+ when run, you must cause it, when started running for such\r
+ interactive use in the most ordinary way, to print or display an\r
+ announcement including an appropriate copyright notice and a\r
+ notice that there is no warranty (or else, saying that you provide\r
+ a warranty) and that users may redistribute the program under\r
+ these conditions, and telling the user how to view a copy of this\r
+ License. (Exception: if the Program itself is interactive but\r
+ does not normally print such an announcement, your work based on\r
+ the Program is not required to print an announcement.)\r
+\r
+These requirements apply to the modified work as a whole. If\r
+identifiable sections of that work are not derived from the Program,\r
+and can be reasonably considered independent and separate works in\r
+themselves, then this License, and its terms, do not apply to those\r
+sections when you distribute them as separate works. But when you\r
+distribute the same sections as part of a whole which is a work based\r
+on the Program, the distribution of the whole must be on the terms of\r
+this License, whose permissions for other licensees extend to the\r
+entire whole, and thus to each and every part regardless of who wrote it.\r
+\r
+Thus, it is not the intent of this section to claim rights or contest\r
+your rights to work written entirely by you; rather, the intent is to\r
+exercise the right to control the distribution of derivative or\r
+collective works based on the Program.\r
+\r
+In addition, mere aggregation of another work not based on the Program\r
+with the Program (or with a work based on the Program) on a volume of\r
+a storage or distribution medium does not bring the other work under\r
+the scope of this License.\r
+\r
+ 3. You may copy and distribute the Program (or a work based on it,\r
+under Section 2) in object code or executable form under the terms of\r
+Sections 1 and 2 above provided that you also do one of the following:\r
+\r
+ a) Accompany it with the complete corresponding machine-readable\r
+ source code, which must be distributed under the terms of Sections\r
+ 1 and 2 above on a medium customarily used for software\r
+ interchange; or,\r
+\r
+ b) Accompany it with a written offer, valid for at least three\r
+ years, to give any third party, for a charge no more than your\r
+ cost of physically performing source distribution, a complete\r
+ machine-readable copy of the corresponding source code, to be\r
+ distributed under the terms of Sections 1 and 2 above on a medium\r
+ customarily used for software interchange; or,\r
+\r
+ c) Accompany it with the information you received as to the offer\r
+ to distribute corresponding source code. (This alternative is\r
+ allowed only for noncommercial distribution and only if you\r
+ received the program in object code or executable form with such\r
+ an offer, in accord with Subsection b above.)\r
+\r
+The source code for a work means the preferred form of the work for\r
+making modifications to it. For an executable work, complete source\r
+code means all the source code for all modules it contains, plus any\r
+associated interface definition files, plus the scripts used to\r
+control compilation and installation of the executable. However, as a\r
+special exception, the source code distributed need not include\r
+anything that is normally distributed (in either source or binary\r
+form) with the major components (compiler, kernel, and so on) of the\r
+operating system on which the executable runs, unless that component\r
+itself accompanies the executable.\r
+\r
+If distribution of executable or object code is made by offering\r
+access to copy from a designated place, then offering equivalent\r
+access to copy the source code from the same place counts as\r
+distribution of the source code, even though third parties are not\r
+compelled to copy the source along with the object code.\r
+\r
+ 4. You may not copy, modify, sublicense, or distribute the Program\r
+except as expressly provided under this License. Any attempt\r
+otherwise to copy, modify, sublicense or distribute the Program is\r
+void, and will automatically terminate your rights under this License.\r
+However, parties who have received copies, or rights, from you under\r
+this License will not have their licenses terminated so long as such\r
+parties remain in full compliance.\r
+\r
+ 5. You are not required to accept this License, since you have not\r
+signed it. However, nothing else grants you permission to modify or\r
+distribute the Program or its derivative works. These actions are\r
+prohibited by law if you do not accept this License. Therefore, by\r
+modifying or distributing the Program (or any work based on the\r
+Program), you indicate your acceptance of this License to do so, and\r
+all its terms and conditions for copying, distributing or modifying\r
+the Program or works based on it.\r
+\r
+ 6. Each time you redistribute the Program (or any work based on the\r
+Program), the recipient automatically receives a license from the\r
+original licensor to copy, distribute or modify the Program subject to\r
+these terms and conditions. You may not impose any further\r
+restrictions on the recipients' exercise of the rights granted herein.\r
+You are not responsible for enforcing compliance by third parties to\r
+this License.\r
+\r
+ 7. If, as a consequence of a court judgment or allegation of patent\r
+infringement or for any other reason (not limited to patent issues),\r
+conditions are imposed on you (whether by court order, agreement or\r
+otherwise) that contradict the conditions of this License, they do not\r
+excuse you from the conditions of this License. If you cannot\r
+distribute so as to satisfy simultaneously your obligations under this\r
+License and any other pertinent obligations, then as a consequence you\r
+may not distribute the Program at all. For example, if a patent\r
+license would not permit royalty-free redistribution of the Program by\r
+all those who receive copies directly or indirectly through you, then\r
+the only way you could satisfy both it and this License would be to\r
+refrain entirely from distribution of the Program.\r
+\r
+If any portion of this section is held invalid or unenforceable under\r
+any particular circumstance, the balance of the section is intended to\r
+apply and the section as a whole is intended to apply in other\r
+circumstances.\r
+\r
+It is not the purpose of this section to induce you to infringe any\r
+patents or other property right claims or to contest validity of any\r
+such claims; this section has the sole purpose of protecting the\r
+integrity of the free software distribution system, which is\r
+implemented by public license practices. Many people have made\r
+generous contributions to the wide range of software distributed\r
+through that system in reliance on consistent application of that\r
+system; it is up to the author/donor to decide if he or she is willing\r
+to distribute software through any other system and a licensee cannot\r
+impose that choice.\r
+\r
+This section is intended to make thoroughly clear what is believed to\r
+be a consequence of the rest of this License.\r
+\r
+ 8. If the distribution and/or use of the Program is restricted in\r
+certain countries either by patents or by copyrighted interfaces, the\r
+original copyright holder who places the Program under this License\r
+may add an explicit geographical distribution limitation excluding\r
+those countries, so that distribution is permitted only in or among\r
+countries not thus excluded. In such case, this License incorporates\r
+the limitation as if written in the body of this License.\r
+\r
+ 9. The Free Software Foundation may publish revised and/or new versions\r
+of the General Public License from time to time. Such new versions will\r
+be similar in spirit to the present version, but may differ in detail to\r
+address new problems or concerns.\r
+\r
+Each version is given a distinguishing version number. If the Program\r
+specifies a version number of this License which applies to it and\r
+"any later version", you have the option of following the terms and\r
+conditions either of that version or of any later version published by\r
+the Free Software Foundation. If the Program does not specify a\r
+version number of this License, you may choose any version ever\r
+published by the Free Software Foundation.\r
+\r
+ 10. If you wish to incorporate parts of the Program into other free\r
+programs whose distribution conditions are different, write to the author\r
+to ask for permission. For software which is copyrighted by the Free\r
+Software Foundation, write to the Free Software Foundation; we sometimes\r
+make exceptions for this. Our decision will be guided by the two goals\r
+of preserving the free status of all derivatives of our free software and\r
+of promoting the sharing and reuse of software generally.\r
+\r
+ NO WARRANTY\r
+\r
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO\r
+WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.\r
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR\r
+OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY\r
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE\r
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE\r
+PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME\r
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\r
+\r
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN\r
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY\r
+AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU\r
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR\r
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE\r
+PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING\r
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A\r
+FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF\r
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH\r
+DAMAGES.\r
+\r
+ END OF TERMS AND CONDITIONS\r
+\r
+ How to Apply These Terms to Your New Programs\r
+\r
+ If you develop a new program, and you want it to be of the greatest\r
+possible use to the public, the best way to achieve this is to make it\r
+free software which everyone can redistribute and change under these\r
+terms.\r
+\r
+ To do so, attach the following notices to the program. It is safest\r
+to attach them to the start of each source file to most effectively\r
+convey the exclusion of warranty; and each file should have at least\r
+the "copyright" line and a pointer to where the full notice is found.\r
+\r
+ <one line to give the program's name and a brief idea of what it does.>\r
+ Copyright (C) <year> <name of author>\r
+\r
+ This program is free software; you can redistribute it and/or modify\r
+ it under the terms of the GNU General Public License as published by\r
+ the Free Software Foundation; either version 2 of the License, or\r
+ (at your option) any later version.\r
+\r
+ This program is distributed in the hope that it will be useful,\r
+ but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+ GNU General Public License for more details.\r
+\r
+ You should have received a copy of the GNU General Public License\r
+ along with this program; if not, write to the Free Software\r
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA\r
+\r
+\r
+Also add information on how to contact you by electronic and paper mail.\r
+\r
+If the program is interactive, make it output a short notice like this\r
+when it starts in an interactive mode:\r
+\r
+ Gnomovision version 69, Copyright (C) year name of author\r
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\r
+ This is free software, and you are welcome to redistribute it\r
+ under certain conditions; type `show c' for details.\r
+\r
+The hypothetical commands `show w' and `show c' should show the appropriate\r
+parts of the General Public License. Of course, the commands you use may\r
+be called something other than `show w' and `show c'; they could even be\r
+mouse-clicks or menu items--whatever suits your program.\r
+\r
+You should also get your employer (if you work as a programmer) or your\r
+school, if any, to sign a "copyright disclaimer" for the program, if\r
+necessary. Here is a sample; alter the names:\r
+\r
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program\r
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.\r
+\r
+ <signature of Ty Coon>, 1 April 1989\r
+ Ty Coon, President of Vice\r
+\r
+This General Public License does not permit incorporating your program into\r
+proprietary programs. If your program is a subroutine library, you may\r
+consider it more useful to permit linking proprietary applications with the\r
+library. If this is what you want to do, use the GNU Library General\r
+Public License instead of this License.\r
--- /dev/null
+#!/bin/bash
+export PATH=$PATH:`pwd`/bin
+java -jar FSNConverter.jar
--- /dev/null
+What is this?
+
+- Yet another one of my random portish things. The Fate/Stay night VNDS
+converter at first glance looked very unixy-okay, so I figured I might
+be able to just run it on linux.
+- Not exactly.
+
+Here was my list of concerns:
+ - Bundled tools.
+ - Java code putting C: and Z: in automatically.
+ - No regard for system tools.
+ - Source code is windows specific.
+ - Launch4j needs butchering.
+
+Basically, I've fixed all of these imperfectly. Enough to use it to do
+its job. As proof, the fate version I'm playing right now was a byproduct of
+this.
+
+ - Misc tools converted. Lotta windows calls ugh
+ - Java code altered a bit. Some things needed to go.
+ - tools has been renamed to bin, and we append it to the path instead.
+ - System tools preferred, and in most cases, required.
+
+Install these deps or you won't be getting anywhere:
+ - pngcrush
+ - ffmpeg/libav
+ - liboggz
+ - ant
+ - java1.6
+These are optional.
+ - pngquant
+ - pngnq
+
+FAQ:
+ Q: So let me get this straight. Fate is a windows game, and you're
+ running a tool on linux to convert for android. WTF is wrong with
+ you.
+ A: There's this thing called a 'canadian-cross' compiler. This is like
+ that. So no, not crazy.
+
+ Q: Does this work?
+ A: For the most part. During conversion I DID get a few warnings spat
+ out about some tlg files.
+
+ Q: I never managed to get fate to run on wine. HELP
+ A: Install Dx9. Also, you probably won't get the videos working so rename
+ video.xp3 to video.xp3.disabled or something.
+
+For license read COPYING. tl;dr same as weaboo.nl, since I can't change the license ( GPLv2 )
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) 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
+this service 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 make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. 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.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+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
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the 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 a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE 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.
+
+ END OF TERMS AND CONDITIONS
+\f
+ 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
+convey 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 2 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision 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, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This 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 Library General
+Public License instead of this License.
--- /dev/null
+/*
+ Copyright (C) 2006
+ http://pie.bbspink.com/test/read.cgi/leaf/1141063964/191
+ http://pie.bbspink.com/test/read.cgi/leaf/1141063964/258
+
+ 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 2 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+*/
+
+// mpeg2 audio layer-2\82Ì\83f\83R\81[\83h\95û\96@\82Ímpg123\82ð\8eQ\8dl\82É\82µ\82Ü\82µ\82½
+// http://www.mpg123.de/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#define AHXHED "\x80\x00\x00\x20\x11\x00\x00" //AHX\83w\83b\83_\82Ì\92è\8b`
+#define AHXCHK "(c)CRI" //\8am\94F\97pAHX\83w\83b\83_\82Ì\92è\8b`
+#define AHXFOT "AHXE(c)CRI" //AHX\83t\83b\83^\82Ì\92è\8b`
+#define OPT_d 0x01 //\83f\83B\83\8c\83N\83g\83\8a\8dì\90¬\83I\83v\83V\83\87\83\93
+
+int getopt(int ac, char **av, char *opts);
+extern int opterr, optind, optopt;
+extern char *optarg;
+
+void *memmem (const void *base,int count,const void *pattern,int length)
+{
+ const char *start;
+ const char *p;
+ if ( count <= 0 )
+ return NULL;
+ start = (const char *)base;
+ for( p = start; p + length <= start + count; ++p ) {
+ if ( 0 == memcmp( p, pattern, length ) )
+ return (char *)p;
+ }
+ return NULL;
+} //memmem
+
+
+unsigned long getword(unsigned char *p)
+{
+ return p[0]+p[1]*256;
+} //modify for bcc32
+
+unsigned long getlong(unsigned char *p)
+{
+ return p[0]+p[1]*256+(p[2]+p[3]*256)*65536;
+} //modify for bcc32
+
+unsigned long getlongb(unsigned char *p)
+{
+ return p[3]+p[2]*256+(p[1]+p[0]*256)*65536;
+} //modify for bcc32
+
+void putword(unsigned char *p,int a)
+{
+ p[0] = a;
+ p[1] = a>>8;
+} //modify for bcc32
+
+void putlong(unsigned char *p,int a)
+{
+ p[0] = a;
+ p[1] = a>>8;
+ p[2] = a>>16;
+ p[3] = a>>24;
+} //modify for bcc32
+
+int bit_alloc_table[32] = { 4,4,4,4,3,3,3,3,3,3,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 };
+int offset_table[5][16] = {
+ { 0},
+ { 0},
+ { 0, 1, 3, 4, },
+ { 0, 1, 3, 4, 5, 6, 7, 8, },
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }
+};
+
+struct quantclass {
+ int nlevels;
+ int bits;
+} const qc_table[17] = {
+ { 3, -5},
+ { 5, -7},
+ { 7, 3},
+ { 9, -10},
+ { 15, 4},
+ { 31, 5},
+ { 63, 6},
+ { 127, 7},
+ { 255, 8},
+ { 511, 9},
+ { 1023, 10},
+ { 2047, 11},
+ { 4095, 12},
+ { 8191, 13},
+ { 16383, 14},
+ { 32767, 15},
+ { 65535, 16}
+};
+int intwinbase[257] = {
+ 0, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -3, -3, -4, -4, -5,
+ -5, -6, -7, -7, -8, -9, -10, -11, -13, -14, -16, -17, -19, -21, -24, -26,
+ -29, -31, -35, -38, -41, -45, -49, -53, -58, -63, -68, -73, -79, -85, -91, -97,
+ -104, -111, -117, -125, -132, -139, -147, -154, -161, -169, -176, -183, -190, -196, -202, -208,
+ -213, -218, -222, -225, -227, -228, -228, -227, -224, -221, -215, -208, -200, -189, -177, -163,
+ -146, -127, -106, -83, -57, -29, 2, 36, 72, 111, 153, 197, 244, 294, 347, 401,
+ 459, 519, 581, 645, 711, 779, 848, 919, 991, 1064, 1137, 1210, 1283, 1356, 1428, 1498,
+ 1567, 1634, 1698, 1759, 1817, 1870, 1919, 1962, 2001, 2032, 2057, 2075, 2085, 2087, 2080, 2063,
+ 2037, 2000, 1952, 1893, 1822, 1739, 1644, 1535, 1414, 1280, 1131, 970, 794, 605, 402, 185,
+ -45, -288, -545, -814, -1095, -1388, -1692, -2006, -2330, -2663, -3004, -3351, -3705, -4063, -4425, -4788,
+ -5153, -5517, -5879, -6237, -6589, -6935, -7271, -7597, -7910, -8209, -8491, -8755, -8998, -9219, -9416, -9585,
+ -9727, -9838, -9916, -9959, -9966, -9935, -9863, -9750, -9592, -9389, -9139, -8840, -8492, -8092, -7640, -7134,
+ -6574, -5959, -5288, -4561, -3776, -2935, -2037, -1082, -70, 998, 2122, 3300, 4533, 5818, 7154, 8540,
+ 9975, 11455, 12980, 14548, 16155, 17799, 19478, 21189, 22929, 24694, 26482, 28289, 30112, 31947, 33791, 35640,
+ 37489, 39336, 41176, 43006, 44821, 46617, 48390, 50137, 51853, 53534, 55178, 56778, 58333, 59838, 61289, 62684,
+ 64019, 65290, 66494, 67629, 68692, 69679, 70590, 71420, 72169, 72835, 73415, 73908, 74313, 74630, 74856, 74992,
+ 75038 };
+double costable[5][16],decwin[512+32];
+
+int getbits(unsigned char **src, int *bit_data, int *bit_rest, int bits)
+{
+ int __ret;
+
+ while(*bit_rest<24){
+ *bit_data<<=8;
+ *bit_data|=**src;
+ *src=*src+1;
+ *bit_rest+=8;
+ }
+
+ __ret = (*bit_data>>(*bit_rest-bits)) & ((1<<bits)-1);
+ *bit_rest -= bits;
+ /* printf("getbits : %d %x\n",bits,__ret); */
+
+ return(__ret);
+} //modify for bcc32 : from ExtractData
+
+void dct(double *src,double *dst0,double *dst1)
+{
+ double tmp[2][32]={0};
+ int i;
+
+ for(i=0;i<32;i++) {
+ if(i&16) tmp[0][i] = ( - src[i] + src[31^i]) * costable[0][~i&15];
+ else tmp[0][i] = ( + src[i] + src[31^i]) ;
+ }
+ for(i=0;i<32;i++){
+ if(i&8) tmp[1][i] = ( - tmp[0][i] + tmp[0][15^i]) * costable[1][~i&7] * ((i&16) ? -1.0 : 1.0);
+ else tmp[1][i] = ( + tmp[0][i] + tmp[0][15^i]);
+ }
+ for(i=0;i<32;i++){
+ if(i&4) tmp[0][i] = ( - tmp[1][i] + tmp[1][7^i]) * costable[2][~i&3] * ((i&8) ? -1.0 : 1.0);
+ else tmp[0][i] = ( + tmp[1][i] + tmp[1][7^i]);
+ }
+ for(i=0;i<32;i++){
+ if(i&2) tmp[1][i] = ( - tmp[0][i] + tmp[0][3^i]) * costable[3][~i&1] * ((i&4) ? -1.0 : 1.0);
+ else tmp[1][i] = ( + tmp[0][i] + tmp[0][3^i]);
+ }
+ for(i=0;i<32;i++){
+ if(i&1) tmp[0][i] = ( - tmp[1][i] + tmp[1][1^i]) * costable[4][0] * ((i&2) ? -1.0 : 1.0);
+ else tmp[0][i] = ( + tmp[1][i] + tmp[1][1^i]);
+ }
+
+ //for (int i = 0; i < 8; i++)
+ //tmp[0][i * 4 + 2] += tmp[0][i * 4 + 3];
+ for (i = 0; i < 32; i += 4)
+ tmp[0][i + 2] += tmp[0][i + 3]; //modify : from ExtractData
+
+ //for (int i = 0; i < 4; i++) {
+ // tmp[0][i * 8 + 4] += tmp[0][i * 8 + 6];
+ // tmp[0][i * 8 + 6] += tmp[0][i * 8 + 5];
+ // tmp[0][i * 8 + 5] += tmp[0][i * 8 + 7];
+ //}
+ for (i = 0; i < 32; i += 8) {
+ tmp[0][i + 4] += tmp[0][i + 6];
+ tmp[0][i + 6] += tmp[0][i + 5];
+ tmp[0][i + 5] += tmp[0][i + 7];
+ } //modify : from ExtractData
+
+ //for (int i = 0; i < 2; i++) {
+ // tmp[0][i * 16 + 8] += tmp[0][i * 16 + 12];
+ // tmp[0][i * 16 + 12] += tmp[0][i * 16 + 10];
+ // tmp[0][i * 16 + 10] += tmp[0][i * 16 + 14];
+ // tmp[0][i * 16 + 14] += tmp[0][i * 16 + 9];
+ // tmp[0][i * 16 + 9] += tmp[0][i * 16 + 13];
+ // tmp[0][i * 16 + 13] += tmp[0][i * 16 + 11];
+ // tmp[0][i * 16 + 11] += tmp[0][i * 16 + 15];
+ //}
+ for (i = 0; i < 32; i += 16) {
+ tmp[0][i + 8] += tmp[0][i + 12];
+ tmp[0][i + 12] += tmp[0][i + 10];
+ tmp[0][i + 10] += tmp[0][i + 14];
+ tmp[0][i + 14] += tmp[0][i + 9];
+ tmp[0][i + 9] += tmp[0][i + 13];
+ tmp[0][i + 13] += tmp[0][i + 11];
+ tmp[0][i + 11] += tmp[0][i + 15];
+ } //modify : from ExtractData
+
+ dst0[16] = tmp[0][0];
+ dst0[15] = tmp[0][16+0] + tmp[0][16+8];
+ dst0[14] = tmp[0][8];
+ dst0[13] = tmp[0][16+8] + tmp[0][16+4];
+ dst0[12] = tmp[0][4];
+ dst0[11] = tmp[0][16+4] + tmp[0][16+12];
+ dst0[10] = tmp[0][12];
+ dst0[ 9] = tmp[0][16+12] + tmp[0][16+2];
+ dst0[ 8] = tmp[0][2];
+ dst0[ 7] = tmp[0][16+2] + tmp[0][16+10];
+ dst0[ 6] = tmp[0][10];
+ dst0[ 5] = tmp[0][16+10] + tmp[0][16+6];
+ dst0[ 4] = tmp[0][6];
+ dst0[ 3] = tmp[0][16+6] + tmp[0][16+14];
+ dst0[ 2] = tmp[0][14];
+ dst0[ 1] = tmp[0][16+14] + tmp[0][16+1];
+ dst0[ 0] = tmp[0][1];
+
+ dst1[ 0] = tmp[0][1];
+ dst1[ 1] = tmp[0][16+1] + tmp[0][16+9];
+ dst1[ 2] = tmp[0][9];
+ dst1[ 3] = tmp[0][16+9] + tmp[0][16+5];
+ dst1[ 4] = tmp[0][5];
+ dst1[ 5] = tmp[0][16+5] + tmp[0][16+13];
+ dst1[ 6] = tmp[0][13];
+ dst1[ 7] = tmp[0][16+13] + tmp[0][16+3];
+ dst1[ 8] = tmp[0][3];
+ dst1[ 9] = tmp[0][16+3] + tmp[0][16+11];
+ dst1[10] = tmp[0][11];
+ dst1[11] = tmp[0][16+11] + tmp[0][16+7];
+ dst1[12] = tmp[0][7];
+ dst1[13] = tmp[0][16+7] + tmp[0][16+15];
+ dst1[14] = tmp[0][15];
+ dst1[15] = tmp[0][16+15];
+}
+
+int decode_ahx(unsigned char *src,unsigned char *dst,int srclen)
+{
+ int bit_rest=0,bit_data;
+ unsigned char *src_start = src;
+ int i,j,sb,gr,phase=0;
+ int bit_alloc[32]={0},scfsi[32]={0},scalefactor[32][3]={0}; // modify for bcc32
+ short *dst_p=(short*)dst; // from ExtractData
+ double sbsamples[36][32]={0},powtable[64]={0};
+ double dctbuf[2][16][17]={0};
+ double *win;
+ int frame=0;
+
+ //printf("before : dst_p=%d, *dst_p=%d\n", dst_p, *dst_p);
+ //printf("after : dst=%d, *dst=%d\n", dst, *dst);
+
+ for(i=0;i<64;i++) powtable[i] = pow(2.0,(3-i)/3.0);
+ for(i=0;i<36;i++) for(sb=0;sb<32;sb++) sbsamples[i][sb] = 0.0;
+
+ //for (int i = 0; i < 16; i++)
+ // costable[0][i] = 0.5 / cos(M_PI * ((double) i * 2.0 + 1.0) / 64.0);
+
+ //for (int i = 0; i < 8; i++)
+ // costable[1][i] = 0.5 / cos(M_PI * ((double) i * 2.0 + 1.0) / 32.0);
+
+ //for (int i = 0; i < 4; i++)
+ // costable[2][i] = 0.5 / cos(M_PI * ((double) i * 2.0 + 1.0) / 16.0);
+
+ //for (int i = 0; i < 2; i++)
+ // costable[3][i] = 0.5 / cos(M_PI * ((double) i * 2.0 + 1.0) / 8.0);
+
+ //for (int i = 0; i < 1; i++)
+ // costable[4][i] = 0.5 / cos(M_PI * ((double) i * 2.0 + 1.0) / 4.0);
+
+ for (i = 0; i < 16; i++)
+ costable[0][i] = 0.5 / cos(M_PI * ((i << 1) + 1) / 64.0); //modify : from ExtractData
+
+ for (i = 0; i < 8; i++)
+ costable[1][i] = 0.5 / cos(M_PI * ((i << 1) + 1) / 32.0); //modify : from ExtractData
+
+ for (i = 0; i < 4; i++)
+ costable[2][i] = 0.5 / cos(M_PI * ((i << 1) + 1) / 16.0); //modify : from ExtractData
+
+ for (i = 0; i < 2; i++)
+ costable[3][i] = 0.5 / cos(M_PI * ((i << 1) + 1) / 8.0); //modify : from ExtractData
+
+ for (i = 0; i < 1; i++)
+ costable[4][i] = 0.5 / cos(M_PI * ((i << 1) + 1) / 4.0); //modify : from ExtractData
+
+ for(i=0,j=0;i<256;i++,j+=32){
+ if(j<512+16) decwin[j]=decwin[j+16] = intwinbase[i] / 65536.0 * 32768.0 * ((i&64) ? +1.0 : -1.0);
+ if((i&31)==31) j-=1023;
+ }
+ for(i=0,j=8;i<256;i++,j+=32){
+ if(j<512+16) decwin[j]=decwin[j+16] = intwinbase[256-i] / 65536.0 * 32768.0 * ((i&64) ? +1.0 : -1.0);
+ if((i&31)==31) j-=1023;
+ }
+
+ src += src[2]*256+src[3]+4;
+
+ while(src-src_start < srclen && getbits(&src, &bit_data, &bit_rest, 12) == 0xfff) {
+ frame ++;
+ getbits(&src, &bit_data, &bit_rest, 1); // LSF
+ getbits(&src, &bit_data, &bit_rest, 2); // layer
+ getbits(&src, &bit_data, &bit_rest, 1); // CRC
+ getbits(&src, &bit_data, &bit_rest, 4); // bitrate
+ getbits(&src, &bit_data, &bit_rest, 2); // freq
+ getbits(&src, &bit_data, &bit_rest, 1); // padding
+ getbits(&src, &bit_data, &bit_rest, 1); // gap
+ getbits(&src, &bit_data, &bit_rest, 2); // mode
+ getbits(&src, &bit_data, &bit_rest, 2); // mode_ext
+ getbits(&src, &bit_data, &bit_rest, 1); // protect
+ getbits(&src, &bit_data, &bit_rest, 1); // copy
+ getbits(&src, &bit_data, &bit_rest, 2); // emphasis
+
+ for(sb=0;sb<30;sb++){
+ bit_alloc[sb] = getbits(&src, &bit_data, &bit_rest, bit_alloc_table[sb]);
+ }
+ for(sb=0;sb<30;sb++){
+ if(bit_alloc[sb]){
+ scfsi[sb] = getbits(&src, &bit_data, &bit_rest, 2);
+ }
+ }
+ for(sb=0;sb<30;sb++){
+ if(bit_alloc[sb]){
+ scalefactor[sb][0] = getbits(&src, &bit_data, &bit_rest, 6);
+ switch(scfsi[sb]){
+ case 0:
+ scalefactor[sb][1] = getbits(&src, &bit_data, &bit_rest, 6);
+ scalefactor[sb][2] = getbits(&src, &bit_data, &bit_rest, 6);
+ break;
+ case 1:
+ scalefactor[sb][1] = scalefactor[sb][0];
+ scalefactor[sb][2] = getbits(&src, &bit_data, &bit_rest, 6);
+ break;
+ case 2:
+ scalefactor[sb][1] = scalefactor[sb][0];
+ scalefactor[sb][2] = scalefactor[sb][0];
+ break;
+ case 3:
+ scalefactor[sb][1] =
+ scalefactor[sb][2] = getbits(&src, &bit_data, &bit_rest, 6);
+ break;
+ }
+ }
+ }
+ for(gr=0;gr<12;gr++){
+ for(sb=0;sb<30;sb++){
+ if(bit_alloc[sb]){
+ int index=offset_table[bit_alloc_table[sb]][bit_alloc[sb]-1],q;
+ if(qc_table[index].bits<0){
+ int t=getbits(&src, &bit_data, &bit_rest, -qc_table[index].bits);
+ q = (t % qc_table[index].nlevels)*2 -qc_table[index].nlevels + 1;
+ sbsamples[gr*3+0][sb] = (double) q / (double) qc_table[index].nlevels ;
+ t /= qc_table[index].nlevels;
+ q = (t % qc_table[index].nlevels)*2 -qc_table[index].nlevels + 1;
+ sbsamples[gr*3+1][sb] = (double) q / (double) qc_table[index].nlevels ;
+ t /= qc_table[index].nlevels;
+ q = t*2 -qc_table[index].nlevels + 1;
+ sbsamples[gr*3+2][sb] = (double) q / (double) qc_table[index].nlevels ;
+ } else {
+ q= getbits(&src, &bit_data, &bit_rest, qc_table[index].bits)*2 - qc_table[index].nlevels + 1;
+ sbsamples[gr*3+0][sb] = (double) q / (double) qc_table[index].nlevels ;
+ q= getbits(&src, &bit_data, &bit_rest, qc_table[index].bits)*2 - qc_table[index].nlevels + 1;
+ sbsamples[gr*3+1][sb] = (double) q / (double) qc_table[index].nlevels ;
+ q= getbits(&src, &bit_data, &bit_rest, qc_table[index].bits)*2 - qc_table[index].nlevels + 1;
+ sbsamples[gr*3+2][sb] = (double) q / (double) qc_table[index].nlevels ;
+ }
+ } else {
+ sbsamples[gr*3+0][sb] = 0;
+ sbsamples[gr*3+1][sb] = 0;
+ sbsamples[gr*3+2][sb] = 0;
+ }
+ //sbsamples[gr * 3 + 0][sb] *= powtable[scalefactor[sb][gr / 4]];
+ //sbsamples[gr * 3 + 1][sb] *= powtable[scalefactor[sb][gr / 4]];
+ //sbsamples[gr * 3 + 2][sb] *= powtable[scalefactor[sb][gr / 4]];
+ sbsamples[gr * 3 + 0][sb] *= powtable[scalefactor[sb][gr >> 2]]; //modify : from ExtractData
+ sbsamples[gr * 3 + 1][sb] *= powtable[scalefactor[sb][gr >> 2]]; //modify : from ExtractData
+ sbsamples[gr * 3 + 2][sb] *= powtable[scalefactor[sb][gr >> 2]]; //modify : from ExtractData
+ }
+ }
+ // synth
+ for(gr=0;gr<36;gr++){
+ double sum=0;
+ if(phase&1)
+ dct(sbsamples[gr],dctbuf[0][phase+1&15],dctbuf[1][phase]);
+ else
+ dct(sbsamples[gr],dctbuf[1][phase],dctbuf[0][phase+1]);
+ win = decwin + 16-(phase|1);
+ for(i=0;i<16;i++,win+=16){
+ sum = *win++ * dctbuf[phase&1][0][i];
+ sum -= *win++ * dctbuf[phase&1][1][i];
+ sum += *win++ * dctbuf[phase&1][2][i];
+ sum -= *win++ * dctbuf[phase&1][3][i];
+ sum += *win++ * dctbuf[phase&1][4][i];
+ sum -= *win++ * dctbuf[phase&1][5][i];
+ sum += *win++ * dctbuf[phase&1][6][i];
+ sum -= *win++ * dctbuf[phase&1][7][i];
+ sum += *win++ * dctbuf[phase&1][8][i];
+ sum -= *win++ * dctbuf[phase&1][9][i];
+ sum += *win++ * dctbuf[phase&1][10][i];
+ sum -= *win++ * dctbuf[phase&1][11][i];
+ sum += *win++ * dctbuf[phase&1][12][i];
+ sum -= *win++ * dctbuf[phase&1][13][i];
+ sum += *win++ * dctbuf[phase&1][14][i];
+ sum -= *win++ * dctbuf[phase&1][15][i];
+ if(sum>=32767) *dst_p ++ = 32767;
+ else if(sum<=-32767) *dst_p ++ = -32767;
+ else *dst_p ++ = sum;
+ }
+ sum = win[0] * dctbuf[phase&1][0][16];
+ sum += win[2] * dctbuf[phase&1][2][16];
+ sum += win[4] * dctbuf[phase&1][4][16];
+ sum += win[6] * dctbuf[phase&1][6][16];
+ sum += win[8] * dctbuf[phase&1][8][16];
+ sum += win[10] * dctbuf[phase&1][10][16];
+ sum += win[12] * dctbuf[phase&1][12][16];
+ sum += win[14] * dctbuf[phase&1][14][16];
+ if(sum>=32767) *dst_p ++ = 32767;
+ else if(sum<=-32767) *dst_p ++ = -32767;
+ else *dst_p ++ = sum;
+ win += -16 + (phase|1)*2;
+ for(i=15;i>=1;i--,win-=16){
+ sum = -*--win * dctbuf[phase&1][0][i];
+ sum -= *--win * dctbuf[phase&1][1][i];
+ sum -= *--win * dctbuf[phase&1][2][i];
+ sum -= *--win * dctbuf[phase&1][3][i];
+ sum -= *--win * dctbuf[phase&1][4][i];
+ sum -= *--win * dctbuf[phase&1][5][i];
+ sum -= *--win * dctbuf[phase&1][6][i];
+ sum -= *--win * dctbuf[phase&1][7][i];
+ sum -= *--win * dctbuf[phase&1][8][i];
+ sum -= *--win * dctbuf[phase&1][9][i];
+ sum -= *--win * dctbuf[phase&1][10][i];
+ sum -= *--win * dctbuf[phase&1][11][i];
+ sum -= *--win * dctbuf[phase&1][12][i];
+ sum -= *--win * dctbuf[phase&1][13][i];
+ sum -= *--win * dctbuf[phase&1][14][i];
+ sum -= *--win * dctbuf[phase&1][15][i];
+ if(sum>=32767) *dst_p ++ = 32767;
+ else if(sum<=-32767) *dst_p ++ = -32767;
+ else *dst_p ++ = sum;
+ }
+ phase = phase-1 & 15;
+ }
+ // skip padding bits
+ if(bit_rest&7) getbits(&src, &bit_data, &bit_rest, bit_rest&7);
+ }
+ //printf("%d frames\n",frame);
+ //printf("after : dst_p=%d, *dst_p=%d\n", dst_p, *dst_p);
+ //printf("after : dst=%d, *dst=%d\n", dst, *dst);
+ return ((char*)dst_p - (char*)dst); //modify : from ExtractData
+}
+
+void fwrite_wav_hed(int len,int freq,FILE *fp)
+{
+ static unsigned char hed[0x2c] = {
+ 'R','I','F','F',0,0,0,0,
+ 'W','A','V','E','f','m','t',' ',0x10,0,0,0,
+ 0x01,0x00, // Linier PCM
+ 0x02,0x00, // ch
+ 0x44,0xac,0x00,0x00, // 44.1kHz
+ 0x10,0xb1,0x02,0x00, // 44.1*4B
+ 0x04,0x00, // block size
+ 0x10,0x00, // bit/sample
+ 'd','a','t','a',0,0,0,0
+ };
+ putword(hed+0x16,1); // ch
+ putlong(hed+0x18,freq); // freq
+ putlong(hed+0x1c,freq*2); // byte/s
+ putword(hed+0x20,0x02); // block size
+ putword(hed+0x22,0x10); // bit/sample
+
+ putlong(hed+4,len+0x2c-4);
+ putlong(hed+0x28,len);
+
+ fwrite(hed,1,0x2c,fp);
+}
+
+#define MAX_PATH 4096
+
+int main(int argc,char **argv)
+{
+ FILE *fp;
+ DIR *fd;
+ unsigned char *src_buf, *ahx_buf, *wav_buf;
+ unsigned char *src_buf_set, *ahx_hed, *ahx_fot;
+ char wav_fn[MAX_PATH], wav_dir[MAX_PATH], src_dir[MAX_PATH]; //modify for ahx2wav
+ int src_buf_len, src_buf_rem, ahx_buf_len, wav_buf_len; //modify for ahx2wav
+ int ahx_count=0, opt=0;
+ char teststr[6];
+
+ if(argc<2) {
+ printf("usage : ahx2wav [-d] filename\nexample : ahx2wav hoge.ahx\n");
+ exit(1);
+ } //modify for ahx2wav
+
+ opterr = 0; //getopt \83G\83\89\81[\94ñ\95\\8e¦
+ while( getopt(argc, argv, "dl:") != EOF ) {
+ switch(optopt) {
+ case 'd':
+ opt = opt | OPT_d;
+ break;
+ default:
+ printf("unknown option -%c\n", optopt);
+ exit(2);
+ }
+ }
+
+ argv = argv + optind; //\83t\83@\83C\83\8b\96¼\8aJ\8en\88Ê\92u\82É\83Z\83b\83g
+
+ if (opt & OPT_d) {
+ // Enumerate over files
+ fd = opendir(*argv);
+
+ if ( !fd ) {
+ printf("Directory handle invalid.");
+ }
+ else {
+ struct dirent *ep;
+
+ do {
+ ep = readdir(fd);
+
+ struct stat st;
+
+ lstat(ep->d_name, &st);
+
+ if(! S_ISDIR(st.st_mode)) {
+ fp = fopen(ep->d_name, "rb");
+ if( fp == NULL) {
+ printf("Can't open %s.\n", ep->d_name);
+ }
+ else if ( !(strcmp(ep->d_name, "ahx2wav.exe")) )
+ fclose(fp);
+ else {
+ getcwd(src_dir, MAX_PATH);
+
+ fseek(fp,0,SEEK_END);
+ src_buf_len = src_buf_rem = ftell(fp);
+ src_buf_set = malloc(src_buf_len);
+ fseek(fp,0,SEEK_SET);
+ fread(src_buf_set,1,src_buf_len,fp);
+ src_buf = src_buf_set;
+ fclose(fp);
+
+ while(src_buf_rem>0) {
+ ahx_hed=memmem(src_buf,src_buf_rem,AHXHED,sizeof(AHXHED)-1);
+ ahx_fot=memmem(src_buf,src_buf_rem,AHXFOT,sizeof(AHXFOT)-1);
+ if(ahx_hed==NULL || ahx_fot==NULL)
+ src_buf_rem=0;
+ else if (strncmp(ahx_hed+30, AHXCHK, sizeof(AHXCHK)-1)!=0) {
+ src_buf++;
+ src_buf_rem--;
+ }
+ else {
+ ahx_count++;
+
+ ahx_buf_len=ahx_fot+12-ahx_hed;
+ ahx_buf=malloc(ahx_buf_len);
+ memcpy(ahx_buf,ahx_hed,ahx_buf_len);
+ wav_buf_len=getlongb(ahx_buf+12)*2;
+ wav_buf=malloc(wav_buf_len+1152*16); //modify : from ExtractData; //+1152*2); margen = layer-2 frame size
+ wav_buf_len=decode_ahx(ahx_buf,wav_buf,ahx_buf_len); //modify : from ExtractData
+
+ if( opt & OPT_d ) { //\83t\83H\83\8b\83_\95Ê\8fo\97Í
+ sprintf(wav_dir, "_%s", ep->d_name);
+ mkdir(wav_dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ chdir(wav_dir);
+ }
+ else
+ strcpy(wav_dir, src_dir);
+ sprintf(wav_fn, "%s_%03d.wav", ep->d_name,ahx_count);
+
+ fp=fopen(wav_fn,"wb");
+ fwrite_wav_hed(wav_buf_len,getlongb(ahx_buf+8),fp);
+ fwrite(wav_buf,1,wav_buf_len,fp);
+ //printf("%s > %s\\%s\n", fd->cFileName, wav_dir, wav_fn);
+ fclose(fp);
+ free(ahx_buf);
+ free(wav_buf);
+
+ src_buf=ahx_fot+12;
+ src_buf_rem=src_buf_len-(src_buf-src_buf_set);
+ chdir(src_dir);
+ }
+ }
+ if ( ahx_count>0 )
+ printf("%s is done.\n", ep->d_name);
+ ahx_count=0;
+ free(src_buf_set);
+ }
+ }
+ } while( *++argv != NULL );
+ }
+ }
+ else {
+
+ }
+ return 0;
+}
--- /dev/null
+/*LINTLIBRARY*/\r
+/***********************************************************************\r
+ getopt.c \82±\82Ìgetopt.c\82ÍPublic Domain\82Å\82·\r
+ 1996.4 Hiroshi Masuda \95Ï\8dX\r
+***********************************************************************/\r
+#include <stdio.h>\r
+#include <string.h>\r
+\r
+int opterr = 1; /* getopt()\83G\83\89\81[\83\81\83b\83Z\81[\83W 1:\95\\8e¦ 0:\94ñ\95\\8e¦ */\r
+int optind = 1; /* getopt()\83C\83\93\83f\83b\83N\83X */\r
+int optopt; /* \8eæ\93¾\83I\83v\83V\83\87\83\93\95¶\8e\9a */\r
+char *optarg; /* \8eæ\93¾\83I\83v\83V\83\87\83\93\83p\83\89\83\81\81[\83^\95¶\8e\9a\97ñ */\r
+\r
+static void errdisp(char *cmd, char *as)\r
+{\r
+ static char crtail[ ] = {0, '\n', 0};\r
+\r
+ if(opterr){\r
+ fputs(cmd, stderr); fputs(as, stderr);\r
+ *crtail = optopt; fputs(crtail, stderr);\r
+ }\r
+}\r
+\r
+int getopt(int ac, char **av, char *opts)\r
+{\r
+ static char *curopt = NULL;\r
+ register char *cp;\r
+\r
+ if(curopt == NULL || !*curopt){\r
+ curopt = av[optind];\r
+ if(optind >= ac || *curopt != '-' || !curopt[1])\r
+ return(EOF); /* \83I\83v\83V\83\87\83\93\8ew\92è\82È\82µ */\r
+ if(!strcmp("-", ++curopt)){\r
+ ++optind;\r
+ return(EOF); /* -- \8ew\92è */\r
+ }\r
+ }\r
+ optopt = *curopt++; /* option\95¶\8e\9a\8ai\94[ */\r
+ cp = strchr(opts, optopt); /* option\95¶\8e\9a\8c\9f\8dõ */\r
+ if(optopt == ':' || cp == NULL){ /* option\95¶\8e\9a\95s\90³ or \8c\9f\8dõ\8e¸\94s */\r
+ errdisp(*av, ": unknown option, -");\r
+ if(!*curopt)\r
+ ++optind;\r
+ return('?');\r
+ }\r
+ if(*++cp == ':'){ /* option\83p\83\89\83\81\81[\83^\82 \82è */\r
+ ++optind;\r
+ if(*curopt){\r
+ optarg = curopt;\r
+ curopt = NULL;\r
+ }else{\r
+ if(optind >= ac){\r
+ errdisp(*av, ": argument missing for -");\r
+ return('?');\r
+ }else{\r
+ optarg = av[optind++];\r
+ }\r
+ /* now *curopt == '\0' */\r
+ }\r
+ }else{\r
+ optarg = NULL;\r
+ if(!*curopt) ++optind;\r
+ }\r
+ return(optopt);\r
+}\r
--- /dev/null
+#!/bin/bash
+
+F=$1
+
+if [ "$F" == "ahx2wav" ]; then
+ gcc -c ahx2wav.c
+ gcc -c getopt.c
+ gcc -lm getopt.o ahx2wav.o -o ahx2wav
+elif [ "$F" == "clean" ]; then
+ rm getopt.o ahx2wav.o ahx2wav
+fi
--- /dev/null
+#include <stdio.h>\r
+#include <string.h>\r
+#include <malloc.h>\r
+\r
+#define WAV_FORMAT_IMA_ADPCM 0x14\r
+#define CD_BUFFER_SIZE 8192\r
+#define CD_BUFFER_CHUNK_SIZE (CD_BUFFER_SIZE >> 2)\r
+\r
+#define u32 unsigned int\r
+#define u16 unsigned short\r
+\r
+#define s32 int\r
+#define s16 short\r
+\r
+#define MAX_PATH 260\r
+\r
+struct CD_WaveHeader\r
+{\r
+ char riff[4]; // 'RIFF'\r
+ u32 size; // Size of the file\r
+ char wave[4]; // 'WAVE'\r
+\r
+ // fmt chunk\r
+ char fmt[4]; // 'fmt '\r
+ u32 fmtSize; // Chunk size\r
+ u16 fmtFormatTag; // Format of this file\r
+ u16 fmtChannels; // Num channels\r
+ u32 fmtSamPerSec; // Samples per second\r
+ u32 fmtBytesPerSec; // Bytes per second\r
+ u16 fmtBlockAlign; // Block alignment\r
+ u16 fmtBitsPerSam; // Bits per sample\r
+\r
+ u16 fmtExtraData; // Number of extra fmt bytes\r
+ u16 fmtExtra; // Samples per block (only for IMA-ADPCM files)\r
+}; // __attribute__ ((packed));\r
+\r
+struct CD_chunkHeader\r
+{\r
+ char name[4];\r
+ u32 size;\r
+}; // __attribute__ ((packed));\r
+\r
+struct CD_Header\r
+{\r
+ s16 firstSample;\r
+ char stepTableIndex;\r
+ char reserved;\r
+}; // __attribute__ ((packed));\r
+\r
+struct CD_decoderFormat\r
+{\r
+ s16 initial;\r
+ unsigned char tableIndex;\r
+ unsigned char test;\r
+ unsigned char sample[1024];\r
+}; // __attribute__ ((packed));\r
+\r
+bool CD_active = false;\r
+CD_WaveHeader CD_waveHeader;\r
+CD_Header CD_blockHeader;\r
+FILE* CD_file;\r
+int CD_fillPos;\r
+bool CD_isPlayingFlag = false;\r
+\r
+s16* CD_audioBuffer;\r
+u32 CD_sampleNum;\r
+s16* CD_decompressionBuffer;\r
+int CD_numLoops;\r
+int CD_blockCount;\r
+int CD_dataChunkStart;\r
+\r
+// These are from Microsoft's document on DVI ADPCM\r
+const int CD_stepTab[ 89 ] =\r
+{\r
+ 7, 8, 9, 10, 11, 12, 13, 14,\r
+ 16, 17, 19, 21, 23, 25, 28, 31,\r
+ 34, 37, 41, 45, 50, 55, 60, 66,\r
+ 73, 80, 88, 97, 107, 118, 130, 143,\r
+ 157, 173, 190, 209, 230, 253, 279, 307,\r
+ 337, 371, 408, 449, 494, 544, 598, 658,\r
+ 724, 796, 876, 963, 1060, 1166, 1282, 1411,\r
+ 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,\r
+ 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,\r
+ 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,\r
+ 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,\r
+ 32767\r
+};\r
+\r
+const int CD_indexTab[ 16 ] = { -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 };\r
+\r
+int main(int argc, char *argv[])\r
+{\r
+ if(argc < 2)\r
+ {\r
+ printf("too few parameters - need a filename\n");\r
+ return 0;\r
+ }\r
+\r
+ char filename[MAX_PATH];\r
+ strcpy(filename, argv[1]);\r
+ FILE* inFile = fopen(filename, "rb");\r
+\r
+ if(inFile == NULL)\r
+ {\r
+ printf("file not found - %s\n", filename);\r
+ return 0;\r
+ }\r
+\r
+ char outfilename[MAX_PATH];\r
+ sprintf(outfilename, "%s.raw", filename);\r
+ FILE* outFile = fopen(outfilename, "wb");\r
+\r
+ if(outFile == NULL)\r
+ {\r
+ printf("%s could not be created\n", outfilename);\r
+ fclose(inFile);\r
+ return 0;\r
+ }\r
+\r
+ fread((void*)&CD_waveHeader, sizeof(CD_waveHeader), 1, inFile);\r
+\r
+ printf("In file:\n");\r
+ printf("Format: %d\n", CD_waveHeader.fmtFormatTag);\r
+ printf("Rate : %d\n", CD_waveHeader.fmtSamPerSec);\r
+ printf("Bits : %d\n", CD_waveHeader.fmtBitsPerSam);\r
+ printf("BlkSz : %d\n", CD_waveHeader.fmtExtra);\r
+\r
+ if((CD_waveHeader.fmtFormatTag != 17) && (CD_waveHeader.fmtFormatTag != 20))\r
+ {\r
+ printf("Wave file is in the wrong format! You must use IMA-ADPCM 4-bit mono.\n");\r
+ return 0;\r
+ }\r
+\r
+ // Skip chunks until we reach the data chunk\r
+ CD_chunkHeader chunk;\r
+ fread((void*)&chunk, sizeof(CD_chunkHeader), 1, inFile);\r
+\r
+ while(!((chunk.name[0] == 'd') && (chunk.name[1] == 'a') && (chunk.name[2] == 't') && (chunk.name[3] == 'a')))\r
+ {\r
+ fseek(inFile, chunk.size, SEEK_CUR);\r
+ fread((void*)&chunk, sizeof(CD_chunkHeader), 1, inFile);\r
+ }\r
+\r
+ int blocksize = CD_waveHeader.fmtBlockAlign - sizeof(CD_blockHeader);\r
+ char* block = (char*)malloc(blocksize);\r
+\r
+ int firstblock = 1;\r
+\r
+ while( !feof(inFile) )\r
+ {\r
+ fread((void*)&CD_blockHeader, sizeof(CD_blockHeader), 1, inFile);\r
+ if(firstblock)\r
+ {\r
+ fwrite((const void*)&CD_blockHeader, sizeof(CD_blockHeader), 1, outFile);\r
+ firstblock = 0;\r
+ }\r
+\r
+ size_t bytesread = fread(block, 1, blocksize, inFile);\r
+ fwrite(block, 1, bytesread, outFile);\r
+ }\r
+\r
+ free(block);\r
+ fclose(inFile);\r
+ fclose(outFile);\r
+}\r
--- /dev/null
+#include <stdio.h>\r
+#include <stdlib.h>\r
+#include <string.h>\r
+#include <stdint.h>\r
+\r
+typedef int32_t LONG;\r
+typedef uint32_t DWORD;\r
+typedef uint8_t BYTE;\r
+typedef uint16_t WORD;\r
+\r
+typedef struct tagBITMAPINFOHEADER {\r
+ DWORD biSize; //4\r
+ LONG biWidth; //8\r
+ LONG biHeight; //12\r
+ WORD biPlanes; //14\r
+ WORD biBitCount; //16\r
+ DWORD biCompression; //20\r
+ DWORD biSizeImage; //24\r
+ LONG biXPelsPerMeter; //28\r
+ LONG biYPelsPerMeter; //32\r
+ DWORD biClrUsed; //36\r
+ DWORD biClrImportant; //40\r
+} __attribute__((packed)) BITMAPINFOHEADER, *PBITMAPINFOHEADER;\r
+\r
+typedef struct tagBITMAPFILEHEADER {\r
+ WORD bfType;\r
+ DWORD bfSize;\r
+ WORD bfReserved1;\r
+ WORD bfReserved2;\r
+ DWORD bfOffBits;\r
+} __attribute__((packed)) BITMAPFILEHEADER, *PBITMAPFILEHEADER;\r
+\r
+typedef unsigned char uchar;\r
+\r
+typedef struct {\r
+ char signature[11];\r
+ uchar colors;\r
+ LONG width;\r
+ LONG height;\r
+ LONG blockheight;\r
+} __attribute__((packed)) TLGHeader;\r
+\r
+typedef struct {\r
+ uchar flag;\r
+ LONG size;\r
+} __attribute__((packed)) BlockHeader;\r
+\r
+void Convert(FILE* input, FILE* output);\r
+\r
+int main(int argc, char** argv)\r
+{\r
+ if (argc < 2){\r
+ printf("%s inputfile outputfile\n", argv[0]);\r
+ return 0;\r
+ }\r
+ char* inputfile = argv[1];\r
+ char* outputfile = argv[2];\r
+\r
+ FILE* f = fopen(inputfile, "rb");\r
+ FILE* o = fopen(outputfile, "wb");\r
+ if (f == NULL || o == NULL){\r
+ fprintf(stderr, "fopen error\n");\r
+ return 1;\r
+ }\r
+\r
+ Convert(f, o);\r
+ fclose(f);\r
+ fclose(o);\r
+ return 0;\r
+}\r
+\r
+\r
+int DecodeLZSS(uchar* out, uchar* in, int insize, uchar* text, int initialr)\r
+{\r
+ int r = initialr;\r
+ unsigned int flags = 0;\r
+ const uchar* inlim = in + insize;\r
+ while(in < inlim)\r
+ {\r
+ if(((flags >>= 1) & 256) == 0)\r
+ {\r
+ flags = *in++ | 0xff00;\r
+ }\r
+ if(flags & 1)\r
+ {\r
+ int mpos = in[0] | ((in[1] & 0xf) << 8);\r
+ int mlen = (in[1] & 0xf0) >> 4;\r
+ in += 2;\r
+ mlen += 3;\r
+ if(mlen == 18) mlen += 0[in++];\r
+\r
+ while(mlen--)\r
+ {\r
+ *out++ = text[r++] = text[mpos++];\r
+ mpos &= (4096 - 1);\r
+ r &= (4096 - 1);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ unsigned char c = 0[in++];\r
+ *out++ = c;\r
+ text[r++] = c;\r
+/* 0[out++] = text[r++] = 0[in++];*/\r
+ r &= (4096 - 1);\r
+ }\r
+ }\r
+ return r;\r
+}\r
+\r
+\r
+\r
+void Convert(FILE* input, FILE* output)\r
+{\r
+ TLGHeader header;\r
+ fread(&header, 1, sizeof(header), input);\r
+\r
+ if(0 != memcmp("TLG5.0\x00raw\x1a", header.signature, sizeof(header.signature))){\r
+ fprintf(stderr, "signature error");\r
+ exit(1);\r
+ }\r
+ if (header.colors != 3 && header.colors != 4){\r
+ fprintf(stderr, "unsupported colors");\r
+ exit(1);\r
+ }\r
+\r
+ // write bitmap header\r
+ BITMAPFILEHEADER bf;\r
+ BITMAPINFOHEADER bi;\r
+ memset(&bf, 0, sizeof(bf));\r
+ memset(&bi, 0, sizeof(bi));\r
+\r
+ bf.bfType = 'B' | ('M' << 8);\r
+ bf.bfOffBits = sizeof(bf) + sizeof(bi);\r
+\r
+ bi.biSize = sizeof(bi);\r
+ bi.biWidth = header.width;\r
+ bi.biHeight = header.height;\r
+ bi.biPlanes = 1;\r
+ bi.biBitCount = header.colors * 8;\r
+\r
+ fwrite(&bf, 1, sizeof(bf), output);\r
+ fwrite(&bi, 1, sizeof(bi), output);\r
+ int imagestart = ftell(output);\r
+\r
+ // skip tlg block size sectionblock\r
+ long blockcount = ((header.height - 1) / header.blockheight) + 1;\r
+ fseek(input, 4*blockcount, SEEK_CUR);\r
+\r
+ uchar* outbuf[4];\r
+ uchar* text = (uchar*)malloc(sizeof(uchar) * 4096);\r
+ memset(text, 0, 4096);\r
+ uchar* inbuf = (uchar*)malloc(sizeof(uchar) * header.blockheight * header.width + 10);\r
+ for (int i=0; i < header.colors; i++){\r
+ outbuf[i] = (uchar*)malloc(sizeof(uchar) * header.blockheight * header.width + 10);\r
+ }\r
+\r
+ int r = 0;\r
+\r
+ uchar *prevline = (uchar*)malloc(sizeof(uchar) * header.width * header.colors);\r
+ memset(prevline, 0, header.width * header.colors);\r
+ for(int y_blk = 0; y_blk < header.height; y_blk += header.blockheight)\r
+ {\r
+ // read file and decompress\r
+ int i;\r
+ for(i = 0; i < header.colors; i++)\r
+ {\r
+ BlockHeader h;\r
+\r
+ fread(&h, 1, sizeof(h), input);\r
+ if(h.flag == 0)\r
+ {\r
+ // modified LZSS compressed data\r
+ fread(inbuf, 1, h.size, input);\r
+ r = DecodeLZSS(outbuf[i], inbuf, h.size, text, r);\r
+ }\r
+ else\r
+ {\r
+ // raw data\r
+ fread(outbuf[i], 1, h.size, input);\r
+ }\r
+ }\r
+\r
+ // compose colors and store\r
+ int y_lim = y_blk + header.blockheight;\r
+ if(y_lim > header.height)\r
+ y_lim = header.height;\r
+\r
+ uchar* outbufp[4];\r
+ for(i = 0; i < header.colors; i++)\r
+ outbufp[i] = outbuf[i];\r
+ for(int y = y_blk; y < y_lim; y++)\r
+ {\r
+ uchar* prevp = prevline;\r
+ uchar pr = 0, pg = 0, pb = 0, pa = 0;\r
+ int x = 0;\r
+ fseek(output, imagestart + header.colors*header.width*(header.height - (y+1)), SEEK_SET);\r
+ switch(header.colors)\r
+ {\r
+ case 3:\r
+ for(pr = 0, pg = 0, pb = 0, x = 0;\r
+ x < header.width; x++)\r
+ {\r
+ int b = outbufp[0][x];\r
+ int g = outbufp[1][x];\r
+ int r = outbufp[2][x];\r
+ b += g; r += g;\r
+ pb += b;\r
+ pg += g;\r
+ pr += r;\r
+\r
+ *prevp += pb; fputc(*prevp, output); prevp++;\r
+ *prevp += pg; fputc(*prevp, output); prevp++;\r
+ *prevp += pr; fputc(*prevp, output); prevp++;\r
+ }\r
+ outbufp[0] += header.width;\r
+ outbufp[1] += header.width;\r
+ outbufp[2] += header.width;\r
+ break;\r
+\r
+ case 4:\r
+ for(pr = 0, pg = 0, pb = 0, pa = 0, x = 0;\r
+ x < header.width; x++)\r
+ {\r
+ int b = outbufp[0][x];\r
+ int g = outbufp[1][x];\r
+ int r = outbufp[2][x];\r
+ int a = outbufp[3][x];\r
+ b += g; r += g;\r
+ pb += b;\r
+ pg += g;\r
+ pr += r;\r
+ pa += a;\r
+ *prevp += pb; fputc(*prevp, output); prevp++;\r
+ *prevp += pg; fputc(*prevp, output); prevp++;\r
+ *prevp += pr; fputc(*prevp, output); prevp++;\r
+ *prevp += pa; fputc(*prevp, output); prevp++;\r
+ }\r
+ outbufp[0] += header.width;\r
+ outbufp[1] += header.width;\r
+ outbufp[2] += header.width;\r
+ outbufp[3] += header.width;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+}\r
--- /dev/null
+\r
+<project name="VNDS" basedir="." default="main">\r
+\r
+<!-- Imports -->\r
+\r
+<!-- Properties -->\r
+\r
+ <property name="build.sysclasspath" value="ignore"/>\r
+ <property name="src.dir" value="src" />\r
+ <property name="bin.dir" value="bin" />\r
+ <property name="dist.dir" value="dist" />\r
+ <property name="tools.dir" value="tools" />\r
+\r
+ <path id="classpath">\r
+ <fileset dir="." includes="lib/**/*.jar" />\r
+ </path>\r
+\r
+ <pathconvert property="jar-classpath-raw" pathsep=" " dirsep="/" refid="classpath">\r
+ <map from="${basedir}/" to="" />\r
+ </pathconvert>\r
+ <property name="jar-classpath" value=". ${jar-classpath-raw}" />\r
+\r
+ <property name="fate-packages" value="nl/weeaboo/vnds/* nl/weeaboo/vnds/installer/** nl/weeaboo/krkr/** nl/weeaboo/vnds/tools/**" />\r
+ <property name="umineko-packages" value="nl/weeaboo/umineko/** nl/weeaboo/vnds/*.* nl/weeaboo/vnds/tools/**" />\r
+ <property name="higurashi-packages" value="nl/weeaboo/vnds/* nl/weeaboo/bgi/** nl/weeaboo/vnds/tools/**" />\r
+ <property name="kirakira-packages" value="nl/weeaboo/vnds/* nl/weeaboo/bgi/** nl/weeaboo/vnds/tools/**" />\r
+ <property name="crosschannel-packages" value="nl/weeaboo/vnds/* nl/weeaboo/cc/** nl/weeaboo/vnds/tools/**" />\r
+ <property name="ever17-packages" value="nl/weeaboo/vnds/* nl/weeaboo/kid/** nl/weeaboo/kid/ever17/** nl/weeaboo/vnds/tools/**" />\r
+ <property name="inganock-packages" value="nl/weeaboo/vnds/* nl/weeaboo/liar/** nl/weeaboo/vnds/tools/**" />\r
+ <property name="sharnoth-packages" value="nl/weeaboo/vnds/* nl/weeaboo/liar/** nl/weeaboo/vnds/tools/**" />\r
+ <property name="never7-packages" value="nl/weeaboo/vnds/* nl/weeaboo/kid/** nl/weeaboo/kid/never7/** nl/weeaboo/vnds/tools/**" />\r
+ \r
+<!-- Tasks -->\r
+\r
+ <macrodef name="dist-converter">\r
+ <attribute name="name" />\r
+ <attribute name="template-name" />\r
+ <attribute name="packages" />\r
+\r
+ <sequential>\r
+ <!-- ZIP source code -->\r
+ <zip basedir="src" destfile="${dist.dir}/src.zip" includes="@{packages}" />\r
+ <delete dir="${dist.dir}/src" />\r
+\r
+ <!-- Add templates -->\r
+ <copy todir="${dist.dir}/template/@{template-name}">\r
+ <fileset dir="template/@{template-name}" />\r
+ </copy>\r
+\r
+ <!-- Rename dist dir -->\r
+ <move todir="@{name}Converter">\r
+ <fileset dir="${dist.dir}"/>\r
+ </move>\r
+ </sequential>\r
+ </macrodef>\r
+ \r
+<!-- Targets -->\r
+\r
+ <target name="init">\r
+ <tstamp />\r
+ </target>\r
+\r
+ <!-- Remove all generated resources -->\r
+ <target name="clean" depends="init">\r
+ <delete dir="${dist.dir}" />\r
+ <delete dir="${bin.dir}" />\r
+ <delete dir="." includes="*.jar *.exe" />\r
+ </target>\r
+\r
+ <target name="compile" depends="init"> \r
+ <mkdir dir="${bin.dir}"/>\r
+ \r
+ <javac srcdir="${src.dir}" destdir="${bin.dir}" encoding="UTF-8" debug="true">\r
+ <classpath refid="classpath" />\r
+ </javac>\r
+\r
+ <!-- Copy resources (embedded images, etc.) to bin dir -->\r
+ <copy todir="${bin.dir}">\r
+ <fileset dir="${src.dir}">\r
+ <exclude name="**/*.java" />\r
+ </fileset>\r
+ </copy>\r
+ </target>\r
+\r
+ <!-- Do a clean compile and generate the main jar -->\r
+ <target name="jar" depends="clean, compile"> \r
+ <jar destfile="NovelManager.jar" basedir="${bin.dir}"\r
+ includes="nl/weeaboo/vnds/* nl/weeaboo/vnds/novelmanager/**">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.vnds.novelmanager.NovelSwitcherGUI"/>\r
+ </manifest>\r
+ </jar>\r
+ <jar destfile="ImageConverter.jar" basedir="${bin.dir}"\r
+ includes="nl/weeaboo/vnds/* nl/weeaboo/vnds/tools/*">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.vnds.tools.ImageConverterGUI"/>\r
+ </manifest>\r
+ </jar>\r
+ <jar destfile="SoundConverter.jar" basedir="${bin.dir}"\r
+ includes="nl/weeaboo/vnds/* nl/weeaboo/vnds/tools/*">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.vnds.tools.SoundConverterGUI"/>\r
+ </manifest>\r
+ </jar>\r
+ <jar destfile="TextureConverter.jar" basedir="${bin.dir}"\r
+ includes="nl/weeaboo/vnds/* nl/weeaboo/vnds/tools/*">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.vnds.tools.TextureConverterGUI"/>\r
+ </manifest>\r
+ </jar>\r
+ <jar destfile="FSNInstaller.jar" basedir="${bin.dir}"\r
+ includes="nl/weeaboo/vnds/* nl/weeaboo/vnds/installer/** nl/weeaboo/krkr/fate/FateInstaller*">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.krkr.fate.FateInstaller"/>\r
+ </manifest>\r
+ </jar>\r
+ <jar destfile="FSNConverter.jar" basedir="${bin.dir}"\r
+ includes="${fate-packages}">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.krkr.fate.ConversionGUI"/>\r
+ </manifest>\r
+ </jar>\r
+\r
+ <!-- Umineko Converter -->\r
+ <jar destfile="UminekoConverter.jar" basedir="${bin.dir}" includes="${umineko-packages}">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.umineko.ConversionGUI"/>\r
+ </manifest>\r
+ </jar>\r
+\r
+ <!-- Higurashi Converter -->\r
+ <jar destfile="HigurashiConverter.jar" basedir="${bin.dir}" includes="${higurashi-packages}">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.bgi.higurashi.ConversionGUI"/>\r
+ </manifest>\r
+ </jar>\r
+\r
+ <!-- KiraKira Converter -->\r
+ <jar destfile="KiraKiraConverter.jar" basedir="${bin.dir}" includes="${kirakira-packages}">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.bgi.kirakira.ConversionGUI"/>\r
+ </manifest>\r
+ </jar>\r
+\r
+ <!-- CrossChannel Converter -->\r
+ <jar destfile="CrossChannelConverter.jar" basedir="${bin.dir}" includes="${crosschannel-packages}">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.cc.ConversionGUI"/>\r
+ </manifest>\r
+ </jar>\r
+\r
+ <!-- Ever17 Converter -->\r
+ <jar destfile="Ever17Converter.jar" basedir="${bin.dir}" includes="${ever17-packages}">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.kid.ever17.ConversionGUI"/>\r
+ </manifest>\r
+ </jar>\r
+\r
+ <!-- Never7 Converter -->\r
+ <jar destfile="Never7Converter.jar" basedir="${bin.dir}" includes="${never7-packages}">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.kid.never7.ConversionGUI"/>\r
+ </manifest>\r
+ </jar>\r
+\r
+ <!-- Inganock Converter -->\r
+ <jar destfile="InganockConverter.jar" basedir="${bin.dir}" includes="${inganock-packages}">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.liar.inganock.ConversionGUI"/>\r
+ </manifest>\r
+ </jar>\r
+\r
+ <!-- Sharnoth Converter -->\r
+ <jar destfile="SharnothConverter.jar" basedir="${bin.dir}" includes="${sharnoth-packages}">\r
+ <manifest>\r
+ <attribute name="Class-Path" value="${jar-classpath}"/>\r
+ <attribute name="Main-Class" value="nl.weeaboo.liar.sharnoth.ConversionGUI"/>\r
+ </manifest>\r
+ </jar>\r
+\r
+ </target>\r
+\r
+ <target name="dist" depends="clean, jar">\r
+ <delete dir="${dist.dir}" />\r
+ <mkdir dir="${dist.dir}" />\r
+\r
+ <copy todir="${dist.dir}/lib">\r
+ <fileset dir="lib" />\r
+ </copy>\r
+\r
+ <copy todir="${dist.dir}/${tools.dir}">\r
+ <fileset dir="${tools.dir}" />\r
+ </copy>\r
+\r
+ <copy todir="${dist.dir}">\r
+ <fileset dir=".">\r
+ <include name="license.txt" />\r
+ <include name="build.xml" />\r
+ </fileset>\r
+ </copy>\r
+ </target>\r
+\r
+ <target name="dist-sharnoth" depends="dist">\r
+ <dist-converter name="Sharnoth" template-name="sharnoth" packages="${sharnoth-packages}" />\r
+ </target>\r
+\r
+ <target name="dist-inganock" depends="dist">\r
+ <dist-converter name="Inganock" template-name="inganock" packages="${inganock-packages}" />\r
+ </target>\r
+ \r
+ <target name="dist-ever17" depends="dist">\r
+ <dist-converter name="Ever17" template-name="ever17" packages="${ever17-packages}" />\r
+ </target>\r
+ \r
+ <target name="dist-never7" depends="dist">\r
+ <dist-converter name="Never7" template-name="never7" packages="${never7-packages}" />\r
+ </target>\r
+ \r
+ <target name="dist-crosschannel" depends="dist">\r
+ <dist-converter name="CrossChannel" template-name="crosschannel" packages="${crosschannel-packages}" />\r
+ </target>\r
+\r
+ <target name="dist-kirakira" depends="dist">\r
+ <dist-converter name="KiraKira" template-name="kirakira" packages="${kirakira-packages}" />\r
+ </target>\r
+\r
+ <target name="dist-higurashi" depends="dist">\r
+ <!-- Add Kai template -->\r
+ <copy todir="${dist.dir}/template/higurashi-kai">\r
+ <fileset dir="template/higurashi-kai" />\r
+ </copy>\r
+\r
+ <dist-converter name="Higurashi" template-name="higurashi" packages="${higurashi-packages}" />\r
+ </target>\r
+\r
+ <target name="dist-umineko" depends="dist">\r
+ <!-- Add Chiru templates -->\r
+ <copy todir="${dist.dir}/template/umineko-chiru">\r
+ <fileset dir="template/umineko-chiru" />\r
+ </copy>\r
+\r
+ <dist-converter name="Umineko" template-name="umineko" packages="${umineko-packages}" />\r
+ </target>\r
+\r
+ <target name="dist-fate" depends="dist">\r
+ <copy todir="${dist.dir}" file="FSNInstaller.jar" />\r
+\r
+ <dist-converter name="FSN" template-name="fate" packages="${fate-packages}" />\r
+ </target>\r
+ \r
+ <target name="dist-vnds-tools" depends="dist">\r
+\r
+ <!-- ZIP source code -->\r
+ <zip basedir="src" destfile="${dist.dir}/src.zip"\r
+ includes="nl/weeaboo/vnds/tools/*" />\r
+ <delete dir="${dist.dir}/src" />\r
+\r
+ <!-- Include additional file(s) -->\r
+ <copy todir="${dist.dir}">\r
+ <fileset dir=".">\r
+ <include name="ImageConverter.jar" />\r
+ <include name="SoundConverter.jar" />\r
+ <include name="TextureConverter.jar" />\r
+ </fileset>\r
+ </copy>\r
+\r
+ <!-- Rename dist dir -->\r
+ <move todir="vnds-tools">\r
+ <fileset dir="${dist.dir}"/>\r
+ </move>\r
+\r
+ </target>\r
+ \r
+ <target name="main" depends="jar" />\r
+\r
+</project>\r
--- /dev/null
+package nl.weeaboo.krkr;\r
+\r
+@SuppressWarnings("serial")\r
+public class InvalidLayeringException extends RuntimeException {\r
+\r
+ //Functions\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr;\r
+\r
+import java.io.BufferedOutputStream;\r
+import java.io.BufferedReader;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStreamReader;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.HashMap;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.Set;\r
+import java.util.TreeMap;\r
+import java.util.TreeSet;\r
+\r
+import nl.weeaboo.common.Dim;\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.settings.INIFile;\r
+import nl.weeaboo.vnds.FileExts;\r
+import nl.weeaboo.vnds.FileMapper;\r
+import nl.weeaboo.vnds.Log;\r
+import nl.weeaboo.vnds.Patcher;\r
+\r
+public class KiriKiriConverter {\r
+ \r
+ private String sourceFileEncoding = "UTF-16";\r
+ protected boolean showOutput = false;\r
+ protected boolean ignoreText = false;\r
+ \r
+ //Patching\r
+ protected Patcher patcher;\r
+ protected Map<String, String> appendMap;\r
+ protected Map<String, Map<Integer, String>> patchPreMap;\r
+ protected Map<String, Map<Integer, String>> patchPostMap;\r
+ \r
+ private File scriptFolder;\r
+ private File outFolder;\r
+ private File infoFolder;\r
+ \r
+ private int imageW, imageH;\r
+ protected MacroParser macroParser;\r
+ protected final FileExts fileExts;\r
+ \r
+ private FileMapper filenameMapper;\r
+ private Set<File> scriptFilesWritten;\r
+ private Set<String> unhandledTextMacros;\r
+ private Set<String> unhandledMacros;\r
+ private List<String> parseErrors;\r
+ private List<String> layeringErrors;\r
+ \r
+ public KiriKiriConverter(File srcF, File scriptF, File dstF) {\r
+ this.scriptFolder = scriptF;\r
+ this.outFolder = new File(dstF, "script");\r
+ this.infoFolder = new File(dstF, "_info");\r
+\r
+ Dim d = new Dim(256, 192);\r
+ try {\r
+ INIFile imgIni = new INIFile();\r
+ imgIni.read(new File(dstF, "img.ini"));\r
+ d = new Dim(imgIni.getInt("width", d.w),\r
+ imgIni.getInt("height", d.h));\r
+ } catch (IOException ioe) {\r
+ Log.w("Error reading img.ini", ioe);\r
+ } \r
+ imageW = d.w;\r
+ imageH = d.h;\r
+ \r
+ FileExts exts;\r
+ try {\r
+ exts = FileExts.fromFile(new File(dstF, "exts.ini"));\r
+ } catch (IOException e) {\r
+ //Ignore\r
+ exts = new FileExts();\r
+ }\r
+ fileExts = exts;\r
+ \r
+ macroParser = new MacroParser(this);\r
+ }\r
+ public void convert() { \r
+ float scale = Math.min(256f/imageW, 192f/imageH);\r
+ macroParser.setScale(scale);\r
+\r
+ outFolder.mkdirs();\r
+ infoFolder.mkdirs();\r
+ \r
+ appendMap = new HashMap<String, String>();\r
+ patchPreMap = new HashMap<String, Map<Integer, String>>();\r
+ patchPostMap = new HashMap<String, Map<Integer, String>>();\r
+ \r
+ patcher = createPatcher();\r
+ patcher.patchPre(patchPreMap);\r
+ patcher.patchPost(patchPostMap);\r
+ patcher.fillAppendMap(appendMap);\r
+ \r
+ filenameMapper = new FileMapper();\r
+ scriptFilesWritten = new TreeSet<File>();\r
+ unhandledTextMacros = new TreeSet<String>();\r
+ unhandledMacros = new TreeSet<String>();\r
+ parseErrors = new ArrayList<String>();\r
+ layeringErrors = new ArrayList<String>();\r
+ \r
+ try {\r
+ filenameMapper.load(infoFolder+"/filenames.txt");\r
+ } catch (IOException e) { }\r
+ \r
+ Map<String, File> scriptFolderContents = new TreeMap<String, File>();\r
+ FileUtil.collectFiles(scriptFolderContents, scriptFolder, false);\r
+ for (int n = 1; n <= getNumberOfPasses(); n++) {\r
+ for (Entry<String, File> entry : scriptFolderContents.entrySet()) {\r
+ scriptConvert(n, entry.getKey(), entry.getValue());\r
+ }\r
+ }\r
+ \r
+ //Generate script files for each file in the append map, so appending to a non-existing file\r
+ //generates a new file.\r
+ for (Entry<String, String> entry : appendMap.entrySet()) {\r
+ String key = entry.getKey();\r
+ String value = entry.getValue();\r
+ if (value == null) {\r
+ continue;\r
+ }\r
+ \r
+ if (!scriptFilesWritten.contains(new File(createOutputPath(key)))) {\r
+ try {\r
+ LinkedList<String> list = new LinkedList<String>();\r
+ list.add(value);\r
+ writeScript(list, key);\r
+ } catch (IOException ioe) {\r
+ ioe.printStackTrace();\r
+ }\r
+ }\r
+ }\r
+\r
+ try { filenameMapper.save(infoFolder+"/filenames.txt"); } catch (IOException e) { Log.w("Exception", e); }\r
+ try { saveStringSet(unhandledTextMacros, infoFolder+"/unhandled_textmacro.txt"); } catch (IOException e) { Log.w("Exception", e); }\r
+ try { saveStringSet(unhandledMacros, infoFolder+"/unhandled_macro.txt"); } catch (IOException e) { Log.w("Exception", e); }\r
+ try { saveStringSet(parseErrors, infoFolder+"/parse_errors.txt"); } catch (IOException e) { Log.w("Exception", e); }\r
+ try { saveStringSet(layeringErrors, infoFolder+"/layering_errors.txt"); } catch (IOException e) { Log.w("Exception", e); }\r
+ }\r
+ \r
+ //Functions \r
+ protected static void saveStringSet(Collection<String> strings, String filename) throws IOException {\r
+ BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(filename), 512*1024);\r
+ try {\r
+ for (String s : strings) {\r
+ fout.write(s.getBytes("UTF-8"));\r
+ fout.write('\n');\r
+ }\r
+ fout.flush();\r
+ } finally {\r
+ fout.close();\r
+ }\r
+ }\r
+ \r
+ protected Patcher createPatcher() {\r
+ return new Patcher();\r
+ }\r
+ \r
+ protected void scriptConvert(int pass, String relpath, File file) { \r
+ if (pass == getNumberOfPasses() && file.getName().endsWith("ks")) {\r
+ try {\r
+ processKSFile(file, file.getName());\r
+ } catch (IOException e) {\r
+ Log.w("Exception during script convert", e);\r
+ }\r
+ }\r
+ }\r
+ protected void processKSFile(File file, String patchMapKey) throws IOException { \r
+ Map<Integer, String> patchPreMap = this.patchPreMap.remove(patchMapKey);\r
+ if (patchPreMap == null) patchPreMap = new HashMap<Integer, String>();\r
+ Map<Integer, String> patchPostMap = this.patchPostMap.remove(patchMapKey);\r
+ if (patchPostMap == null) patchPostMap = new HashMap<Integer, String>();\r
+ \r
+ List<String> list = new ArrayList<String>();\r
+ BufferedReader in = new BufferedReader(new InputStreamReader(\r
+ new FileInputStream(file), getSourceFileEncoding()));\r
+\r
+ macroParser.reset();\r
+ \r
+ String line;\r
+ int t = 1;\r
+ while ((line = in.readLine()) != null) {\r
+ if (patchPreMap.get(t) != null) {\r
+ line = patchPreMap.get(t);\r
+ }\r
+ \r
+ if (patchPostMap.get(t) != null) {\r
+ list.add(macroParser.flush(file.getName(), t, line));\r
+ list.add(patchPostMap.get(t));\r
+ } else if (line.startsWith("@")) {\r
+ //Macro\r
+ String val = macroParser.parseMacro(file.getName(), t, line);\r
+ if (val != null && val.length() > 0) {\r
+ list.add(val);\r
+ }\r
+ } else { \r
+ if (line.startsWith("*") || line.startsWith(";")) {\r
+ //Label or comment\r
+ list.add("#" + line);\r
+ } else {\r
+ //Text\r
+ String parsed = parseText(file.getName(), t, line);\r
+ if (parsed != null) {\r
+ list.add((ignoreText ? "#" : "") + parsed);\r
+ }\r
+ }\r
+ } \r
+ t++;\r
+ }\r
+ in.close();\r
+\r
+ String appendData = appendMap.remove(patchMapKey);\r
+ //System.out.println(file.getName() + " " + patchMapKey + " " + (appendData != null ? appendData.length() : 0));\r
+ if (appendData != null) {\r
+ list.add(appendData);\r
+ }\r
+ \r
+ //Write to disc\r
+ writeScript(list, file.getName()); \r
+ }\r
+\r
+ protected void writeScript(List<String> list, String filename) throws IOException {\r
+ String path = createOutputPath(filename);\r
+ \r
+ File outFile = new File(path);\r
+ scriptFilesWritten.add(outFile);\r
+ outFile.getParentFile().mkdirs();\r
+ \r
+ BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outFile), 512*1024);\r
+ for (String s : list) {\r
+ byte bytes[] = s.getBytes("UTF-8");\r
+ if (bytes.length > 0 && bytes[0] != '#') {\r
+ int start = 0;\r
+ while (start < bytes.length) {\r
+ if (bytes[start] != ' ' || bytes[start] != '\t' || bytes[start] != '\n') {\r
+ break;\r
+ }\r
+ start++;\r
+ } \r
+ if (start < bytes.length) {\r
+ int end = bytes.length;\r
+ while (end > start) {\r
+ if (bytes[end-1] != ' ' || bytes[end-1] != '\t' || bytes[end-1] != '\n') {\r
+ break;\r
+ }\r
+ end--;\r
+ }\r
+ \r
+ if (end-start > 0) {\r
+ out.write(bytes, start, end-start);\r
+ out.write('\n');\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ out.flush();\r
+ out.close();\r
+ \r
+ if (showOutput) {\r
+ System.out.println("Writing: " + path);\r
+ }\r
+ }\r
+ \r
+ protected String parseText(String filename, int lineNumber, String line) { \r
+ //Save+Remove macros\r
+ int index = 0;\r
+ while ((index = line.indexOf('[')) >= 0) {\r
+ int index2 = index+1;\r
+ boolean inQuotes = false;\r
+ while (index2 < line.length()) {\r
+ if (line.charAt(index2) == '\"') {\r
+ inQuotes = !inQuotes;\r
+ } else if (line.charAt(index2) == '\\') {\r
+ index2++;\r
+ } else if (line.charAt(index2) == ']') {\r
+ if (!inQuotes) {\r
+ index2++;\r
+ break;\r
+ }\r
+ }\r
+ index2++;\r
+ }\r
+ \r
+ String macro = line.substring(index, index2);\r
+ String insert = macroParser.parseTextMacro(filename, lineNumber, macro);\r
+\r
+ line = line.substring(0, index) + insert + line.substring(index + macro.length(), line.length());\r
+ index += insert.length();\r
+ }\r
+\r
+ //line = line.replaceAll("\\[([^\\[])*?\\]", "");\r
+ return line.trim();\r
+ }\r
+\r
+ public String addRes(String prefix, String filename) {\r
+ return filenameMapper.add(prefix+filename.toLowerCase());\r
+ }\r
+ \r
+ protected String createOutputPath(String filename) {\r
+ return new File(outFolder, StringUtil.stripExtension(filename) + ".scr").getAbsolutePath(); \r
+ }\r
+ \r
+ public static String repeatString(String pattern, int times) {\r
+ StringBuilder sb = new StringBuilder();\r
+ for (int n = 0; n < times; n++) {\r
+ sb.append(pattern);\r
+ }\r
+ return sb.toString();\r
+ }\r
+ \r
+ public void addUnhandledTextMacro(String macro) {\r
+ unhandledTextMacros.add(macro);\r
+ } \r
+ public void addUnhandledMacro(String macro) {\r
+ unhandledMacros.add(macro);\r
+ } \r
+ public void addParseError(String errorString) {\r
+ parseErrors.add(errorString);\r
+ }\r
+ public void addLayeringError(String errorString) {\r
+ layeringErrors.add(errorString);\r
+ }\r
+ \r
+ //Getters\r
+ public String getSourceFileEncoding() { return sourceFileEncoding; }\r
+ public File getScriptFolder() { return scriptFolder; }\r
+ public File getOutputFolder() { return outFolder; }\r
+ public File getInfoFolder() { return infoFolder; }\r
+ public int getNumberOfPasses() { return 1; }\r
+ public int getImageW() { return imageW; }\r
+ public int getImageH() { return imageH; }\r
+\r
+ //Setters\r
+ public void setSourceFileEncoding(String enc) {\r
+ this.sourceFileEncoding = enc;\r
+ }\r
+ \r
+ //Append Map\r
+ public String createJump(String... options) {\r
+ StringBuilder sb1 = new StringBuilder("choice ");\r
+ StringBuilder sb2 = new StringBuilder();\r
+ \r
+ int t = 1;\r
+ for (String s : options) {\r
+ String part[] = s.split("\\|");\r
+ if (t > 1) {\r
+ sb1.append("|");\r
+ }\r
+ sb1.append(part[0]);\r
+ \r
+ sb2.append("if selected == " + t + "\n");\r
+ sb2.append(" jump " + part[1] + "\n");\r
+ sb2.append("fi\n");\r
+ t++;\r
+ }\r
+ return sb1.toString() + "\n" + sb2.toString();\r
+ }\r
+}\r
--- /dev/null
+package nl.weeaboo.krkr;\r
+\r
+import java.io.IOException;\r
+import java.util.HashSet;\r
+import java.util.Map;\r
+import java.util.Set;\r
+import java.util.Map.Entry;\r
+\r
+\r
+public class MacroHandler {\r
+\r
+ protected KiriKiriConverter krkr;\r
+ protected MacroParser mp; \r
+ protected Set<String> macroIgnoreList;\r
+ \r
+ public MacroHandler(KiriKiriConverter krkr, MacroParser mp) {\r
+ this.krkr = krkr;\r
+ this.mp = mp;\r
+ \r
+ macroIgnoreList = new HashSet<String>();\r
+ }\r
+ \r
+ //Functions\r
+ public void reset() { \r
+ }\r
+ public String flush() {\r
+ return "";\r
+ }\r
+ \r
+ protected void ignore(String macro) {\r
+ macroIgnoreList.add(macro);\r
+ }\r
+ \r
+ public String process(String macro, Map<String, String> params) throws IOException { \r
+ if (macroIgnoreList.contains(macro)) {\r
+ return "";\r
+ }\r
+ return null;\r
+ }\r
+ \r
+ protected String macroToString(String macro, Map<String, String> params) {\r
+ StringBuilder sb = new StringBuilder(macro);\r
+ for (Entry<String, String> entry : params.entrySet()) {\r
+ sb.append(" " + entry.getKey() + "=" + entry.getValue());\r
+ }\r
+ return sb.toString();\r
+ }\r
+ \r
+ public int readInt(String s, int defaultValue) {\r
+ try {\r
+ return Integer.parseInt(s);\r
+ } catch (Exception e) { }\r
+ return defaultValue;\r
+ }\r
+\r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr;\r
+\r
+import java.awt.image.BufferedImage;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.HashMap;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+import javax.imageio.ImageIO;\r
+\r
+import nl.weeaboo.collections.Tuple2;\r
+import nl.weeaboo.vnds.Log;\r
+\r
+public class MacroParser {\r
+ \r
+ private static final int maxImageCacheSize = 8;\r
+ private boolean disableSprites = false;\r
+\r
+ public static String R_FOREGROUND = "foreground/";\r
+ public static String R_BACKGROUND = "background/";\r
+ public static String R_SOUND = "sound/";\r
+\r
+ protected KiriKiriConverter krkr;\r
+ protected float scale;\r
+ protected List<MacroHandler> macroHandlers;\r
+ protected List<MacroHandler> textMacroHandlers;\r
+ \r
+ protected boolean checkForLayeringErrors = true; \r
+ protected boolean blackedOut;\r
+ protected String currentBG;\r
+ protected Sprite slots[] = new Sprite[10];\r
+ \r
+ private List<Tuple2<String, BufferedImage>> imageCache;\r
+\r
+ public MacroParser(KiriKiriConverter krkr) {\r
+ this.krkr = krkr;\r
+ \r
+ scale = 1f;\r
+ macroHandlers = new ArrayList<MacroHandler>();\r
+ textMacroHandlers = new ArrayList<MacroHandler>();\r
+ imageCache = new LinkedList<Tuple2<String, BufferedImage>>();\r
+ }\r
+ \r
+ //Functions\r
+ public void addMacroHandler(MacroHandler mh) {\r
+ macroHandlers.add(mh);\r
+ }\r
+ public void addTextMacroHandler(MacroHandler mh) {\r
+ textMacroHandlers.add(mh);\r
+ }\r
+ \r
+ public String flush(String filename, int lineNumber, String line) {\r
+ List<String> lines = new ArrayList<String>();\r
+ try {\r
+ for (MacroHandler mh : macroHandlers) {\r
+ String s = mh.flush();\r
+ if (s != null && s.length() > 0) lines.add(s);\r
+ }\r
+ } catch (InvalidLayeringException ile) {\r
+ krkr.addLayeringError(String.format("%s:%d ## %s", filename, lineNumber, line));\r
+ }\r
+ \r
+ StringBuilder sb = new StringBuilder();\r
+ for (String s : lines) {\r
+ if (sb.length() > 0) {\r
+ sb.append("\n");\r
+ }\r
+ sb.append(s);\r
+ }\r
+ return sb.toString();\r
+ }\r
+ \r
+ public void reset() { \r
+ blackedOut = false;\r
+ currentBG = "special/blackout.jpg";\r
+ imageCache.clear();\r
+ clearSlots();\r
+ \r
+ for (MacroHandler mh : macroHandlers) {\r
+ mh.reset();\r
+ }\r
+ for (MacroHandler mh : textMacroHandlers) {\r
+ mh.reset();\r
+ }\r
+ }\r
+ \r
+ public void clearSlots() {\r
+ for (int n = 0; n < slots.length; n++) {\r
+ slots[n] = null;\r
+ }\r
+ }\r
+\r
+ public String restoreSlots(Sprite ss[]) {\r
+ return restoreSlots(currentBG, ss);\r
+ }\r
+ public String restoreSlots(String newBG, final Sprite ss[]) { \r
+ boolean clash = false; //Is it necessary to clear the screen?\r
+ \r
+ /*\r
+ for (int n = 0; n < slots.length; n++) {\r
+ if (slots[n] != null) {\r
+ if (ss[n] == null || !slots[n].equals(ss[n])) {\r
+ clash = true;\r
+ break;\r
+ } \r
+ } else {\r
+ if (ss[n] != null) {\r
+ //If this sprite should be drawn underneath another sprite -> clash = true\r
+ for (int i = 0; i < slots.length; i++) {\r
+ //If should \r
+ if (slots[i] != null && ss[n].z < slots[i].z) {\r
+ //Check if ranges on the x-axis overlap\r
+ if ((slots[i].x >= ss[n].x && slots[i].x < ss[n].x + ss[n].w)\r
+ || (slots[i].x + slots[i].w >= ss[n].x && slots[i].x + slots[i].w < ss[n].x + ss[n].w))\r
+ {\r
+ clash = true;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ */\r
+ clash = true;\r
+ \r
+ String pre = (disableSprites ? "#" : "");\r
+ String text = ""; \r
+ \r
+ if (!blackedOut) {\r
+ boolean hasNonNullSprites = false;\r
+ for (Sprite s : ss) {\r
+ if (s != null) {\r
+ hasNonNullSprites = true;\r
+ break;\r
+ }\r
+ }\r
+ \r
+ if (checkForLayeringErrors && currentBG.equals("special/blackout.jpg")\r
+ && hasNonNullSprites)\r
+ {\r
+ throw new InvalidLayeringException();\r
+ }\r
+ \r
+ if (clash || !newBG.equals(currentBG)) {\r
+ currentBG = newBG;\r
+ text += pre + "bgload " + currentBG; \r
+ } \r
+ \r
+ List<Integer> indexMapping = new ArrayList<Integer>();\r
+ for (int n = 0; n < ss.length; n++) {\r
+ indexMapping.add(n);\r
+ }\r
+ Collections.sort(indexMapping, new Comparator<Integer>() {\r
+ public int compare(Integer i1, Integer i2) {\r
+ if (ss[i1] == null) {\r
+ return -1;\r
+ } else if (ss[i2] == null) {\r
+ return 1;\r
+ }\r
+ return (ss[i1].z > ss[i2].z ? 1 : (ss[i1].z == ss[i2].x ? 0 : -1));\r
+ }\r
+ });\r
+ \r
+ for (int x = 0; x < indexMapping.size(); x++) {\r
+ int n = indexMapping.get(x);\r
+ if (n < 0 || n >= slots.length) {\r
+ continue;\r
+ }\r
+ \r
+ if (clash || (ss[n] != null && !ss[n].equals(slots[n]))) {\r
+ Sprite s = ss[n];\r
+ if (s != null) {\r
+ if (text.length() > 0) {\r
+ text += "\n";\r
+ }\r
+ text += pre + "setimg " + s.image + " " + s.x + " " + s.y;\r
+ }\r
+ }\r
+ }\r
+ } else {\r
+ text = "#blackedout";\r
+ }\r
+ \r
+ slots = ss; \r
+ \r
+ return text;\r
+ }\r
+\r
+ public String parseTextMacro(String filename, int lineNumber, String macro) {\r
+ return parse(filename, lineNumber, 1, macro.substring(1, macro.length()-1));\r
+ }\r
+ public String parseMacro(String filename, int lineNumber, String line) {\r
+ return parse(filename, lineNumber, 0, line.substring(1));\r
+ }\r
+ protected String parse(String filename, int lineNumber, int parseType, String line) {\r
+ int index = line.indexOf(' ');\r
+ if (index < 0) index = line.length();\r
+ \r
+ String macro = line.substring(0, index);\r
+ \r
+ Map<String, String> params = new HashMap<String, String>();\r
+ \r
+ boolean inQuotes = false;\r
+ int mode = 0; \r
+ StringBuilder tempName = new StringBuilder();\r
+ StringBuilder tempValue = new StringBuilder();\r
+ for (int n = index + 1; n < line.length(); n++) {\r
+ char c = line.charAt(n);\r
+ \r
+ if (mode == 0) {\r
+ if (c == '=') {\r
+ mode = 1;\r
+ } else {\r
+ tempName.append(c);\r
+ }\r
+ } else if (mode == 1 && !Character.isWhitespace(c)) {\r
+ mode = 2;\r
+ } \r
+ if (mode == 2) {\r
+ if (inQuotes && c == '\"') {\r
+ inQuotes = false;\r
+ }\r
+ if (tempValue.length() == 0 && c == '\"') {\r
+ inQuotes = true;\r
+ }\r
+ \r
+ if (inQuotes || !Character.isWhitespace(c)) {\r
+ tempValue.append(c);\r
+ }\r
+ if ((!inQuotes && Character.isWhitespace(c)) || n >= line.length() - 1) {\r
+ String value = tempValue.toString().trim();\r
+ if (value.charAt(0) == '\"') {\r
+ value = value.substring(1, value.length()-1);\r
+ }\r
+ \r
+ params.put(tempName.toString().trim(), value);\r
+ tempName.delete(0, tempName.length());\r
+ tempValue.delete(0, tempValue.length());\r
+ mode = 0;\r
+ inQuotes = false;\r
+ }\r
+ }\r
+ }\r
+ \r
+ Collection<MacroHandler> hs = (parseType == 1 ? textMacroHandlers : macroHandlers);\r
+ for (MacroHandler handler : hs) {\r
+ try {\r
+ String result = handler.process(macro, params);\r
+ if (result != null) {\r
+ while (parseType != 1 && result.endsWith("\n")) {\r
+ result = result.substring(0, result.length()-1);\r
+ }\r
+ return result;\r
+ }\r
+ } catch (InvalidLayeringException ile) {\r
+ krkr.addLayeringError(String.format("%s:%d ## %s", filename, lineNumber, line));\r
+ return "#hidden by layering";\r
+ } catch (Exception e) {\r
+ String error = String.format("Error Parsing line (%s:%d) ## %s :: %s", filename, lineNumber, line, e.toString());\r
+ krkr.addParseError(error);\r
+ Log.w(error);\r
+ }\r
+ }\r
+ \r
+ if (parseType == 1) {\r
+ krkr.addUnhandledTextMacro(macro);\r
+ return "";\r
+ } else {\r
+ krkr.addUnhandledMacro(macro);\r
+ return "#" + line;\r
+ }\r
+ }\r
+ \r
+ public Sprite createSprite(String oldName, String newName, int pos, int z) throws IOException {\r
+ oldName = oldName.toLowerCase();\r
+ int x = 0;\r
+ int y = 0;\r
+\r
+ BufferedImage image = null;\r
+ for (Tuple2<String, BufferedImage> entry : imageCache) {\r
+ if (entry.x.equals(oldName)) {\r
+ image = entry.y;\r
+ break;\r
+ }\r
+ }\r
+ if (image == null) {\r
+ image = ImageIO.read(new File(krkr.getOutputFolder()+"/../foreground/"+oldName));\r
+ while (imageCache.size() >= maxImageCacheSize) {\r
+ imageCache.remove(0);\r
+ }\r
+ imageCache.add(Tuple2.newTuple(oldName, image));\r
+ }\r
+ \r
+ int iw = Math.round(scale * image.getWidth());\r
+ int ih = (int)Math.floor(scale * image.getHeight());\r
+ //System.out.println(scale + " " + image.getWidth() + "x" + image.getHeight() + " -> " + iw + "x" + ih);\r
+ \r
+ y = 192 - ih;\r
+ \r
+ if (pos == 0) {\r
+ x = (256 - iw) / 2;\r
+ } else if (pos == 1) {\r
+ x = 256*3/10 - iw/2;\r
+ } else if (pos == 2) {\r
+ x = 256*7/10 - iw/2;\r
+ } else if (pos == 3) {\r
+ x = 256/4 - iw/2;\r
+ } else if (pos == 4) {\r
+ x = 256*3/4 - iw/2;\r
+ } \r
+\r
+ return new Sprite(x, y, z, newName, iw);\r
+ }\r
+ \r
+ public int parsePosValue(String value) {\r
+ int pos = 0; \r
+\r
+ if (value.equals("rc") || value.equals("rightcenter")) {\r
+ pos = 2;\r
+ } else if (value.equals("lc") || value.equals("leftcenter")) {\r
+ pos = 1;\r
+ } else if (value.startsWith("r")) {\r
+ pos = 4;\r
+ } else if (value.startsWith("l")) {\r
+ pos = 3;\r
+ } else if (value.equals("all")) {\r
+ pos = -1;\r
+ }\r
+ return pos;\r
+ }\r
+ \r
+ //Getters\r
+ public String getCurrentBG() { return currentBG; }\r
+ public Sprite[] getSlotsCopy() { return slots.clone(); }\r
+\r
+ //Setters\r
+ public String setBG(String s) {\r
+ if (currentBG != null && currentBG.equals(s)) {\r
+ return "";\r
+ }\r
+ \r
+ blackedOut = false; \r
+ currentBG = s;\r
+ clearSlots();\r
+ return "bgload " + s;\r
+ }\r
+ public void setBlackedOut(boolean b) {\r
+ blackedOut = b;\r
+ }\r
+ public void setScale(float s) {\r
+ scale = s;\r
+ }\r
+\r
+}\r
--- /dev/null
+package nl.weeaboo.krkr;\r
+\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.util.Map.Entry;\r
+import java.util.zip.CRC32;\r
+import java.util.zip.ZipEntry;\r
+import java.util.zip.ZipException;\r
+import java.util.zip.ZipOutputStream;\r
+\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.vnds.FileMapper;\r
+import nl.weeaboo.vnds.Log;\r
+\r
+public class Packer {\r
+ \r
+ public Packer() {\r
+ }\r
+ \r
+ //Functions\r
+ \r
+ public void process(File currentFolder, File targetFolder) {\r
+ cleanAction(targetFolder);\r
+ targetFolder.mkdirs(); \r
+ \r
+ System.out.println("Copying files...");\r
+\r
+ copyAction(currentFolder, targetFolder);\r
+ \r
+ try {\r
+ FileMapper mapper = new FileMapper();\r
+ mapper.load(currentFolder.getAbsolutePath()+"/_info/filenames.txt");\r
+ modifyNameMapping(mapper);\r
+ \r
+ int t = 0;\r
+ for (Entry<String, String> entry : mapper) {\r
+ if (processEntry(currentFolder, targetFolder, entry.getKey(), entry.getValue())) {\r
+ t++;\r
+ if ((t & 0xFF) == 0) {\r
+ System.out.printf("Files Copied: %d\n", t);\r
+ }\r
+ }\r
+ }\r
+\r
+ System.out.println("Zipping files...");\r
+\r
+ zip(new File(targetFolder + "/foreground"), new File(targetFolder + "/foreground.zip"));\r
+ zip(new File(targetFolder + "/background"), new File(targetFolder + "/background.zip"));\r
+ zip(new File(targetFolder + "/script"), new File(targetFolder + "/script.zip"));\r
+ zip(new File(targetFolder + "/sound"), new File(targetFolder + "/sound.zip"));\r
+ } catch (IOException ioe) {\r
+ ioe.printStackTrace();\r
+ }\r
+ }\r
+\r
+ protected void modifyNameMapping(FileMapper mapper) {\r
+ }\r
+ \r
+ protected void cleanAction(File targetFolder) {\r
+ if (targetFolder.exists()) {\r
+ FileUtil.deleteFolder(targetFolder);\r
+ if (targetFolder.exists()) {\r
+ throw new RuntimeException("Unable to delete to target folder");\r
+ }\r
+ }\r
+ }\r
+ \r
+ protected void copyAction(File currentFolder, File targetFolder) {\r
+ copy(new File(currentFolder + "/foreground/special"), new File(targetFolder.getAbsolutePath() + "/foreground/"));\r
+ copy(new File(currentFolder + "/background/special"), new File(targetFolder.getAbsolutePath() + "/background/"));\r
+ copy(new File(currentFolder + "/sound/special"), new File(targetFolder.getAbsolutePath() + "/sound/"));\r
+ copy(new File(currentFolder + "/script"), targetFolder);\r
+ \r
+ new File(targetFolder.getAbsolutePath() + "/save/").mkdirs();\r
+ copy(new File(currentFolder + "/save"), targetFolder);\r
+\r
+ copy(new File(currentFolder + "/default.ttf"), targetFolder);\r
+ copy(new File(currentFolder + "/icon.png"), targetFolder);\r
+ copy(new File(currentFolder + "/thumbnail.png"), targetFolder);\r
+ copy(new File(currentFolder + "/icon-high.png"), targetFolder);\r
+ copy(new File(currentFolder + "/thumbnail-high.png"), targetFolder);\r
+ copy(new File(currentFolder + "/icon-high.jpg"), targetFolder);\r
+ copy(new File(currentFolder + "/thumbnail-high.jpg"), targetFolder);\r
+ copy(new File(currentFolder + "/info.txt"), targetFolder);\r
+ \r
+ File imgIniF = new File(currentFolder + "/img.ini");\r
+ if (imgIniF.exists()) {\r
+ copy(imgIniF, targetFolder);\r
+ }\r
+ }\r
+ \r
+ private boolean processEntry(File srcDir, File dstDir, String hash, String original) {\r
+ String relpath = original.substring(0, original.lastIndexOf('/')+1) + hash;\r
+ File src = new File(srcDir + File.separator + original);\r
+ File dst = new File(dstDir + File.separator + relpath);\r
+ if (src.exists()) {\r
+ try {\r
+ FileUtil.copyFile(src, dst);\r
+ } catch (IOException e) {\r
+ Log.w("Error copying file: " + src + " to " + dst, e);\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+ \r
+ public static File copy(File src, File dstFolder) {\r
+ if (!src.exists()) {\r
+ return null;\r
+ }\r
+ \r
+ if (src.isDirectory()) {\r
+ for (String s : src.list()) {\r
+ copy(new File(src.getAbsolutePath() + File.separator + s), new File(dstFolder.getAbsolutePath() + File.separator + src.getName()));\r
+ }\r
+ return null;\r
+ } else { \r
+ File dst = new File(dstFolder.getAbsolutePath() + File.separator + src.getName());\r
+ try {\r
+ FileUtil.copyFile(src, dst);\r
+ } catch (IOException e) {\r
+ Log.w("Error copying file: " + src + " to " + dst, e);\r
+ }\r
+ return dst;\r
+ }\r
+ }\r
+ \r
+ protected void zip(File folder, File zipFile) throws IOException {\r
+ zip(new File[] {folder}, zipFile);\r
+ }\r
+ protected void zip(File folders[], File zipFile) throws IOException {\r
+ for (File folder : folders) {\r
+ if (!folder.exists() || folder.listFiles().length == 0) {\r
+ continue;\r
+ }\r
+ \r
+ try {\r
+ ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(zipFile)); \r
+ zout.setMethod(ZipOutputStream.STORED);\r
+ \r
+ addToZip(zout, folder, "", true);\r
+ \r
+ zout.flush();\r
+ zout.close();\r
+ } catch (ZipException ze) {\r
+ Log.v("Empty ZIP file: " + zipFile);\r
+ zipFile.delete();\r
+ }\r
+ }\r
+ }\r
+ protected void addToZip(ZipOutputStream zout, File file, String prefix, boolean deleteWhenDone) throws IOException {\r
+ addToZip(zout, file, prefix, file.getName(), deleteWhenDone);\r
+ }\r
+ protected void addToZip(ZipOutputStream zout, File file, String prefix, String filename, boolean deleteWhenDone) throws IOException {\r
+ if (!file.exists()) {\r
+ return;\r
+ }\r
+ \r
+ if (prefix.length() > 0) {\r
+ prefix += '/';\r
+ }\r
+ prefix += filename;\r
+ \r
+ if (file.isDirectory()) {\r
+ for (File f : file.listFiles()) {\r
+ addToZip(zout, f, prefix, deleteWhenDone);\r
+ }\r
+ } else {\r
+ if (!filename.endsWith(".mp3")) { \r
+ //System.out.println("ZIP: " + file.getName());\r
+ \r
+ //Read file contents and delete file afterwards\r
+ byte[] buffer = FileUtil.readBytes(file);\r
+ if (deleteWhenDone) {\r
+ file.delete();\r
+ }\r
+ \r
+ //Create ZIP Entry\r
+ ZipEntry entry = new ZipEntry(prefix); \r
+ entry.setSize(buffer.length);\r
+ entry.setCompressedSize(buffer.length);\r
+ CRC32 crc = new CRC32();\r
+ crc.update(buffer);\r
+ entry.setCrc(crc.getValue());\r
+ zout.putNextEntry(entry);\r
+ \r
+ //Write File contents to ZIP\r
+ zout.write(buffer);\r
+ \r
+ zout.flush();\r
+ zout.closeEntry();\r
+ }\r
+ }\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr;\r
+\r
+public class Sprite {\r
+ \r
+ public final int x;\r
+ public final int y;\r
+ public final int z;\r
+ public final String image;\r
+ public final int w;\r
+ \r
+ public Sprite(int x, int y, int z, String image, int w) {\r
+ this.x = x;\r
+ this.y = y;\r
+ this.z = z;\r
+ this.image = image;\r
+ this.w = w;\r
+ }\r
+ \r
+ public boolean equals(Object o) {\r
+ return (o instanceof Sprite ? equals((Sprite)o) : false);\r
+ }\r
+ public boolean equals(Sprite s) {\r
+ return s != null && s.image.equals(image) && s.x == x && s.y == y && s.z == z && s.w == w;\r
+ }\r
+}\r
--- /dev/null
+package nl.weeaboo.krkr;\r
+\r
+import java.io.BufferedOutputStream;\r
+import java.io.EOFException;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.OutputStream;\r
+import java.nio.channels.FileChannel;\r
+import java.util.zip.DataFormatException;\r
+import java.util.zip.Inflater;\r
+\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.vnds.Log;\r
+import nl.weeaboo.vnds.ProgressListener;\r
+\r
+public class XP3Extractor {\r
+\r
+ //Temporary buffers for unzip\r
+ private static final byte infBuffer[] = new byte[64 * 1024];\r
+ private static final byte readBuffer[] = new byte[64 * 1024];\r
+ \r
+ public XP3Extractor() { \r
+ }\r
+ \r
+ //Functions\r
+ public static final int read_s32(InputStream in) throws IOException {\r
+ return (int)readLE(in, 4);\r
+ }\r
+ public static final long read_s64(InputStream in) throws IOException {\r
+ return readLE(in, 8);\r
+ }\r
+ public static final long readLE(InputStream in, int bytes) throws IOException {\r
+ int result = 0;\r
+ for (int n = 0; n < bytes; n++) {\r
+ result += (in.read() << (8 * n));\r
+ }\r
+ return result;\r
+ }\r
+ \r
+ /**\r
+ * Warning: dst should use ascii-only in its pathname\r
+ */\r
+ public void extract(String archive, String dst, ProgressListener pl) throws IOException {\r
+ Log.v("Extracting " + archive);\r
+ \r
+ FileInputStream fin = new FileInputStream(archive);\r
+ FileChannel fc = fin.getChannel();\r
+ \r
+ int origSize;\r
+ File uncompressedFile = new File(dst+"/__temp__.dat");\r
+ uncompressedFile.getParentFile().mkdirs();\r
+ \r
+ {\r
+ byte signature[] = new byte[] {(byte)'X', (byte)'P', (byte)'3',\r
+ (byte)0x0D, (byte)0x0A, (byte) ' ', (byte)0x0A,\r
+ (byte)0x1A, (byte)0x8B, (byte)0x67, (byte)0x01 };\r
+ byte tempsig[] = new byte[signature.length];\r
+ fin.read(tempsig);\r
+ for (int n = 0; n < tempsig.length; n++) {\r
+ if (signature[n] != tempsig[n]) {\r
+ throw new IOException("FileFormat error");\r
+ }\r
+ }\r
+ \r
+ int indexOffset = (int)read_s64(fin);\r
+ if (indexOffset > fc.size()) throw new IOException("FileFormat error");\r
+ fc.position(indexOffset);\r
+ \r
+ boolean compression = readLE(fin, 1) != 0;\r
+ if (compression) {\r
+ int compSize = (int)read_s64(fin);\r
+ origSize = (int)read_s64(fin);\r
+ \r
+ if (indexOffset+compSize+17 != fc.size()) throw new IOException("FileFormat error");\r
+ \r
+ int uncompressedL = -1;\r
+ BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(uncompressedFile));\r
+ try {\r
+ uncompressedL = unzip(fin, bout, compSize);\r
+ } finally {\r
+ bout.close();\r
+ }\r
+ \r
+ if (uncompressedL != origSize) throw new IOException("FileFormat error");\r
+ } else {\r
+ origSize = (int)read_s64(fin);\r
+ BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(uncompressedFile));\r
+ int read = 0;\r
+ byte buffer[] = new byte[256*1024];\r
+ while (read < origSize) {\r
+ int r = fin.read(buffer, 0, buffer.length);\r
+ if (r < 0) break;\r
+ bout.write(buffer, 0, r);\r
+ }\r
+ bout.close();\r
+ }\r
+ }\r
+\r
+ FileInputStream uncompressedIn = new FileInputStream(uncompressedFile);\r
+ FileChannel uncompressedC = uncompressedIn.getChannel();\r
+ \r
+ byte out[] = new byte[1024 * 1024];\r
+ int outL = 0;\r
+\r
+ int t = 0;\r
+ int read = 0;\r
+ while (uncompressedC.position() < origSize) {\r
+ Entry entry = readEntry(uncompressedIn);\r
+ \r
+ File outFile = new File(String.format("%s/%s", dst, entry.file));\r
+ outFile.getParentFile().mkdirs();\r
+\r
+ outL = 0;\r
+ t++;\r
+ \r
+ if (pl != null && (t & 0xFF) == 0) { \r
+ pl.onProgress(read, (int)fc.size(), "");\r
+ }\r
+ \r
+ //Log.verbose("[write] " + outFile.getAbsolutePath());\r
+ //Benchmark.tick();\r
+ \r
+ //Write segments to seperate files\r
+ int totalSize = 0;\r
+ for (Segment segment : entry.segments) {\r
+ totalSize += segment.origSize;\r
+ }\r
+ if (out.length < totalSize) {\r
+ out = new byte[totalSize];\r
+ }\r
+ \r
+ for (Segment segment : entry.segments) {\r
+ fc.position(segment.offset);\r
+\r
+ if (segment.compressed) {\r
+ outL += unzip(fin, out, segment.compSize);\r
+ } else {\r
+ outL += fin.read(out, outL, segment.compSize);\r
+ }\r
+\r
+ read += segment.compSize;\r
+ }\r
+ \r
+ //Decrypt \r
+ if (entry.encrypted) {\r
+ decrypt(outFile.getName(), out, outL);\r
+ }\r
+ \r
+ try {\r
+ if (outFile.getName().endsWith(".tlg")) {\r
+ if (out.length >= 2 && out[0] == 'B' && out[1] == 'M') {\r
+ //Bitmap with TLG extension, lolwut\r
+ File bmpF = new File(StringUtil.stripExtension(outFile.getAbsolutePath())+".bmp");\r
+ FileUtil.writeBytes(bmpF, out, 0, outL);\r
+ } else {\r
+ /*\r
+ String tlgTemp = dst+"/__temp__.tlg";\r
+ String bmpTemp = dst+"/__temp__.bmp";\r
+ FileUtil.writeBytes(new File(tlgTemp), out, 0, outL);\r
+ Process p = ProcessUtil.execInDir(\r
+ String.format("tlg2bmp \"%s\" \"%s\"",\r
+ tlgTemp, bmpTemp),\r
+ "tools/");\r
+ ProcessUtil.waitFor(p);\r
+ ProcessUtil.kill(p);\r
+ \r
+ outFile.delete();\r
+ new File(tlgTemp).delete();\r
+ new File(bmpTemp).renameTo(new File(StringUtil.stripExtension(outFile.getAbsolutePath())+".bmp"));\r
+ */\r
+ \r
+ FileUtil.writeBytes(outFile, out, 0, outL);\r
+ }\r
+ } else {\r
+ FileUtil.writeBytes(outFile, out, 0, outL);\r
+ }\r
+ } catch (IOException ioe) {\r
+ if (outFile.getName().length() <= 128) {\r
+ //Don't warn about long (garbage?) filenames that may be used as padding\r
+ Log.w(ioe.toString());\r
+ }\r
+ }\r
+ \r
+ //Benchmark.tock(outFile.getName() + " %s");\r
+ }\r
+ \r
+ uncompressedC.close();\r
+ uncompressedIn.close();\r
+\r
+ fc.close();\r
+ fin.close();\r
+\r
+ uncompressedFile.delete();\r
+ \r
+ if (pl != null) pl.onFinished(archive + " fully extracted");\r
+ }\r
+ \r
+ static synchronized int unzip(InputStream in, byte out[], int inL) throws IOException {\r
+ Inflater inf = new Inflater();\r
+ \r
+ int read = 0;\r
+ int inflated = 0;\r
+ try {\r
+ while (true) {\r
+ int i = inf.inflate(out, inflated, out.length-inflated);\r
+ if (i > 0) {\r
+ inflated += i;\r
+ } else if (inf.finished() || inf.needsDictionary()) {\r
+ return inflated;\r
+ } else {\r
+ int readLeft = readBuffer.length;\r
+ if (inL >= 0 && inL-read < readLeft) {\r
+ readLeft = inL-read;\r
+ }\r
+ int r = in.read(readBuffer, 0, readLeft);\r
+ if (r == -1) {\r
+ throw new EOFException("Unexpected end of ZLIB input stream");\r
+ }\r
+ read += r; \r
+ inf.setInput(readBuffer, 0, r);\r
+ }\r
+ }\r
+ } catch (DataFormatException e) {\r
+ throw new IOException(e);\r
+ }\r
+ }\r
+ \r
+ static synchronized int unzip(InputStream in, OutputStream out, int inL) throws IOException {\r
+ Inflater inf = new Inflater();\r
+ \r
+ int read = 0;\r
+ int inflated = 0;\r
+ try {\r
+ while (true) {\r
+ int i = inf.inflate(infBuffer, 0, infBuffer.length);\r
+ if (i > 0) {\r
+ inflated += i;\r
+ out.write(infBuffer, 0, i);\r
+ } else if (inf.finished() || inf.needsDictionary()) {\r
+ return inflated;\r
+ } else {\r
+ int readLeft = readBuffer.length;\r
+ if (inL >= 0 && inL-read < readLeft) {\r
+ readLeft = inL-read;\r
+ }\r
+ int r = in.read(readBuffer, 0, readLeft);\r
+ if (r == -1) {\r
+ throw new EOFException("Unexpected end of ZIP input stream");\r
+ }\r
+ read += r; \r
+ inf.setInput(readBuffer, 0, r);\r
+ }\r
+ }\r
+ } catch (DataFormatException e) {\r
+ throw new IOException(e);\r
+ }\r
+ }\r
+ \r
+ @SuppressWarnings("unused")\r
+ protected Entry readEntry(InputStream in) throws IOException {\r
+ Entry entry = new Entry();\r
+ \r
+ byte temp[] = new byte[4];\r
+ \r
+ in.read(temp);\r
+ if (!new String(temp).equals("File")) throw new IOException("FileFormat error :: " + new String(temp));\r
+ int entryLength = (int)read_s64(in);\r
+ \r
+ in.read(temp);\r
+ if (!new String(temp).equals("info")) throw new IOException("FileFormat error");\r
+ int infoLength = (int)read_s64(in);\r
+ \r
+ entry.encrypted = read_s32(in) != 0;\r
+ int origSize = (int)read_s64(in);\r
+ int compSize = (int)read_s64(in);\r
+ \r
+ int filenameL = (int)readLE(in, 2);\r
+ //System.err.println(origSize + " " + compSize + " " + new String(temp) + " " + filenameL); \r
+ if (infoLength != filenameL*2+22) throw new IOException("FileFormat error");\r
+ \r
+ char filename[] = new char[filenameL];\r
+ for (int n = 0; n < filenameL; n++) {\r
+ filename[n] = (char)readLE(in, 2);\r
+ }\r
+ entry.file = new String(filename); \r
+ \r
+ in.read(temp);\r
+ if (!new String(temp).equals("segm")) throw new IOException("FileFormat error");\r
+ int numSegments = ((int)read_s64(in)) / 28;\r
+ entry.segments = new Segment[numSegments];\r
+ for (int n = 0; n < numSegments; n++) {\r
+ Segment s = new Segment();\r
+ s.compressed = read_s32(in) != 0;\r
+ s.offset = (int)read_s64(in);\r
+ s.origSize = (int)read_s64(in);\r
+ s.compSize = (int)read_s64(in);\r
+ \r
+ entry.segments[n] = s;\r
+ }\r
+\r
+ in.read(temp);\r
+ //System.err.println(new String(temp));\r
+ if (read_s64(in) != 4) throw new IOException("FileFormat error");\r
+ int adler = read_s32(in);\r
+\r
+ return entry;\r
+ }\r
+ \r
+ public void decrypt(String filename, byte data[], int dataL) {\r
+ if (dataL > 0x13) {\r
+ data[0x13] ^= 1;\r
+ }\r
+ if (dataL > 0x2ea29) { \r
+ data[0x2ea29] ^= 3;\r
+ }\r
+\r
+ for (int n = 0; n < dataL; n++) {\r
+ data[n] ^= 0x36;\r
+ }\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+ //Inner Classes\r
+ private static class Entry {\r
+ public String file;\r
+ public boolean encrypted;\r
+ public Segment segments[];\r
+ }\r
+ private static class Segment {\r
+ public boolean compressed;\r
+ public int offset;\r
+ public int origSize;\r
+ public int compSize; \r
+ }\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.io.File;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import javax.swing.JComboBox;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+\r
+import nl.weeaboo.awt.AwtUtil;\r
+import nl.weeaboo.awt.FileBrowseField;\r
+import nl.weeaboo.common.Dim;\r
+import nl.weeaboo.krkr.fate.FateScriptConverter.Language;\r
+import nl.weeaboo.vnds.AbstractConversionGUI;\r
+\r
+@SuppressWarnings("serial")\r
+public class ConversionGUI extends AbstractConversionGUI {\r
+\r
+ private static final String version = "1.2.5";\r
+\r
+ /*\r
+ * Changes:\r
+ * \r
+ * 2013/03/19 -- v1.2.5\r
+ * - Colored background effects didn't scale to higher resolutions properly.\r
+ * \r
+ * 2012/12/08 -- v1.2.4\r
+ * - Sprite scaling was incorrect\r
+ * \r
+ * 2012/04/29 -- v1.2.3\r
+ * - Fixed Heaven's Feel unlock flag not getting set\r
+ * \r
+ * 2011/09/22 -- v1.2.2\r
+ * - Dumb typo\r
+ *\r
+ * 2011/09/22 -- v1.2.1\r
+ * - Android conversion uses Ogg-Vorbis for all audio\r
+ * - Support for pre-installed voice data without using a Realta Nua disc\r
+ * \r
+ * 2011/04/03 -- v1.2.0\r
+ * - Support for Android and high-res output\r
+ */\r
+ \r
+ protected final FileBrowseField realtaNuaField;\r
+ protected final JComboBox languageCombo;\r
+ \r
+ private Language lang;\r
+ \r
+ public ConversionGUI() {\r
+ super("Fate/Stay Night -> VNDS Conversion GUI v" + version,\r
+ ConversionGUI.class.getResource("res/icon.png"),\r
+ new File(""),\r
+ new File(""),\r
+ "fate",\r
+ true,\r
+ new Dim(800, 600));\r
+\r
+ realtaNuaField = FileBrowseField.writeFolder("", new File(""));\r
+\r
+ languageCombo = new JComboBox(Language.values());\r
+ languageCombo.setSelectedItem(Language.EN);\r
+ }\r
+\r
+ public static void main(String args[]) {\r
+ AwtUtil.setDefaultLAF();\r
+ System.setProperty("line.separator", "\n");\r
+\r
+ new ConversionGUI().create();\r
+ }\r
+ \r
+ @Override\r
+ protected void fillPathsPanel(JPanel panel) {\r
+ super.fillPathsPanel(panel);\r
+ \r
+ panel.add(new JLabel("Realta Nua (Optional)")); panel.add(realtaNuaField); \r
+ }\r
+\r
+ @Override\r
+ protected void fillSettingsPanel(JPanel panel) {\r
+ panel.add(new JLabel("Language")); panel.add(languageCombo); \r
+ \r
+ super.fillSettingsPanel(panel); \r
+ }\r
+ \r
+ @Override\r
+ protected boolean preConvertCheck(File gameFolder, File outputFolder) {\r
+ lang = (Language)languageCombo.getSelectedItem();\r
+ \r
+ return super.preConvertCheck(gameFolder, outputFolder);\r
+ } \r
+ \r
+ @Override\r
+ protected void callResourceConverter(String templateFolder, String srcFolder,\r
+ String dstFolder, String... args)\r
+ {\r
+ String[] merged = new String[args.length+2];\r
+ merged[0] = srcFolder;\r
+ merged[1] = dstFolder;\r
+ System.arraycopy(args, 0, merged, merged.length-args.length, args.length);\r
+ FateResourceConverter.main(merged);\r
+ }\r
+ \r
+ @Override\r
+ protected void callScriptConverter(String srcFolder, String dstFolder) {\r
+ List<String> list = new ArrayList<String>();\r
+ list.add(srcFolder);\r
+ list.add(dstFolder);\r
+ list.add(lang.name());\r
+ FateScriptConverter.main(list.toArray(new String[0]));\r
+ }\r
+\r
+ @Override\r
+ protected void callPacker(String srcFolder, String dstFolder) {\r
+ FatePacker.main(new String[] {srcFolder, dstFolder});\r
+ }\r
+\r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.io.File;\r
+\r
+public class FateDirectoryFlattener {\r
+\r
+ private static final File root = new File("c:/users/timon/desktop/2/");\r
+\r
+ //Functions\r
+ public static void main(String args[]) { \r
+ process(1, root); \r
+ process(2, root);\r
+ }\r
+ \r
+ public static void process(int pass, File file) {\r
+ if (file.isDirectory()) {\r
+ File files[] = file.listFiles();\r
+ for (File f : files) {\r
+ process(pass, f);\r
+ }\r
+ \r
+ if (pass == 2) {\r
+ file.delete();\r
+ }\r
+ } else {\r
+ if (pass == 1) {\r
+ String path = file.getAbsolutePath();\r
+ path = path.substring(root.getAbsolutePath().length(), path.length());\r
+ String filename = path.replace(File.separatorChar, '/').replaceAll("\\/", "");\r
+ \r
+ //Uncomment when flattening the scenario folder, this fixes the garbled filenames\r
+ /*try {\r
+ //String newName = new String(filename.getBytes("SJIS"), "CP1252");\r
+ String newName = new String(filename.getBytes("CP1252"), "CP1252");\r
+ newName = newName.replace((char)0xfffd, '?');\r
+ //newName = newName.replaceAll("\\?", ""); \r
+ String converted = RouteParser.scenarioFileRename(4, newName);\r
+ if (converted != null) {\r
+ filename = converted;\r
+ } else {\r
+ System.out.println(filename + " " + newName);\r
+ }\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ }*/\r
+ \r
+ File f = new File(root.getAbsolutePath() + File.separator + filename);\r
+ file.renameTo(f); \r
+ System.out.println(file.getAbsolutePath() + " " + f.getAbsolutePath());\r
+ }\r
+ }\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.Hashtable;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.SortedSet;\r
+import java.util.TreeSet;\r
+\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.krkr.XP3Extractor;\r
+import nl.weeaboo.string.StringUtil2;\r
+import nl.weeaboo.vnds.Log;\r
+import nl.weeaboo.vnds.ProgressListener;\r
+\r
+public class FateExtractor {\r
+ \r
+ private String realtaNuaPath;\r
+ \r
+ public FateExtractor() {\r
+ \r
+ }\r
+ \r
+ //Functions\r
+ protected static void printUsage() {\r
+ System.err.println("Usage: java -jar FateExtractor.jar <game-folder> <output-folder> <flags>\nflags:"\r
+ + "\n\t-rn <realta-nua-disc-path>"\r
+ + "\n\t-novoice"\r
+ + "\n\t-cleanTempFiles"\r
+ ); \r
+ }\r
+ \r
+ public static void main(String args[]) {\r
+ if (args.length < 2) {\r
+ printUsage();\r
+ return;\r
+ }\r
+ \r
+ boolean extractVoice = true;\r
+ boolean cleanTempFiles = false;\r
+ FateExtractor ex = new FateExtractor(); \r
+ try {\r
+ for (int n = 2; n < args.length; n++) {\r
+ if (args[n].startsWith("-rn")) {\r
+ ex.realtaNuaPath = args[++n];\r
+ } else if (args[n].startsWith("-novoice")) {\r
+ extractVoice = false;\r
+ } else if (args[n].startsWith("-cleanTempFiles")) {\r
+ cleanTempFiles = true;\r
+ }\r
+ }\r
+ } catch (RuntimeException re) {\r
+ printUsage();\r
+ return;\r
+ }\r
+\r
+ SortedSet<String> archives = new TreeSet<String>(StringUtil2.getStringComparator());\r
+ \r
+ File src = new File(args[0]);\r
+ File dst = new File(args[1]);\r
+ if (src.isDirectory()) {\r
+ for (File file : src.listFiles()) {\r
+ if ("xp3".equalsIgnoreCase(StringUtil.getExtension(file.getName()))) {\r
+ if (file.getName().equals("patch6.xp3")) {\r
+ if (extractVoice &&\r
+ (ex.realtaNuaPath == null || !new File(ex.realtaNuaPath).exists()))\r
+ {\r
+ archives.add(file.getAbsolutePath());\r
+ }\r
+ } else {\r
+ archives.add(file.getAbsolutePath());\r
+ }\r
+ }\r
+ }\r
+ } else {\r
+ archives.add(src.getAbsolutePath());\r
+ } \r
+\r
+ Log.v("Extracting...");\r
+ //Extract <name>.xp3 to outFolder/<name>\r
+ for (String arc : archives) {\r
+ ProgressListener pl = new ProgressListener() {\r
+ @Override\r
+ public void onProgress(int value, int max, String message) {\r
+ Log.v(String.format("%s/%s %s...", StringUtil.formatMemoryAmount(value),\r
+ StringUtil.formatMemoryAmount(max), message));\r
+ }\r
+ @Override\r
+ public void onFinished(String message) {\r
+ Log.v(message);\r
+ }\r
+ };\r
+ \r
+ try {\r
+ ex.extractXP3(arc, dst.getAbsolutePath(), pl);\r
+ } catch (IOException e) {\r
+ Log.e("Exception extracting XP3 archive", e);\r
+ }\r
+ }\r
+ \r
+ if (ex.realtaNuaPath != null && extractVoice) {\r
+ RealtaNuaSoundExtractor rne = new RealtaNuaSoundExtractor();\r
+ try {\r
+ rne.extract(ex.realtaNuaPath, dst.getAbsolutePath()+"/patch6");\r
+ } catch (IOException e) {\r
+ Log.w("Unable to extract voice data from Realta Nua: " + e);\r
+ }\r
+ }\r
+ \r
+ Log.v("Flattening Folders...");\r
+ for (File folder : dst.listFiles()) {\r
+ if (folder.isDirectory()) {\r
+ Map<String, File> fileMap = new Hashtable<String, File>();\r
+ FileUtil.collectFiles(fileMap, folder, false);\r
+ \r
+ for (Entry<String, File> entry : fileMap.entrySet()) {\r
+ File file = entry.getValue();\r
+ File target = new File(folder.getAbsolutePath()+'/'+file.getName().toLowerCase());\r
+ \r
+ if (!file.equals(target) &&\r
+ !file.getAbsolutePath().equalsIgnoreCase(target.getAbsolutePath()))\r
+ {\r
+ target.delete();\r
+ }\r
+ file.renameTo(target);\r
+ }\r
+ \r
+ FileUtil.deleteEmptyFolders(folder);\r
+ }\r
+ }\r
+ \r
+ Log.v("Patching...");\r
+ \r
+ //Create patch list\r
+ SortedSet<String> patchFolders = new TreeSet<String>(StringUtil2.getStringComparator());\r
+ for (File folder : dst.listFiles()) {\r
+ if (folder.isDirectory() && folder.getName().toLowerCase().startsWith("patch")) {\r
+ patchFolders.add(folder.getAbsolutePath());\r
+ }\r
+ }\r
+\r
+ Map<String, File> patchMap = new Hashtable<String, File>();\r
+ for (String folder : patchFolders) {\r
+ //Iterate the sorted list of patches so patch2 gets overwritten by patch6\r
+ FileUtil.collectFiles(patchMap, new File(folder), false);\r
+ }\r
+ \r
+ //Apply patches to each archive separately\r
+ for (File folder : dst.listFiles()) {\r
+ if (folder.isDirectory() && !folder.getName().toLowerCase().startsWith("patch")) {\r
+ Map<String, File> fileMap = new Hashtable<String, File>();\r
+ FileUtil.collectFiles(fileMap, folder, false);\r
+ \r
+ for (Entry<String, File> patchEntry : patchMap.entrySet()) {\r
+ File file = fileMap.get(patchEntry.getKey());\r
+ if (file != null) {\r
+ //Overwrite if exists\r
+ try {\r
+ FileUtil.copyFile(patchEntry.getValue(), file);\r
+ } catch (IOException e) {\r
+ Log.w("Error trying to copy file: " + patchEntry.getValue() + " to " + file, e);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ if (cleanTempFiles) {\r
+ Log.v("Cleaning temp files...");\r
+ \r
+ String delete[] = new String[] {"image", "patch", "patch2", "patch3", "patch4", "patch5",\r
+ "patch", "system", "version", "video"};\r
+ for (String d : delete) {\r
+ FileUtil.deleteFolder(new File(dst.getAbsolutePath()+'/'+d));\r
+ }\r
+ }\r
+ Log.v("Done.");\r
+ }\r
+ \r
+ public void extractXP3(String archive, String outFolder, ProgressListener pl) throws IOException {\r
+ XP3Extractor xp3ex = new XP3Extractor();\r
+ xp3ex.extract(archive, outFolder+"/"+StringUtil.stripExtension(archive.substring(archive.replace('\\', '/').lastIndexOf('/'))), pl);\r
+\r
+ /*\r
+ if (archive.endsWith("patch6.xp3")) {\r
+ //Weird, uncompressed xp3, crass doesn't like that\r
+ XP3Extractor xp3ex = new XP3Extractor();\r
+ xp3ex.extract(archive, outFolder+"/"+FileUtil.stripExtension(archive.substring(archive.replace('\\', '/').lastIndexOf('/'))));\r
+ return;\r
+ }\r
+\r
+ ProcessOutputReader pr = new ProcessOutputReader();\r
+ String cmd = String.format("crage -p \"%s\" -o \"%s\" -O game=FSN", archive, outFolder);\r
+ System.out.println(cmd);\r
+ Process p = SystemUtil.execInDir(cmd, "tools/crass-0.4.13.0");\r
+ \r
+ String output = pr.read(p).trim();\r
+ if (output.length() > 0) {\r
+ System.err.println(output);\r
+ }\r
+ */\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Dimension;\r
+import java.awt.FlowLayout;\r
+import java.awt.GridLayout;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import javax.swing.JButton;\r
+import javax.swing.JCheckBox;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JOptionPane;\r
+import javax.swing.JPanel;\r
+import javax.swing.SwingUtilities;\r
+import javax.swing.border.EmptyBorder;\r
+\r
+import nl.weeaboo.awt.AwtUtil;\r
+import nl.weeaboo.awt.DirectoryChooser;\r
+import nl.weeaboo.awt.Sash;\r
+import nl.weeaboo.vnds.ProgressListener;\r
+import nl.weeaboo.vnds.ProgressRunnable;\r
+import nl.weeaboo.vnds.VNDSProgressDialog;\r
+import nl.weeaboo.vnds.installer.Installer;\r
+\r
+/*\r
+ * Changes:\r
+ *\r
+ * 2009/03/01 -- v1.1\r
+ * - Moved xml config files to a subfolder\r
+ *\r
+ * 2008/11/03 -- v1.0\r
+ * - Initial Release\r
+ * \r
+ */\r
+@SuppressWarnings("serial")\r
+public class FateInstaller extends JFrame {\r
+\r
+ private ComponentCheckBox coreCheck;\r
+ private ComponentCheckBox prologueCheck;\r
+ private ComponentCheckBox route1Check;\r
+ private ComponentCheckBox route2Check;\r
+ private ComponentCheckBox route3Check;\r
+ \r
+ public FateInstaller() { \r
+ setTitle("Fate/Stay Night Installer v1.1");\r
+ \r
+ add(createCenterPanel());\r
+ \r
+ pack();\r
+ setResizable(false);\r
+ setLocationRelativeTo(null);\r
+ setVisible(true);\r
+ }\r
+ \r
+ //Functions\r
+ public static void main(String args[]) {\r
+ AwtUtil.setDefaultLAF();\r
+ \r
+ FateInstaller fi = new FateInstaller();\r
+ fi.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); \r
+ }\r
+ \r
+ private JPanel createCenterPanel() {\r
+ coreCheck = new ComponentCheckBox("Core", "_installer/core.xml");\r
+ coreCheck.setEnabled(false);\r
+ prologueCheck = new ComponentCheckBox("Prologue", "_installer/prologue.xml");\r
+ route1Check = new ComponentCheckBox("Route 1: Fate", "_installer/route01-fate.xml");\r
+ route2Check = new ComponentCheckBox("Route 2: UBW", "_installer/route02-ubw.xml");\r
+ route3Check = new ComponentCheckBox("Route 3: HF", "_installer/route03-hf.xml");\r
+ \r
+ JPanel panel2 = new JPanel(new GridLayout(-1, 1, 5, 5));\r
+ panel2.add(coreCheck);\r
+ panel2.add(prologueCheck);\r
+ panel2.add(route1Check);\r
+ panel2.add(route2Check);\r
+ panel2.add(route3Check);\r
+\r
+ JButton installButton = new JButton("Install");\r
+ installButton.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent e) {\r
+ DirectoryChooser dc = new DirectoryChooser(true);\r
+ if (!dc.showDialog(FateInstaller.this, "Choose a folder to install to...")) {\r
+ return;\r
+ }\r
+ final String installFolder = dc.getSelectedDirectory().getAbsolutePath()+"/fate/"; \r
+ final List<String> files = new ArrayList<String>();\r
+ \r
+ if (coreCheck.isSelected()) files.add(coreCheck.getPath());\r
+ if (prologueCheck.isSelected()) files.add(prologueCheck.getPath());\r
+ if (route1Check.isSelected()) files.add(route1Check.getPath());\r
+ if (route2Check.isSelected()) files.add(route2Check.getPath());\r
+ if (route3Check.isSelected()) files.add(route3Check.getPath());\r
+ \r
+ ProgressListener pl = new ProgressListener() {\r
+ public void onFinished(String message) {\r
+ JOptionPane.showMessageDialog(null, String.format(\r
+ "<html>Installation finished.<br>Installed to: %s</html>", installFolder),\r
+ "Finished", JOptionPane.PLAIN_MESSAGE);\r
+ }\r
+ public void onProgress(int value, int max, String message) {\r
+ }\r
+ };\r
+ \r
+ ProgressRunnable task = new ProgressRunnable() {\r
+ public void run(ProgressListener pl) {\r
+ final String errors = Installer.install(installFolder, pl, files.toArray(new String[0]));\r
+ \r
+ if (errors.trim().length() > 0) {\r
+ SwingUtilities.invokeLater(new Runnable() {\r
+ public void run() {\r
+ JOptionPane.showMessageDialog(null,\r
+ String.format("<html>%s</html>", errors.replaceAll("\\\n", "<br>")),\r
+ "Error", JOptionPane.ERROR_MESSAGE);\r
+ }\r
+ });\r
+ }\r
+ }\r
+ }; \r
+ VNDSProgressDialog dialog = new VNDSProgressDialog();\r
+ dialog.showDialog(task, pl);\r
+ }\r
+ });\r
+ \r
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));\r
+ buttonPanel.add(installButton);\r
+ \r
+ JPanel bottomPanel = new JPanel(new BorderLayout(10, 10));\r
+ bottomPanel.add(new Sash(Sash.HORIZONTAL), BorderLayout.NORTH);\r
+ bottomPanel.add(buttonPanel, BorderLayout.CENTER);\r
+ \r
+ JPanel panel = new JPanel(new BorderLayout(10, 10));\r
+ panel.setBorder(new EmptyBorder(10, 10, 10, 10));\r
+ panel.add(panel2, BorderLayout.CENTER);\r
+ panel.add(bottomPanel, BorderLayout.SOUTH);\r
+ return panel;\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+ //Inner Classes\r
+ private static class ComponentCheckBox extends JPanel {\r
+ \r
+ private String path;\r
+ private JCheckBox check;\r
+ \r
+ public ComponentCheckBox(String labelString, String path) {\r
+ this.path = path;\r
+ \r
+ JLabel label = new JLabel(labelString);\r
+ label.setPreferredSize(new Dimension(100, 20));\r
+ check = new JCheckBox();\r
+ check.setSelected(true);\r
+ \r
+ setLayout(new BorderLayout(10, 0));\r
+ add(label, BorderLayout.WEST);\r
+ add(check, BorderLayout.CENTER);\r
+ }\r
+ \r
+ public boolean isSelected() {\r
+ return check.isSelected();\r
+ }\r
+ public String getPath() {\r
+ return path;\r
+ }\r
+ \r
+ public void setEnabled(boolean e) {\r
+ super.setEnabled(e);\r
+ check.setEnabled(e);\r
+ }\r
+ }\r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import static nl.weeaboo.krkr.MacroParser.R_BACKGROUND;\r
+import static nl.weeaboo.krkr.MacroParser.R_FOREGROUND;\r
+import static nl.weeaboo.krkr.MacroParser.R_SOUND;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import nl.weeaboo.krkr.MacroHandler;\r
+import nl.weeaboo.krkr.MacroParser;\r
+import nl.weeaboo.krkr.Sprite;\r
+import nl.weeaboo.vnds.FileExts;\r
+\r
+public class FateMacroHandler extends MacroHandler {\r
+ \r
+ public static final int DEFAULT_Z = 1000;\r
+\r
+ protected final FileExts fileExts;\r
+ \r
+ protected FateScriptConverter fc;\r
+ protected Sprite sprites[];\r
+ protected boolean spriteFlushNeeded;\r
+ protected int soundPlayingLength;\r
+ \r
+ public FateMacroHandler(FateScriptConverter fc, MacroParser mp, FileExts exts) {\r
+ super(fc, mp);\r
+ \r
+ this.fc = fc; \r
+ this.fileExts = exts;\r
+ \r
+ String ignore[] = new String[] {\r
+ //Inline\r
+ "aero", "atlas", "keraino", "troya", "margos", "heart",\r
+ "l", "r",\r
+ \r
+ //Normal\r
+ "backlay", "broadencombo", "broadencomboT",\r
+ "canseeStatusMenu", "canSeeStatusMenu",\r
+ "cinescoT", "cinesco_offT", "clickskip",\r
+ "cm", //Called on the start of each day\r
+ "condoff", "condoffT",\r
+ "contrast", "contrastT", "contrastoff", "contrastoffT",\r
+ "darken", "darkenoff", "darkenT", "darkenoffT",\r
+ "dash", "dashcombo", "dashcomboT",\r
+ "defocus", "delay", "displayedoff", "displayedon",\r
+ "encountServant", "erasestaffroll",\r
+ "flicker", "flickerT", "flushcombo", "foldcombo", "foldcomboT",\r
+ "font", "haze", "hazeTrans", "hazetrans", "hearttonecombo",\r
+ "image", "imageex", "image4demo", //Used for animations only\r
+ "initabsolute", //??? \r
+ "interlude_end", "interlude_in", "interlude_in_", "interlude_out", "interlude_out_", "interlude_start",\r
+ "knowMasterName", "knowTrueName", "large", "layopt",\r
+ "monocro", "monocroT", "move", "nega", "negaT",\r
+ "nohaze_next",\r
+ "noise", "noise_back", "noise_noback", "noiseT", "stopnoise", "stopnoiseT", //Overlays animated white noise on the background\r
+ /*"pasttime", "pasttime_long",*/ //What do these do? <-- Some kind of background transition it seems\r
+ "pgnl", "pgtg", "prickT", "quad",\r
+ "quake", "quake_max", "quakeT",\r
+ "r", //Inserts a newline\r
+ "rclick", "redraw", "resetfont", "resetwait", "return", "rf",\r
+ "sepia", "sepiaT", "shock", "shockT",\r
+ "slideclosecomboT", /*"slideopencomboT",*/ "small",\r
+ "smudge", "smudgeT", "smudgeoff", "smudgeoffT",\r
+ "splinemovecombo", "splinemovecomboT", "staffrollsetting",\r
+ "stophaze", "superpose", "superpose_off", "textoff", "texton",\r
+ "tiger_start", "tiger_end", //Change font-style etc. to tiger-dojo style and back\r
+ "touchimages", "trans", "transex_w", /*"turnaround",*/ "tvoffcomboT", "useSkill",\r
+ "useSpecial", "useWeapon",\r
+ "wait", "waitT", "waitn",\r
+ "waveT", "whaze", "wm", "wq", "wshock", "wstaffroll", "wt", "zoomming" \r
+ };\r
+ \r
+ for (String s : ignore) {\r
+ ignore(s);\r
+ }\r
+ }\r
+\r
+ //Functions\r
+ public void reset() {\r
+ sprites = mp.getSlotsCopy();\r
+ spriteFlushNeeded = false;\r
+ }\r
+ \r
+ public String process(String macro, Map<String, String> params) throws IOException {\r
+ StringBuilder result = new StringBuilder();\r
+ \r
+ if (macro.equals("pg")) { \r
+ if (fc.isSoundPlaying()) {\r
+ result.append("sound ~\n");\r
+ fc.setSoundPlaying(false);\r
+ }\r
+ }\r
+ \r
+ if (macro.startsWith("ldall")) {\r
+ result.append(parse_ldall(params));\r
+ } else if (macro.equals("ld") || macro.startsWith("ld_")) {\r
+ result.append(parse_ld(params));\r
+ } else if (macro.equals("cl") || macro.startsWith("cl_")) {\r
+ result.append(parse_cl(params));\r
+ } else {\r
+ mp.setBlackedOut(false);\r
+\r
+ if (macro.startsWith("i2") || macro.startsWith("a2") || macro.equals("bg")) {\r
+ result.append(parse_bg(params));\r
+ } else if (macro.equals("rep")) {\r
+ result.append(parse_rep(params));\r
+ } else if (macro.equals("fadein")) {\r
+ result.append(parse_fadein(params));\r
+ } else if (macro.equals("flushover")) {\r
+ result.append(parse_flushover(params));\r
+ } else if (macro.equals("black")) {\r
+ result.append(parse_black(params));\r
+ } else if (macro.startsWith("pasttime")) {\r
+ result.append(parse_pasttime(params));\r
+ } else if (macro.equals("blackout")) {\r
+ result.append(parse_blackout(params));\r
+ } else if (macro.equals("blue") || macro.equals("blueT")) {\r
+ result.append(parse_blue(params));\r
+ } else if (macro.equals("red") || macro.equals("redT")) {\r
+ result.append(parse_red(params));\r
+ } else if (macro.equals("white") || macro.equals("whiteT")) {\r
+ result.append(parse_white(params));\r
+ } else if (macro.equals("green") || macro.equals("greenT")) {\r
+ result.append(parse_green(params));\r
+ } else {\r
+ //Flush sprites whenever a non-sprite, non-bg command is issued.\r
+ \r
+ result.append(flush());\r
+ if (result.length() > 0) result.append("\n"); \r
+ }\r
+ }\r
+ \r
+ if (macro.equals("date_title")) {\r
+ result.append(parse_date_title(params));\r
+ } else if (macro.equals("l")) {\r
+ result.append("text ");\r
+ } else if (macro.equals("edoublecolumn")) {\r
+ result.append(parse_edoublecolumn(params));\r
+ } else if (macro.equals("approachTigerSchool")) {\r
+ result.append(parse_approachTigerSchool(params));\r
+ } else if (macro.equals("slideopencomboT")) {\r
+ result.append(parse_slideopencomboT(params));\r
+ } else if (macro.equals("turnaround")) {\r
+ result.append(parse_turnaround(params));\r
+ } else if (macro.startsWith("hearttonecombo")) {\r
+ Map<String, String> p = new HashMap<String, String>();\r
+ p.put("file", "se028");\r
+ result.append(process("se", p));\r
+ }\r
+ \r
+ //Audio Functions\r
+ if (macro.startsWith("playstop") || macro.startsWith("playpause") || macro.startsWith("playresume")) {\r
+ result.append("music ~");\r
+ } else if (macro.equals("play") || macro.equals("play_")) {\r
+ String filename = krkr.addRes(R_SOUND, params.get("file")+"."+fileExts.music); \r
+ result.append("music " + filename); \r
+ } else if (macro.startsWith("seloop")) { \r
+ String filename = krkr.addRes(R_SOUND, params.get("file")+"."+fileExts.sound);\r
+\r
+ //Multiple concurrent music streams aren't supported (yet)\r
+ //return "music " + filename;\r
+ result.append("sound ~\nsound " + filename); \r
+ } else if (macro.equals("se") || macro.equals("se_")) {\r
+ String filename = krkr.addRes(R_SOUND, params.get("file")+"."+fileExts.sound); \r
+ result.append(playSFX(params, params.get("file"), filename));\r
+ } else if (macro.startsWith("sestop")) {\r
+ result.append("sound ~");\r
+ } else if (macro.equals("say") || macro.equals("lvoice")) { //@say in EN, JA, @lvoice in CH\r
+ String fp = (macro.equals("say") ? params.get("n") : params.get("file"));\r
+ \r
+ if (macro.equals("say")) {\r
+ String filename = krkr.addRes(MacroParser.R_SOUND, fp + "." + fileExts.voice); \r
+ result.append(String.format("sound %s", filename));\r
+ } else {\r
+ //Don't check if the file exists in CH version\r
+ //Don't change the filename either\r
+ result.append(String.format("sound %s.%s", fp, fileExts.voice));\r
+ }\r
+ fc.setSoundPlaying(true); \r
+ }\r
+ \r
+ if (result.length() > 0) {\r
+ return result.toString();\r
+ }\r
+ \r
+ return super.process(macro, params);\r
+ }\r
+\r
+ private String addSprite(int pos, Sprite sprite) {\r
+ sprites[pos] = sprite;\r
+ \r
+ spriteFlushNeeded = true;\r
+ return "";\r
+ }\r
+ public String flush() {\r
+ if (spriteFlushNeeded) {\r
+ spriteFlushNeeded = false;\r
+ return mp.restoreSlots(sprites);\r
+ }\r
+ return "";\r
+ }\r
+ private String setBG(String filename) {\r
+ clearSlots();\r
+ return mp.setBG(filename);\r
+ }\r
+ private void clearSlots() {\r
+ mp.clearSlots();\r
+ sprites = mp.getSlotsCopy();\r
+ spriteFlushNeeded = false;\r
+ }\r
+ private String playSFX(Map<String, String> params, String oldFilename, String newFilename) throws IOException {\r
+ File file = new File(krkr.getOutputFolder() + "/../sound/" + oldFilename + "." + fileExts.sound); \r
+ int waitTime = Math.min(60, Math.max(0, (int)Math.ceil(60f * (file.length()-4) / 11025f)-6));\r
+\r
+ int wait = (fc.isSoundPlaying() ? soundPlayingLength : 0);\r
+ soundPlayingLength = waitTime;\r
+ fc.setSoundPlaying(true);\r
+ return String.format("delay %d\nsound %s", wait, newFilename); \r
+ }\r
+\r
+ private String parse_ldall(Map<String, String> params) throws IOException {\r
+ clearSlots();\r
+ spriteFlushNeeded = true;\r
+ \r
+ if (params.containsKey("l")) {\r
+ String name = params.get("l")+".png";\r
+ String newName = krkr.addRes(R_FOREGROUND, name);\r
+ addSprite(3, mp.createSprite(name, newName, 3, readInt(params.get("il"), DEFAULT_Z)));\r
+ }\r
+ if (params.containsKey("lc")) {\r
+ String name = params.get("lc")+".png";\r
+ String newName = krkr.addRes(R_FOREGROUND, name);\r
+ addSprite(1, mp.createSprite(name, newName, 1, readInt(params.get("ilc"), DEFAULT_Z)));\r
+ }\r
+ if (params.containsKey("c")) {\r
+ String name = params.get("c")+".png";\r
+ String newName = krkr.addRes(R_FOREGROUND, name);\r
+ addSprite(0, mp.createSprite(name, newName, 0, readInt(params.get("ic"), DEFAULT_Z)));\r
+ }\r
+ if (params.containsKey("rc")) {\r
+ String name = params.get("rc")+".png";\r
+ String newName = krkr.addRes(R_FOREGROUND, name);\r
+ addSprite(2, mp.createSprite(name, newName, 2, readInt(params.get("irc"), DEFAULT_Z)));\r
+ }\r
+ if (params.containsKey("r")) {\r
+ String name = params.get("r")+".png";\r
+ String newName = krkr.addRes(R_FOREGROUND, name);\r
+ addSprite(4, mp.createSprite(name, newName, 4, readInt(params.get("ir"), DEFAULT_Z)));\r
+ }\r
+\r
+ return ""; \r
+ }\r
+ private String parse_ld(Map<String, String> params) throws IOException {\r
+ int pos = mp.parsePosValue(params.get("pos"));\r
+ \r
+ String oldFilename = params.get("file")+".png";\r
+ String filename = krkr.addRes(R_FOREGROUND, oldFilename);\r
+ int z = readInt(params.get("index"), DEFAULT_Z);\r
+ \r
+ return addSprite(pos, mp.createSprite(oldFilename, filename, pos, z));\r
+ }\r
+ private String parse_bg(Map<String, String> params) {\r
+ String filename = params.get("file")+".jpg";\r
+ filename = krkr.addRes(R_BACKGROUND, filename); \r
+ return setBG(filename);\r
+ }\r
+ private String parse_fadein(Map<String, String> params) {\r
+ //I think it should clear all sprites, not 100% sure though\r
+ clearSlots();\r
+ \r
+ String filename = krkr.addRes(R_BACKGROUND, params.get("file")+".jpg"); \r
+ return setBG(filename);\r
+ }\r
+ private String parse_rep(Map<String, String> params) {\r
+ String filename = krkr.addRes(R_BACKGROUND, params.get("bg")+".jpg"); \r
+ return setBG(filename);\r
+ }\r
+ private String parse_cl(Map<String, String> params) {\r
+ int pos = mp.parsePosValue(params.get("pos")); \r
+ \r
+ //If pos >= 0 delete 1 sprite, if < 0, delete them all\r
+ if (pos >= 0) {\r
+ sprites[pos] = null;\r
+ } else {\r
+ for (int n = 0; n < sprites.length; n++) {\r
+ sprites[n] = null;\r
+ }\r
+ }\r
+ spriteFlushNeeded = true;\r
+ \r
+ return "";\r
+ } \r
+ private String parse_pasttime(Map<String, String> params) {\r
+ clearSlots();\r
+ return flash("special/blackout.jpg", 60 * 800 / 1000);\r
+ }\r
+ private String parse_flushover(Map<String, String> params) {\r
+ return setBG("special/whiteout.jpg");\r
+ }\r
+ private String parse_black(Map<String, String> params) {\r
+ return setBG("special/blackout.jpg");\r
+ }\r
+ private String parse_white(Map<String, String> params) {\r
+ return parse_color_t("special/whiteout.jpg", params);\r
+ }\r
+ private String parse_red(Map<String, String> params) {\r
+ return parse_color_t("special/redout.jpg", params);\r
+ }\r
+ private String parse_green(Map<String, String> params) {\r
+ return parse_color_t("special/greenout.jpg", params);\r
+ }\r
+ private String parse_blue(Map<String, String> params) {\r
+ return parse_color_t("special/blueout.jpg", params);\r
+ }\r
+ private String parse_color_t(String filename, Map<String, String> params) {\r
+ if (params.get("time") != null) {\r
+ return flash(filename, Integer.parseInt(params.get("time")) * 60 / 1000);\r
+ } else {\r
+ return setBG(filename);\r
+ } \r
+ }\r
+ private String flash(String filename, int delay) {\r
+ String oldBG = mp.getCurrentBG();\r
+ Sprite[] oldSlots = (sprites != null ? sprites.clone() : null);\r
+ \r
+ String result = setBG(filename) + "\ndelay " + delay;\r
+ String append = setBG(oldBG);\r
+ result += (append.length() > 0 ? "\n" : "") + append;\r
+ \r
+ sprites = oldSlots;\r
+ spriteFlushNeeded = true;\r
+ \r
+ return result;\r
+ }\r
+ \r
+ private String parse_blackout(Map<String, String> params) {\r
+ mp.setBlackedOut(true);\r
+ return "";\r
+ }\r
+ private String parse_date_title(Map<String, String> params) {\r
+ String date = params.get("date");\r
+ String month = date.substring(0, date.length()-2);\r
+ if (month.length() <= 2) month = "0" + month;\r
+ \r
+ String day = date.substring(date.length()-2, date.length());\r
+ return "text ~\ntext <DATE " + month + "/" + day + ">\ntext ~";\r
+ }\r
+ private String parse_edoublecolumn(Map<String, String> params) {\r
+ String upper = params.get("upper").replaceAll("\\$", "");\r
+ String lower = params.get("lower").replaceAll("\\$", "");\r
+ \r
+ return "text " + upper + "\ntext " + lower;\r
+ }\r
+ private String parse_approachTigerSchool(Map<String, String> params) {\r
+ return "bgload special/blackout.jpg\ntext <BAD END>\n" +\r
+ "bgload special/tigerdojo.jpg\ntext Welcome to Tiger Dojo."; \r
+ }\r
+ private String parse_slideopencomboT(Map<String, String> params) {\r
+ String filename = params.get("nextimage");\r
+ filename = krkr.addRes(R_BACKGROUND, filename+".jpg"); \r
+ return setBG(filename);\r
+ }\r
+ private String parse_turnaround(Map<String, String> params) { \r
+ String bg = mp.getCurrentBG();\r
+ String temp = setBG("special/blackout.jpg");\r
+ clearSlots();\r
+ return temp + "\n" + setBG(bg);\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.concurrent.TimeUnit;\r
+\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.krkr.Packer;\r
+import nl.weeaboo.krkr.fate.FateScriptConverter.Language;\r
+import nl.weeaboo.vnds.FileMapper;\r
+import nl.weeaboo.vnds.Log;\r
+import nl.weeaboo.vnds.installer.InstallerPacker;\r
+\r
+public class FatePacker extends Packer {\r
+ \r
+ private String language;\r
+ \r
+ public FatePacker(String language, String in) {\r
+ this.language = language;\r
+ }\r
+\r
+ //Functions\r
+ protected static void printUsage() {\r
+ System.err.println("Usage: java -jar FatePacker.jar <src-folder> <target-folder> <flags>\nflags:"\r
+ + "\n\t-novoice"\r
+ + "\n\t-threads <num>"\r
+ + "\n\t-lang <EN|JA|CH>" \r
+ + "\n\t-cleanTempFiles"); \r
+ }\r
+\r
+ public static void main(String args[]) {\r
+ if (args.length < 2) {\r
+ printUsage();\r
+ return;\r
+ }\r
+\r
+ long startTime = System.currentTimeMillis();\r
+ \r
+ Language language = Language.EN; \r
+ String srcFolder = args[0];\r
+ String dstFolder = args[1];\r
+ String tempFolder = srcFolder+"/_temp/";\r
+ int threads = 2;\r
+ boolean cleanTempFiles = false;\r
+\r
+ try {\r
+ for (int n = 2; n < args.length; n++) {\r
+ if (args[n].startsWith("-lang")) {\r
+ language = Language.valueOf(args[++n]);\r
+ } else if (args[n].startsWith("-threads")) {\r
+ threads = Integer.parseInt(args[++n]);\r
+ } else if (args[n].startsWith("-cleanTempFiles")) {\r
+ cleanTempFiles = true;\r
+ }\r
+ }\r
+ } catch (RuntimeException re) {\r
+ printUsage();\r
+ return;\r
+ }\r
+ \r
+ FatePacker packer;\r
+ \r
+ //Clean install folder\r
+ FileUtil.deleteFolder(new File(dstFolder)); \r
+ new File(dstFolder).mkdirs();\r
+ \r
+ packer = new FatePacker(language.getLangCode(), srcFolder);\r
+ packer.process(new File(srcFolder), new File(tempFolder)); \r
+\r
+ System.out.println("Installing "+language+"...");\r
+ InstallerPacker.execute(String.format("create \"%s\" \"%s\"", tempFolder, dstFolder));\r
+\r
+ {\r
+ ResourceUsageAnalyzer rua = new ResourceUsageAnalyzer(srcFolder+"/_info", srcFolder);\r
+ rua.analyze(language, threads);\r
+ File depF = new File(srcFolder+"/_info/dependency_analysis");\r
+ File[] files = depF.listFiles();\r
+ if (files != null) {\r
+ for (File f : files) {\r
+ if (f.getName().endsWith(".xml")) {\r
+ try {\r
+ FileUtil.copyFile(f, new File(dstFolder+"/_installer/"+f.getName()));\r
+ } catch (IOException ioe) {\r
+ Log.w("Error trying to copy file: " + f, ioe);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ System.gc();\r
+\r
+ File dst = new File(dstFolder);\r
+ try {\r
+ FileUtil.copyFile(new File("template/fate/instructions.txt"), dst);\r
+ FileUtil.copyFile(new File("FSNInstaller.jar"), dst);\r
+ copyFolderNoSVN(new File("lib"), new File(dstFolder+"/lib"));\r
+ } catch (IOException e) {\r
+ Log.e("Error trying to copy files", e);\r
+ }\r
+ \r
+ //Clean tempfolder\r
+ FileUtil.deleteFolder(new File(tempFolder));\r
+\r
+ if (cleanTempFiles) {\r
+ Log.v("Cleaning temp files...");\r
+ \r
+ FileUtil.deleteFolder(new File(srcFolder+"/../data"));\r
+ FileUtil.deleteFolder(new File(srcFolder));\r
+ }\r
+ \r
+ //Finished\r
+ Log.v(StringUtil.formatTime(System.currentTimeMillis()-startTime, TimeUnit.MILLISECONDS) + " Finished.");\r
+ }\r
+\r
+ @Override\r
+ protected void modifyNameMapping(FileMapper mapper) {\r
+ mapper.put("info.txt", "info-" + language.toLowerCase() + ".txt"); \r
+ }\r
+ \r
+ protected static void copyFolderNoSVN(File src, File dst) throws IOException {\r
+ Map<String, File> fileMap = new HashMap<String, File>();\r
+ FileUtil.collectFiles(fileMap, src, false);\r
+ for (Iterator<Entry<String, File>> i = fileMap.entrySet().iterator(); i.hasNext(); ) {\r
+ Entry<String, File> entry = i.next();\r
+ if (entry.getValue().isDirectory()) {\r
+ continue;\r
+ }\r
+ \r
+ String key = entry.getKey().replace('\\', '/');\r
+ if (key.contains("/.") || key.startsWith(".") || entry.getKey().equals("instructions")) {\r
+ //Remove unix-style hidden files (like .svn folders)\r
+ //Remove instructions.txt (install instructions)\r
+ i.remove();\r
+ }\r
+ }\r
+ \r
+ new File(dst.getAbsolutePath()+'/'+src.getName()).mkdirs();\r
+ FileUtil.copyFiles(fileMap, dst); \r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import nl.weeaboo.krkr.fate.FateScriptConverter.Language;\r
+import nl.weeaboo.vnds.Patcher;\r
+\r
+public class FatePatcher extends Patcher {\r
+ \r
+ private Language lang;\r
+ \r
+ public FatePatcher(Language lang) {\r
+ this.lang = lang;\r
+ }\r
+ \r
+ //Functions\r
+ public void fillAppendMap(Map<String, String> appendMap) {\r
+ String temp;\r
+\r
+ //Prologue\r
+ {\r
+ appendMap.put("prologue00.ks", "jump prologue01.scr"); \r
+ appendMap.put("prologue01.ks", "jump prologue02.scr"); \r
+ appendMap.put("prologue02.ks", "text <End of prologue>\njump main.scr"); \r
+ }\r
+ \r
+ //Fate\r
+ {\r
+ //Subroutines aren't supported, so I have to change around the append-data a bit\r
+ temp = appendMap.remove("fate15-17.ks");\r
+ appendMap.put("fate-ending.ks", (temp != null ? temp : "") + "\njump fate15-17.scr post");\r
+ }\r
+ \r
+ //UBW\r
+ {\r
+ //Subroutines aren't supported, so I have to change around the append-data a bit\r
+ temp = appendMap.remove("ubw14-15.ks");\r
+ appendMap.put("ubw-ending.ks", (temp != null ? temp : "") + "\njump ubw14-15.scr post");\r
+ \r
+ temp = appendMap.remove("ubw14-16.ks");\r
+ appendMap.put("ubw-ending2.ks", (temp != null ? temp : "") + "\njump ubw14-16.scr post");\r
+ }\r
+\r
+ //HF\r
+ {\r
+ //Subroutines aren't supported, so I have to change around the append-data a bit\r
+ temp = appendMap.remove("hf16-09.ks");\r
+ appendMap.put("hf-ending2.ks", (temp != null ? temp : "") + "\njump hf16-09.scr post");\r
+ \r
+ temp = appendMap.remove("hf16-13.ks");\r
+ appendMap.put("hf-ending.ks", (temp != null ? temp : "") + "\njump hf16-13.scr post"); \r
+ }\r
+ }\r
+ \r
+ public void patchPre(Map<String, Map<Integer, String>> patch) {\r
+ //Shared\r
+ \r
+ //Language Dependent\r
+ if (lang == Language.EN) {\r
+ patchPreEN(patch);\r
+ } else if (lang == Language.JA) {\r
+ patchPreJA(patch);\r
+ } else if (lang == Language.CH) {\r
+ patchPreCH(patch);\r
+ }\r
+ }\r
+ public void patchPreEN(Map<String, Map<Integer, String>> patch) {\r
+ //Warning, pre-patching may be broken\r
+ }\r
+ public void patchPreJA(Map<String, Map<Integer, String>> patch) {\r
+ //Warning, pre-patching may be broken\r
+ }\r
+ public void patchPreCH(Map<String, Map<Integer, String>> patch) {\r
+ //Warning, pre-patching may be broken\r
+ }\r
+ \r
+ public void patchPost(Map<String, Map<Integer, String>> patch) {\r
+ Map<Integer, String> map = null;\r
+\r
+ {\r
+ //Prologue\r
+ map = patchPost(patch, "prologue-00.ks"); \r
+ map.put(5, "#type moon logo removed");\r
+ \r
+ //Fate\r
+ map = patchPost(patch, "fate01-00.ks");\r
+ map.put(6, RM_TEXT);\r
+ map.put(8, "bgload special/fate/bone1.jpg\ntext I am the bone of my sword.");\r
+ map.put(9, "bgload special/fate/bone2.jpg\ntext Steel is my body, and fire is my blood.");\r
+ map.put(10, "bgload special/fate/bone3.jpg\ntext I have created over a thousand blades.");\r
+ map.put(20, RM_TEXT);\r
+ map.put(21, "bgload special/fate/bone4.jpg\ntext Unknown to Death.");\r
+ map.put(22, "bgload special/fate/bone5.jpg\ntext Nor known to Life.");\r
+ map.put(23, "bgload special/fate/bone6.jpg\ntext Have withstood pain to create many weapons.");\r
+ map.put(24, "bgload special/fate/bone7.jpg\ntext Yet, those hands will never hold anything.");\r
+ map.put(25, "bgload special/fate/bone8.jpg\ntext So as I pray, unlimited blade works.");\r
+ map.put(40, RM_TEXT);\r
+ map.put(43, RM_TEXT);\r
+ map.put(44, RM_TEXT);\r
+ \r
+ //UBW\r
+ map = patchPost(patch, "ubw14-15.ks"); \r
+ map.put(5, "jump ubw-ending.scr\nlabel post");\r
+ \r
+ map = patchPost(patch, "ubw14-16.ks"); \r
+ map.put(5, "jump ubw-ending2.scr\nlabel post");\r
+ \r
+ //HF\r
+ \r
+ } \r
+ \r
+ //Language Dependent\r
+ if (lang == Language.EN) {\r
+ patchPostEN(patch);\r
+ } else if (lang == Language.JA) {\r
+ patchPostJA(patch);\r
+ } else if (lang == Language.CH) {\r
+ patchPostCH(patch);\r
+ }\r
+ }\r
+ public void patchPostEN(Map<String, Map<Integer, String>> patch) {\r
+ Map<Integer, String> map = null;\r
+\r
+ //Prologue\r
+\r
+ //Fate\r
+ map = patchPost(patch, "fate04-05.ks"); \r
+ map.put(757, "#status screen related");\r
+ map = patchPost(patch, "fate04-18.ks"); \r
+ map.put(111, "#status screen related");\r
+ map = patchPost(patch, "fate05-11.ks"); \r
+ map.put(96, "#status screen related");\r
+ \r
+ map = patchPost(patch, "fate15-17.ks"); \r
+ map.put(319, "jump fate-ending.scr\nlabel post");\r
+ \r
+ //UBW\r
+ map = patchPost(patch, "ubw04-10.ks"); \r
+ map.put(507, "#status screen related");\r
+ \r
+ //HF (untranslated)\r
+ map = patchPost(patch, "hf04-10.ks"); \r
+ map.put(886, "#status screen related");\r
+\r
+ map = patchPost(patch, "hf16-09.ks"); \r
+ map.put(307, "jump hf-ending2.scr\nlabel post");\r
+\r
+ map = patchPost(patch, "hf16-12.ks"); \r
+ map.put(305, "jump hf-ending2.scr\nlabel post");\r
+\r
+ map = patchPost(patch, "hf16-13.ks"); \r
+ map.put(395, "jump hf-ending.scr\nlabel post");\r
+ }\r
+ public void patchPostJA(Map<String, Map<Integer, String>> patch) {\r
+ Map<Integer, String> map = null;\r
+ \r
+ //Prologue\r
+\r
+ //Fate\r
+ map = patchPost(patch, "fate04-05.ks"); \r
+ map.put(925, "#status screen related");\r
+ map = patchPost(patch, "fate04-18.ks"); \r
+ map.put(131, "#status screen related");\r
+ map = patchPost(patch, "fate05-11.ks"); \r
+ map.put(130, "#status screen related");\r
+ \r
+ map = patchPost(patch, "fate15-17.ks"); \r
+ map.put(350, "jump fate-ending.scr\nlabel post");\r
+ \r
+ //UBW\r
+ map = patchPost(patch, "ubw04-10.ks"); \r
+ map.put(628, "#status screen related");\r
+ \r
+ //HF\r
+ map = patchPost(patch, "hf04-10.ks"); \r
+ map.put(886, "#status screen related");\r
+\r
+ map = patchPost(patch, "hf16-09.ks"); \r
+ map.put(307, "jump hf-ending2.scr\nlabel post");\r
+\r
+ map = patchPost(patch, "hf16-12.ks"); \r
+ map.put(305, "jump hf-ending2.scr\nlabel post");\r
+\r
+ map = patchPost(patch, "hf16-13.ks"); \r
+ map.put(395, "jump hf-ending.scr\nlabel post");\r
+ }\r
+ public void patchPostCH(Map<String, Map<Integer, String>> patch) {\r
+ Map<Integer, String> map = null;\r
+ \r
+ //Prologue\r
+\r
+ //Fate\r
+ map = patchPost(patch, "fate04-05.ks"); \r
+ map.put(987, "#status screen related");\r
+ map = patchPost(patch, "fate04-18.ks"); \r
+ map.put(138, "#status screen related");\r
+ map = patchPost(patch, "fate05-11.ks"); \r
+ map.put(136, "#status screen related");\r
+ \r
+ map = patchPost(patch, "fate15-17.ks"); \r
+ map.put(349, "jump fate-ending.scr\nlabel post");\r
+ \r
+ //UBW\r
+ map = patchPost(patch, "ubw04-10.ks"); \r
+ map.put(683, "#status screen related");\r
+ \r
+ //HF\r
+ map = patchPost(patch, "hf04-10.ks"); \r
+ map.put(1096, "#status screen related");\r
+\r
+ map = patchPost(patch, "hf16-09.ks"); \r
+ map.put(321, "jump hf-ending2.scr\nlabel post");\r
+\r
+ map = patchPost(patch, "hf16-12.ks"); \r
+ map.put(320, "jump hf-ending2.scr\nlabel post");\r
+\r
+ map = patchPost(patch, "hf16-13.ks"); \r
+ map.put(454, "jump hf-ending.scr\nlabel post");\r
+ }\r
+ \r
+ protected Map<Integer, String> patchPost(Map<String, Map<Integer, String>> patchPost, String filename) {\r
+ Map<Integer, String> map = new HashMap<Integer, String>();\r
+ patchPost.put(filename, map);\r
+ return map;\r
+ }\r
+ \r
+ protected Map<Integer, String> patchPre(Map<String, Map<Integer, String>> patchPre, String filename) {\r
+ Map<Integer, String> map = new HashMap<Integer, String>();\r
+ patchPre.put(filename, map);\r
+ return map;\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileCollectFilter;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.system.ProcessUtil;\r
+import nl.weeaboo.vnds.AbstractResourceConverter;\r
+import nl.weeaboo.vnds.BatchProcess;\r
+import nl.weeaboo.vnds.FileOp;\r
+import nl.weeaboo.vnds.Log;\r
+import nl.weeaboo.vnds.tools.ImageConverter;\r
+import nl.weeaboo.vnds.tools.ImageConverter.ScalingType;\r
+import nl.weeaboo.vnds.tools.SoundConverter;\r
+\r
+public class FateResourceConverter extends AbstractResourceConverter {\r
+ \r
+ public FateResourceConverter() {\r
+ }\r
+ \r
+ //Functions\r
+ public static void main(String args[]) {\r
+ System.setProperty("line.separator", "\n");\r
+ \r
+ FateResourceConverter e = new FateResourceConverter();\r
+ try {\r
+ e.parseCommandLine(args, 2);\r
+ } catch (IOException ioe) {\r
+ printUsage(e.getClass());\r
+ return;\r
+ } \r
+ \r
+ try {\r
+ e.extract(args[0], args[1]);\r
+ } catch (IOException ioe) {\r
+ Log.e("Fatal error during resource conversion", ioe);\r
+ }\r
+ }\r
+ public void extract(String src, String dst) throws IOException {\r
+ File dstF = new File(dst);\r
+ File originalF = new File(dstF, "_original");\r
+ File generatedF = new File(dstF, "_generated");\r
+\r
+ //Clean up _original folder\r
+ FileUtil.deleteFolder(originalF);\r
+ originalF.mkdirs();\r
+\r
+ //Extract game data\r
+ FateExtractor.main(new String[] {src, originalF.getAbsolutePath()});\r
+ \r
+ //Clean up _generated folder\r
+ initOutputFolder(generatedF);\r
+ \r
+ //Convert\r
+\r
+ convertBackground(dstF);\r
+ convertForeground(dstF);\r
+ convertSound(dstF);\r
+ convertMusic(dstF);\r
+ \r
+ //Template\r
+ File templateF = new File("template/fate");\r
+ copyTemplate(templateF, generatedF);\r
+ \r
+ //Done\r
+ Log.v("Done");\r
+ }\r
+ \r
+ public void convertBackground(final File root) { \r
+ Log.v("Converting backgrounds...");\r
+ \r
+ final ImageConverter ic = createBackgroundConverter();\r
+ \r
+ Map<String, File> files = new HashMap<String, File>();\r
+ FileUtil.collectFiles(files, new File(root, "/_original/bgimage"), false, false, new FileCollectFilter() {\r
+ public boolean accept(String relpath, File file) {\r
+ if (file.isDirectory()) return true;\r
+ return relpath.endsWith("tlg") || relpath.endsWith("bmp");\r
+ }\r
+ });\r
+ \r
+ BatchProcess bp = createBatch();\r
+ try {\r
+ bp.run(files, new FileOp() {\r
+ @Override\r
+ public void execute(String relpath, File file) throws IOException {\r
+ if (relpath.endsWith(".tlg")) {\r
+ file = convertTLG(file);\r
+ }\r
+ ic.convertFile(file, new File(root, "_generated/background"));\r
+ }\r
+ });\r
+ } catch (InterruptedException ie) {\r
+ Log.w("Batch Process Interrupted");\r
+ }\r
+ }\r
+ \r
+ public void convertForeground(final File root) {\r
+ Log.v("Converting sprites...");\r
+ \r
+ final ImageConverter ic = createForegroundConverter();\r
+ ic.setScalingType(ScalingType.SPRITE);\r
+ \r
+ Map<String, File> files = new HashMap<String, File>();\r
+ FileUtil.collectFiles(files, new File(root, "/_original/fgimage"), false, false, new FileCollectFilter() {\r
+ public boolean accept(String relpath, File file) {\r
+ if (file.isDirectory()) return true;\r
+ return relpath.endsWith("tlg") || relpath.endsWith("bmp");\r
+ }\r
+ });\r
+ \r
+ BatchProcess bp = createBatch();\r
+ try {\r
+ bp.run(files, new FileOp() {\r
+ @Override\r
+ public void execute(String relpath, File file) throws IOException {\r
+ if (relpath.endsWith(".tlg")) {\r
+ file = convertTLG(file);\r
+ }\r
+ ic.convertFile(file, new File(root, "_generated/foreground"));\r
+ }\r
+ });\r
+ } catch (InterruptedException ie) {\r
+ Log.w("Batch Process Interrupted");\r
+ }\r
+ }\r
+\r
+ protected static File convertTLG(File tlgF) throws IOException {\r
+ String hash = "__" + Long.toHexString(Thread.currentThread().getId()) + "__";\r
+ \r
+ File tempTLG = new File(tlgF.getParentFile(), hash + ".tlg");\r
+ File tempBMP = new File(tlgF.getParentFile(), hash + ".bmp");\r
+ \r
+ File bmpF = new File(StringUtil.replaceExt(tlgF.getAbsolutePath(), "bmp"));\r
+ bmpF.delete();\r
+ \r
+ tlgF.renameTo(tempTLG);\r
+ \r
+ try {\r
+ Process p = ProcessUtil.execInDir(\r
+ String.format("tlg2bmp \"%s\" \"%s\"",\r
+ tempTLG.getAbsolutePath(), tempBMP.getAbsolutePath()),\r
+ "tools/");\r
+ ProcessUtil.waitFor(p);\r
+ } finally {\r
+ tempTLG.delete();\r
+ tempBMP.renameTo(bmpF); \r
+ }\r
+ \r
+ return bmpF;\r
+ }\r
+ \r
+ public void convertSound(File root) {\r
+ final File targetFolder = new File(root, "_generated/sound");\r
+ \r
+ try {\r
+ Log.v("Converting SFX...");\r
+ final SoundConverter sc = createSFXEncoder();\r
+ \r
+ Map<String, File> files = new HashMap<String, File>();\r
+ FileUtil.collectFiles(files, new File(root, "/_original/sound"), false, false, new FileCollectFilter() {\r
+ public boolean accept(String relpath, File file) {\r
+ if (file.isDirectory()) return true;\r
+ return relpath.endsWith("wav") || relpath.endsWith("ogg");\r
+ }\r
+ });\r
+\r
+ BatchProcess bp = createBatch();\r
+ bp.run(files, new FileOp() {\r
+ @Override\r
+ public void execute(String relpath, File file) throws IOException {\r
+ sc.convertFile(file, targetFolder);\r
+ }\r
+ });\r
+ } catch (InterruptedException ie) {\r
+ Log.w("Batch Process Interrupted");\r
+ }\r
+ \r
+ if (convertVoice) { \r
+ Log.v("Converting Voice...");\r
+ final SoundConverter sc = createVoiceEncoder();\r
+ \r
+ Map<String, File> files = new HashMap<String, File>();\r
+ FileUtil.collectFiles(files, new File(root, "/_original/patch6"), false, false, new FileCollectFilter() {\r
+ public boolean accept(String relpath, File file) {\r
+ if (file.isDirectory()) return true;\r
+ return relpath.endsWith("wav") || relpath.endsWith("ogg");\r
+ }\r
+ });\r
+\r
+ BatchProcess bp = createBatch();\r
+ bp.setTaskSize(250);\r
+ try {\r
+ bp.run(files, new FileOp() {\r
+ @Override\r
+ public void execute(String relpath, File file) throws IOException {\r
+ sc.convertFile(file, targetFolder);\r
+ }\r
+ });\r
+ } catch (InterruptedException ie) {\r
+ Log.w("Batch Process Interrupted");\r
+ }\r
+ }\r
+ }\r
+\r
+ public void convertMusic(final File root) {\r
+ final File targetFolder = new File(root, "_generated/sound");\r
+\r
+ Log.v("Converting music...");\r
+ final SoundConverter sc = createMusicEncoder();\r
+ \r
+ //Convert music \r
+ Map<String, File> files = new HashMap<String, File>();\r
+ FileUtil.collectFiles(files, new File(root, "/_original/bgm"), false, false, new FileCollectFilter() {\r
+ public boolean accept(String relpath, File file) {\r
+ if (file.isDirectory()) return true;\r
+ return relpath.endsWith("wav") || relpath.endsWith("ogg");\r
+ }\r
+ });\r
+\r
+ BatchProcess bp = createBatch();\r
+ bp.setTaskSize(5);\r
+ try {\r
+ bp.run(files, new FileOp() {\r
+ @Override\r
+ public void execute(String relpath, File file) throws IOException {\r
+ sc.convertFile(file, targetFolder);\r
+ }\r
+ });\r
+ } catch (InterruptedException ie) {\r
+ Log.w("Batch Process Interrupted");\r
+ }\r
+ }\r
+\r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.io.InputStreamReader;\r
+\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.krkr.KiriKiriConverter;\r
+import nl.weeaboo.vnds.Log;\r
+import nl.weeaboo.vnds.Patcher;\r
+\r
+public class FateScriptConverter extends KiriKiriConverter {\r
+ \r
+ //-----------------------------------------------------\r
+ //Fixed after 1.0 release:\r
+ //-----------------------------------------------------\r
+ // * Removed a few lines where KrKr commands were printed in the .scr files\r
+ // * Blank lines removed from EN edition\r
+ // * Updated English translation to version 3.2\r
+ // - 1.1.4 --------------------------------------------\r
+ // * Reduced XP3 extractor memory usage\r
+ // * UTF-8 support for the conversion GUI log\r
+ // * Other memory reductions\r
+ \r
+ public enum Language {\r
+ EN("en", "UTF-16"), JA("ja", "SJIS"), CH("ch", "UTF-16LE");\r
+ \r
+ private String encoding;\r
+ private String langCode;\r
+ \r
+ private Language(String langCode, String encoding) {\r
+ this.langCode = langCode;\r
+ this.encoding = encoding;\r
+ }\r
+ \r
+ public String getEncoding() { return encoding; }\r
+ public String getLangCode() { return langCode; }\r
+ };\r
+ \r
+ private boolean insertVoiceData;\r
+ private Language lang;\r
+ \r
+ private int allowedRoutes = 4; //1=Prologue, 2=P+Fate, 3=P+F+UBW, 4=P+F+UBW+HF \r
+ private RouteParser routeParser;\r
+ private boolean soundPlaying;\r
+ \r
+ public FateScriptConverter(File srcF, File dstF, String language, boolean insertVoice) {\r
+ super(srcF, new File(srcF, "data"), dstF);\r
+ \r
+ lang = Language.valueOf(language);\r
+ insertVoiceData = insertVoice; \r
+ \r
+ if (insertVoiceData) {\r
+ if (lang == Language.CH) {\r
+ System.err.println("Voice support for the Chinese language is not available");\r
+ } else { \r
+ //Inserts the @say commands into the Japanese version\r
+ // -- don't run this on the same file more than once\r
+ \r
+ /*new InsertVoice().patch(getRootFolder() + "/scenario-" + lang.getLangCode(),\r
+ lang.getEncoding(), getRootFolder() + "/scenario-" + Language.EN.getLangCode(),\r
+ Language.EN.getEncoding());\r
+ */\r
+ throw new RuntimeException("TODO: Fix voice insertion");\r
+ }\r
+ } \r
+\r
+ setSourceFileEncoding(lang.getEncoding()); \r
+ \r
+ macroParser.addMacroHandler(new FateMacroHandler(this, macroParser, fileExts));\r
+ macroParser.addTextMacroHandler(new FateTextMacroHandler(this, macroParser));\r
+ }\r
+ \r
+ //Functions\r
+ public static void main(String args[]) {\r
+ File srcF = new File(args[0]);\r
+ File dstF = new File(args[1]);\r
+ String lang = args[2];\r
+ \r
+ Log.v("Converting scripts...");\r
+ FateScriptConverter converter = new FateScriptConverter(srcF, dstF, lang, false);\r
+ converter.convert();\r
+ }\r
+ \r
+ protected Patcher createPatcher() {\r
+ return new FatePatcher(lang);\r
+ }\r
+\r
+ protected String createOutputPath(String filename) {\r
+ return super.createOutputPath(RouteParser.scenarioFileRename(this, filename));\r
+ }\r
+ \r
+ public void convert() {\r
+ routeParser = new RouteParser(this);\r
+ \r
+ super.convert();\r
+ }\r
+ \r
+ protected void scriptConvert(int pass, String relpath, File file) { \r
+ if (pass == 1 && file.getName().endsWith("fcf")) {\r
+ if (!relpath.contains("/")) {\r
+ //If any subfolders exist, it's because the files were extracted on top of\r
+ //an aborted earlier extraction.\r
+ routeParser.parse(file, appendMap);\r
+ }\r
+ } else if (pass == 2 && file.getName().endsWith("ks")) {\r
+ try {\r
+ if (!routeParser.isIncludedInThisRun(file.getName())) {\r
+ return;\r
+ }\r
+ processKSFile(file);\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ }\r
+\r
+ protected void processKSFile(File file) throws IOException {\r
+ soundPlaying = false;\r
+ \r
+ super.processKSFile(file, RouteParser.scenarioFileRename(this, file.getName())+".ks");\r
+ \r
+ if (lang != Language.EN) {\r
+ //Postprocess: remove blank lines and merge lines that belong to a single sentence\r
+\r
+ StringBuilder sb = new StringBuilder();\r
+ StringBuilder string = new StringBuilder();\r
+ \r
+ String path = createOutputPath(file.getName());\r
+ BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF-8"));\r
+ String line;\r
+ while ((line = in.readLine()) != null) { \r
+ if (line.startsWith("text ")) {\r
+ String textPart = line.substring(5).trim(); \r
+\r
+ if (textPart.length() > 0 && textPart.charAt(0) == 0x3000) {\r
+ if (string.length() > 0) {\r
+ sb.append("text ");\r
+ sb.append(string);\r
+ sb.append("\n");\r
+ string.delete(0, string.length());\r
+ }\r
+ }\r
+ string.append(textPart);\r
+ } else {\r
+ if (string.length() > 0) {\r
+ sb.append("text ");\r
+ sb.append(string);\r
+ sb.append("\n");\r
+ string.delete(0, string.length());\r
+ }\r
+ \r
+ sb.append(line);\r
+ sb.append("\n");\r
+ }\r
+ }\r
+ in.close();\r
+ \r
+ FileUtil.write(new File(path), sb.toString());\r
+ }\r
+ }\r
+ \r
+ protected String parseText(String filename, int lineNumber, String line) {\r
+ String soundAppend = "";\r
+ if (lang == Language.EN && soundPlaying) {\r
+ //soundAppend = "\nsound ~";\r
+ //soundPlaying = false;\r
+ }\r
+ \r
+ String result = macroParser.flush(filename, lineNumber, line);\r
+ return result + (result.length() > 0 ? "\n" : "") + "text "\r
+ + super.parseText(filename, lineNumber, line) + soundAppend;\r
+ }\r
+ \r
+ //Getters\r
+ public int getAllowedRoutes() { return allowedRoutes; }\r
+ public boolean isSoundPlaying() { return soundPlaying; }\r
+ public Language getLanguage() { return lang; }\r
+ public int getNumberOfPasses() { return 2; }\r
+ \r
+ //Setters\r
+ public void setSoundPlaying(boolean sp) { this.soundPlaying = sp; }\r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.io.IOException;\r
+import java.util.Map;\r
+\r
+import nl.weeaboo.krkr.KiriKiriConverter;\r
+import nl.weeaboo.krkr.MacroHandler;\r
+import nl.weeaboo.krkr.MacroParser;\r
+\r
+public class FateTextMacroHandler extends MacroHandler {\r
+\r
+ //TODO:\r
+ //p <- Wait for user click, without inserting a newline\r
+ //ruby <- Read-Hint? Red Text?\r
+ //vr <- ???\r
+ \r
+ public FateTextMacroHandler(KiriKiriConverter krkr, MacroParser mp) {\r
+ super(krkr, mp);\r
+ \r
+ String ignore[] = new String[] {\r
+ "aero", "atlas", "keraino", "troya", "margos", "heart",\r
+ "l", "r", \r
+ };\r
+ \r
+ for (String s : ignore) {\r
+ ignore(s);\r
+ }\r
+ }\r
+\r
+ //Functions\r
+ public String process(String macro, Map<String, String> params) throws IOException {\r
+ if (macro.equals("wrap")) {\r
+ return "";\r
+ } else if (macro.equals("szlig")) {\r
+ return String.valueOf(Character.toChars(0x00DF));\r
+ } else if (macro.equals("XAuml")) {\r
+ return String.valueOf(Character.toChars(0x00C4));\r
+ } else if (macro.equals("XOuml")) {\r
+ return String.valueOf(Character.toChars(0x00D6));\r
+ } else if (macro.equals("XUuml")) {\r
+ return String.valueOf(Character.toChars(0x00DC));\r
+ } else if (macro.equals("auml")) {\r
+ return String.valueOf(Character.toChars(0x00E4));\r
+ } else if (macro.equals("ouml")) {\r
+ return String.valueOf(Character.toChars(0x00F6));\r
+ } else if (macro.equals("uuml")) {\r
+ return String.valueOf(Character.toChars(0x00FC));\r
+ } else if (macro.equals("wacky")) {\r
+ return parse_wacky(params); \r
+ } else if (macro.equals("block")) {\r
+ return parse_block(params); \r
+ } else if (macro.startsWith("line")) {\r
+ return parse_line(macro, params);\r
+ } else if (macro.startsWith("ruby")) {\r
+ return parse_ruby(macro, params);\r
+ }\r
+ \r
+ return super.process(macro, params);\r
+ }\r
+\r
+ private String parse_block(Map<String, String> params) {\r
+ int len = Integer.parseInt(params.get("len")); \r
+ return KiriKiriConverter.repeatString("-", len);\r
+ }\r
+\r
+ private String parse_wacky(Map<String, String> params) {\r
+ int len = Integer.parseInt(params.get("len"));\r
+ len = Math.min(6, len);\r
+ \r
+ return "R" + KiriKiriConverter.repeatString("O", len)\r
+ + KiriKiriConverter.repeatString("A", Math.max(1, len/2)) + "R";\r
+ }\r
+ \r
+ private String parse_line(String macro, Map<String, String> params) {\r
+ int num = Integer.parseInt(macro.substring(4)); \r
+ return KiriKiriConverter.repeatString("-", num * 2); \r
+ }\r
+ \r
+ private String parse_ruby(String macro, Map<String, String> params) {\r
+ return ""; //params.get("text");\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStreamReader;\r
+import java.util.Hashtable;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.vnds.Log;\r
+\r
+public class InsertVoice {\r
+\r
+ //Functions\r
+ public void patch(String folderJ, String encodingJ, String folderE, String encodingE) {\r
+ Map<String, File> filesJ = new Hashtable<String, File>();\r
+ FileUtil.collectFiles(filesJ, new File(folderJ), false);\r
+ \r
+ Map<String, File> filesE = new Hashtable<String, File>();\r
+ FileUtil.collectFiles(filesE, new File(folderE), false);\r
+ \r
+ for (Entry<String, File> entry : filesE.entrySet()) {\r
+ File jFile = filesJ.get(entry.getKey());\r
+ if (jFile != null) {\r
+ try {\r
+ patchFile(jFile, encodingJ, entry.getValue(), encodingE);\r
+ } catch (IOException e) {\r
+ Log.e("Error patching voices", e);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ protected void patchFile(File fileJ, String encodingJ, File fileE, String encodingE) throws IOException {\r
+ BufferedReader japIn = new BufferedReader(new InputStreamReader(new FileInputStream(fileJ), encodingJ));\r
+ BufferedReader engIn = new BufferedReader(new InputStreamReader(new FileInputStream(fileE), encodingE));\r
+ \r
+ \r
+ StringBuilder sb = new StringBuilder();\r
+ \r
+ String lineEng;\r
+ String lineJap;\r
+ \r
+ int engPage = -1;\r
+ int japPage = -1;\r
+ \r
+ while ((lineEng = engIn.readLine()) != null) {\r
+ if (lineEng.startsWith("@say")) {\r
+ //sb.append(l + ":");\r
+ sb.append(lineEng); \r
+ sb.append('\n');\r
+ //sb.append("------------------------------------------------------\n"); \r
+ } else if (engPage >= japPage) {\r
+ do {\r
+ int t = 0;\r
+ boolean append = false;\r
+ while ((lineJap = japIn.readLine()) != null) {\r
+ japPage = Math.max(japPage, pageFromLine(lineJap));\r
+ \r
+ char firstChar = '\0';\r
+ char lastChar = '\0';\r
+ String temp = stripTags(lineJap);\r
+ if (temp.length() > 0) { \r
+ firstChar = temp.charAt(0);\r
+ lastChar = temp.charAt(temp.length()-1);\r
+ }\r
+ \r
+ if (t == 0) {\r
+ //sb.append(l + ":");\r
+ sb.append(lineJap);\r
+ sb.append('\n');\r
+ }\r
+ \r
+ if (firstChar == 0x40 || firstChar == 0x2A ) {\r
+ break;\r
+ } else if (t == 0 || (t > 0 && ((firstChar != 0x3000 && firstChar != 0x300C) || append))) {\r
+ if (t > 0) {\r
+ //sb.append(l + ":");\r
+ sb.append(lineJap);\r
+ sb.append('\n');\r
+ }\r
+ \r
+ append = (lastChar == 0x3001);\r
+ japIn.mark(1024);\r
+ t++;\r
+ } else {\r
+ break;\r
+ }\r
+ }\r
+ if (t > 0) {\r
+ japIn.reset();\r
+ }\r
+ //sb.append("------------------------------------------------------\n"); \r
+ //System.out.println(engPage + " " + japPage);\r
+ } while (engPage > japPage);\r
+ }\r
+ engPage = Math.max(engPage, pageFromLine(lineEng));\r
+ }\r
+ \r
+ japIn.close();\r
+ engIn.close();\r
+ \r
+ System.out.println("Inserting Voice Data: " + fileJ.getName());\r
+ //fileJ = new File("e:/temp.txt");\r
+ FileOutputStream fout = new FileOutputStream(fileJ);\r
+ fout.write(sb.toString().getBytes(encodingJ));\r
+ fout.flush();\r
+ fout.close();\r
+ }\r
+ \r
+ protected String stripTags(String s) { \r
+ return s.replaceAll("\\[[^\\]]\\]", "");\r
+ }\r
+ \r
+ protected int pageFromLine(String line) {\r
+ if (line.startsWith("*page")) {\r
+ int index = 5;\r
+ while (index < line.length() && Character.isDigit(line.charAt(index))) {\r
+ index++;\r
+ }\r
+ try {\r
+ return Integer.parseInt(line.substring(5, index));\r
+ } catch (Exception e) { \r
+ }\r
+ }\r
+ return -1;\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Color;\r
+import java.awt.Dimension;\r
+\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JProgressBar;\r
+import javax.swing.JScrollBar;\r
+import javax.swing.JScrollPane;\r
+import javax.swing.SwingUtilities;\r
+import javax.swing.border.EmptyBorder;\r
+\r
+import nl.weeaboo.string.HtmlUtil;\r
+\r
+@SuppressWarnings("serial")\r
+public class OutputField extends JPanel {\r
+\r
+ private JLabel textArea;\r
+ private JScrollPane scrollPane;\r
+ \r
+ public OutputField() {\r
+ textArea = createTextArea();\r
+ \r
+ scrollPane = new JScrollPane(textArea);\r
+ scrollPane.getViewport().setBackground(new Color(241, 241, 241));\r
+\r
+ setPreferredSize(new Dimension(400, 150));\r
+ setLayout(new BorderLayout(2, 2));\r
+ add(scrollPane, BorderLayout.CENTER);\r
+ }\r
+ \r
+ protected JLabel createTextArea() {\r
+ JLabel label = new JLabel();\r
+ label.setBorder(new EmptyBorder(5, 5, 5, 5));\r
+ label.setVerticalTextPosition(JLabel.TOP);\r
+ label.setVerticalAlignment(JLabel.TOP);\r
+ return label;\r
+ }\r
+ \r
+ protected JProgressBar createGlobalProgressBar() {\r
+ JProgressBar bar = new JProgressBar(0, 1000);\r
+ return bar;\r
+ }\r
+ protected JProgressBar createLocalProgressBar() {\r
+ JProgressBar bar = new JProgressBar(0, 100);\r
+ return bar;\r
+ }\r
+ \r
+ //Functions\r
+ public void setText(String t) {\r
+ textArea.setText("<html>" + HtmlUtil.escapeHtml(t) + "</html>");\r
+ textArea.validate();\r
+\r
+ SwingUtilities.invokeLater(new Runnable() {\r
+ public void run() {\r
+ JScrollBar bar = scrollPane.getVerticalScrollBar();\r
+ if (bar != null) {\r
+ bar.setValue(bar.getMaximum());\r
+ }\r
+ }\r
+ });\r
+ \r
+ repaint(); \r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileNotFoundException;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.nio.channels.FileChannel;\r
+\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.system.ProcessUtil;\r
+import nl.weeaboo.vnds.Log;\r
+\r
+public class RealtaNuaSoundExtractor {\r
+\r
+ public RealtaNuaSoundExtractor() { \r
+ }\r
+ \r
+ //Functions\r
+ public static void main(String args[]) {\r
+ RealtaNuaSoundExtractor se = new RealtaNuaSoundExtractor();\r
+ try {\r
+ se.extract(args[0], args[1]);\r
+ } catch (FileNotFoundException fnfe) {\r
+ System.err.println(fnfe);\r
+ } catch (IOException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ \r
+ public static final int read_s32(InputStream in) throws IOException {\r
+ return (int)readLE(in, 4);\r
+ }\r
+ public static final long read_s64(InputStream in) throws IOException {\r
+ return readLE(in, 8);\r
+ }\r
+ public static final long readLE(InputStream in, int bytes) throws IOException {\r
+ int result = 0;\r
+ for (int n = 0; n < bytes; n++) {\r
+ result += (in.read() << (8 * n));\r
+ }\r
+ return result;\r
+ }\r
+\r
+ public void extract(String discRoot, String outFolder) throws IOException {\r
+ File outFolderFile = new File(outFolder);\r
+ outFolderFile.mkdirs();\r
+ \r
+ File newExe = new File(outFolder+"/ahx2wav.exe");\r
+ FileUtil.copyFile(new File("tools/ahx2wav_v014/ahx2wav.exe"), newExe);\r
+ FileInputStream fin = new FileInputStream(discRoot+"/data0.bin");\r
+ FileChannel fc = fin.getChannel();\r
+ \r
+ Log.v("Extracting AHX sound files...");\r
+ \r
+ try {\r
+ byte sig[] = new byte[] {(byte)'A', (byte)'F', (byte)'S', (byte)'\0'};\r
+ byte arcsig[] = new byte[4];\r
+ fin.read(arcsig);\r
+ for (int n = 0; n < 4; n++) {\r
+ if (arcsig[n] != sig[n]) throw new IOException("FileFormat Error");\r
+ }\r
+ \r
+ //0x808 -- file offset/size table offset\r
+ //0x466C3000 -- filename table offset\r
+ //0x00159660 -- filename table length\r
+ //48 -- filename table entry length\r
+ //29747 -- number of files\r
+\r
+ int filesL = 29474;\r
+ FileEntry files[] = new FileEntry[filesL];\r
+\r
+ fc.position(0x808);\r
+ for (int n = 0; n < filesL; n++) {\r
+ files[n] = new FileEntry();\r
+ files[n].offset = 0x800 + read_s32(fin);\r
+ files[n].length = read_s32(fin);\r
+ }\r
+ \r
+ byte nameBuffer[] = new byte[32];\r
+ for (int n = 0; n < filesL; n++) {\r
+ fc.position(0x466C3000 + 0x30 * n);\r
+ fin.read(nameBuffer);\r
+ int l = 0;\r
+ while (l < nameBuffer.length && nameBuffer[l] != '\0') l++;\r
+ files[n].filename = new String(nameBuffer, 0, l);\r
+ }\r
+ \r
+ for (int n = 0; n < filesL; n++) {\r
+ if (n % 256 == 0) {\r
+ Log.v(String.format("(%d/%d) %s...", n+1, filesL, files[n].filename));\r
+ }\r
+ \r
+ File outFile = new File(outFolder+'/'+files[n].filename);\r
+ FileOutputStream fout = new FileOutputStream(outFile);\r
+ int r = 0;\r
+ while (r < files[n].length) {\r
+ r += fc.transferTo(files[n].offset+r, files[n].length-r, fout.getChannel());\r
+ }\r
+ fout.flush();\r
+ fout.close(); \r
+\r
+ //Convert to wav\r
+ Process p = ProcessUtil.execInDir(String.format(\r
+ "ahx2wav %s",\r
+ files[n].filename),\r
+ outFolder);\r
+ ProcessUtil.waitFor(p);\r
+ if (p.exitValue() != 0) {\r
+ throw new IOException("Error converting file: " + outFile.getAbsolutePath() + "\nAborting sound extraction.");\r
+ }\r
+ ProcessUtil.kill(p);\r
+ \r
+ //Delete original\r
+ outFile.delete(); \r
+ }\r
+ \r
+ //Rename a.b.wav to a.wav \r
+ File converted[] = outFolderFile.listFiles();\r
+ for (File f : converted) {\r
+ String filename = f.getName();\r
+ if (filename.endsWith(".wav")) {\r
+ filename = filename.substring(0, filename.indexOf('.')+1) + "wav";\r
+ f.renameTo(new File(outFolder+'/'+filename));\r
+ }\r
+ }\r
+\r
+ Log.v("Done...");\r
+ } finally {\r
+ fc.close();\r
+ fin.close();\r
+ newExe.delete();\r
+ }\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+\r
+ //Inner Classes\r
+ private static class FileEntry {\r
+ public String filename;\r
+ public int offset;\r
+ public int length;\r
+ }\r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.io.BufferedOutputStream;\r
+import java.io.File;\r
+import java.io.FileNotFoundException;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.OutputStreamWriter;\r
+import java.io.PrintWriter;\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.Hashtable;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.Set;\r
+import java.util.SortedSet;\r
+import java.util.TreeSet;\r
+import java.util.concurrent.Callable;\r
+import java.util.concurrent.ExecutionException;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.Executors;\r
+import java.util.concurrent.Future;\r
+\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.krkr.fate.FateScriptConverter.Language;\r
+import nl.weeaboo.string.StringUtil2;\r
+import nl.weeaboo.vnds.FileMapper;\r
+import nl.weeaboo.vnds.HashUtil;\r
+import nl.weeaboo.vnds.Log;\r
+\r
+public class ResourceUsageAnalyzer {\r
+\r
+ private static final int ROUTE_PRO = 1;\r
+ private static final int ROUTE_FATE = 2;\r
+ private static final int ROUTE_UBW = 4;\r
+ private static final int ROUTE_HF = 8;\r
+ \r
+ private Map<Integer, String> names;\r
+ private String infoFolder;\r
+ private String rootFolder;\r
+ \r
+ public ResourceUsageAnalyzer(String infoFolder, String rootFolder) {\r
+ this.infoFolder = infoFolder;\r
+ this.rootFolder = rootFolder;\r
+ \r
+ names = new HashMap<Integer, String>();\r
+ names.put(0, "core");\r
+ names.put(ROUTE_PRO, "prologue");\r
+ names.put(ROUTE_FATE, "route01-fate");\r
+ names.put(ROUTE_UBW, "route02-ubw");\r
+ names.put(ROUTE_HF, "route03-hf");\r
+ }\r
+ \r
+ //Functions \r
+ protected Map<Integer, Set<String>> a() {\r
+ Map<String, File> files = new Hashtable<String, File>();\r
+ FileUtil.collectFiles(files, new File(rootFolder+"/script"), false);\r
+ \r
+ System.out.println("Collecting Resources...");\r
+ \r
+ Map<String, FileInfo> resources = new Hashtable<String, FileInfo>();\r
+ for (Entry<String, File> entry : files.entrySet()) {\r
+ FileInfo info = new FileInfo(entry.getKey());\r
+ resources.put(info.getFilename(), info);\r
+ \r
+ try {\r
+ analyzeResources(rootFolder+"/script", info);\r
+ } catch (IOException ioe) {\r
+ ioe.printStackTrace();\r
+ }\r
+ }\r
+ \r
+ System.out.println("Writing Resource Lists to Disk...");\r
+\r
+ File outputFolder = new File(infoFolder, "dependency_analysis");\r
+ FileUtil.deleteFolder(outputFolder);\r
+ outputFolder.mkdirs();\r
+ for (FileInfo info : resources.values()) {\r
+ try {\r
+ info.save(outputFolder+StringUtil.stripExtension(info.getFilename())+".txt");\r
+ } catch (IOException ioe) {\r
+ ioe.printStackTrace();\r
+ }\r
+ }\r
+\r
+ System.out.println("Graph Analysis...");\r
+\r
+ Map<String, GraphNode> nodes = new HashMap<String, GraphNode>();\r
+ for (FileInfo info : resources.values()) {\r
+ nodes.put(info.getFilename(), new GraphNode(info.getFilename()));\r
+ }\r
+ for (FileInfo info : resources.values()) {\r
+ GraphNode in = nodes.get(info.getFilename());\r
+ for (String s : info.jumpsTo) {\r
+ GraphNode out = nodes.get(s);\r
+ if (in != null && out != null) {\r
+ in.outgoing.add(out);\r
+ out.incoming.add(in);\r
+ }\r
+ }\r
+ }\r
+ \r
+ setRoute(resources, nodes, "prologue00.scr", ROUTE_PRO);\r
+ setRoute(resources, nodes, "fate05-00.scr", ROUTE_FATE);\r
+ setRoute(resources, nodes, "ubw03-09.scr", ROUTE_UBW);\r
+ setRoute(resources, nodes, "ubw04-03.scr", ROUTE_UBW);\r
+ setRoute(resources, nodes, "hf04-11.scr", ROUTE_HF);\r
+ \r
+ SortedSet<String> sorted = new TreeSet<String>(StringUtil2.getStringComparator());\r
+ sorted.addAll(resources.keySet());\r
+ \r
+ for (int n = 0; n < 3; n++) { \r
+ StringBuilder sb = new StringBuilder();\r
+ for (String name : sorted) {\r
+ GraphNode node = nodes.get(name);\r
+ StringBuilder sb2 = new StringBuilder();\r
+ \r
+ String prefix = node.getName().substring(0, Math.min(node.getName().length(), 2));\r
+ int links = 0;\r
+ \r
+ sb2.append(node.getName()+" "+resources.get(name).getRoute());\r
+ sb2.append("\n");\r
+ \r
+ if (n < 2) {\r
+ sb2.append("\tin : ");\r
+ for (GraphNode in : node.incoming) {\r
+ if (n==0 || !in.getName().startsWith(prefix)) {\r
+ sb2.append(in.getName()+" ");\r
+ links++;\r
+ }\r
+ }\r
+ sb2.append("\n");\r
+ \r
+ sb2.append("\tout: ");\r
+ for (GraphNode in : node.outgoing) {\r
+ if (n==0 || !in.getName().startsWith(prefix)) {\r
+ sb2.append(in.getName()+" ");\r
+ links++;\r
+ }\r
+ }\r
+ sb2.append("\n"); \r
+ }\r
+ if (n == 0) {\r
+ sb.append(sb2);\r
+ sb.append("\n\n");\r
+ } else if (n == 2 || links > 0) {\r
+ sb.append(sb2);\r
+ }\r
+ }\r
+ try {\r
+ FileUtil.write(new File(outputFolder+"_graph"+n+".txt"), sb.toString());\r
+ } catch (IOException ioe) {\r
+ ioe.printStackTrace();\r
+ }\r
+ } \r
+ \r
+ System.out.println("Creating XML files...");\r
+\r
+ Map<Integer, Set<String>> res = new HashMap<Integer, Set<String>>();\r
+ for (FileInfo info : resources.values()) {\r
+ Set<String> set = res.get(info.getRoute());\r
+ if (set == null) {\r
+ res.put(info.getRoute(), set = new HashSet<String>());\r
+ }\r
+ \r
+ for (String s : info.background) set.add("background/"+s);\r
+ for (String s : info.foreground) set.add("foreground/"+s);\r
+ for (String s : info.sound) set.add("sound/"+s);\r
+ for (String s : info.music) set.add("music/"+s);\r
+ }\r
+ \r
+ return res;\r
+ }\r
+ \r
+ public void analyze(Language lang, int threads) {\r
+ long tstart = System.currentTimeMillis();\r
+ \r
+ Map<Integer, Set<String>> res = a();\r
+ \r
+ //Generate set of all global resources, including the font, info.txt, etc. \r
+ Set<String> globalResources = res.get(0);\r
+ globalResources.add("info.txt");\r
+ globalResources.add("default.ttf");\r
+ globalResources.add("thumbnail.png");\r
+ globalResources.add("icon.png");\r
+ globalResources.add("img.ini");\r
+ globalResources.add("save/global.sav");\r
+ addFolder(globalResources, "script", "script");\r
+ addFolder(globalResources, "foreground/special", "foreground/special");\r
+ addFolder(globalResources, "background/special", "background/special");\r
+ addFolder(globalResources, "sound/special", "sound/special");;\r
+ \r
+ FileMapper mapper = new FileMapper();\r
+ mapper.put("info.txt", "info-"+lang.getLangCode()+".txt");\r
+ try {\r
+ mapper.load(infoFolder+"/filenames.txt");\r
+ } catch (IOException ioe) {\r
+ Log.e("Exception loading filename mapping", ioe);\r
+ }\r
+ \r
+ for (Entry<Integer, Set<String>> entry : res.entrySet()) {\r
+ try {\r
+ hashFileSet(names.get(entry.getKey()), entry.getValue(), mapper);\r
+ } catch (IOException e) {\r
+ Log.e("Error hashing file set", e);\r
+ }\r
+ }\r
+\r
+ System.out.printf("Took %.2fs\nFinished\n", (System.currentTimeMillis()-tstart)/1000.0);\r
+ }\r
+\r
+ protected void hashFileSet(String name, Set<String> set, FileMapper mapper) throws IOException {\r
+ System.out.println("Hashing files ("+set.size()+")...");\r
+ \r
+ String outputFolder = infoFolder+"/dependency_analysis/";\r
+ String outputFilename = outputFolder+name+".xml";\r
+ File outputF = new File(outputFilename);\r
+\r
+ PrintWriter pout = new PrintWriter(new OutputStreamWriter(\r
+ new BufferedOutputStream(new FileOutputStream(outputF)), "UTF-8"));\r
+ try {\r
+ pout.println("<component name=\""+name+"\" desc=\"\">\n");\r
+\r
+ ExecutorService executor = Executors.newFixedThreadPool(2);\r
+ List<Future<String>> results = new ArrayList<Future<String>>();\r
+ \r
+ int t = 0;\r
+ Iterator<String> i = set.iterator();\r
+ while (t < set.size()) {\r
+ String strings[] = new String[Math.min(set.size()-t, 512)];\r
+ for (int n = 0; n < strings.length; n++) {\r
+ strings[n] = i.next();\r
+ }\r
+ \r
+ Callable<String> task = new FileHashingTask(strings, rootFolder, mapper);\r
+ results.add(executor.submit(task));\r
+ t += strings.length;\r
+ }\r
+ set.clear();\r
+ \r
+ for (Future<String> f : results) {\r
+ try {\r
+ pout.print(f.get());\r
+ } catch (ExecutionException e) {\r
+ Log.w("Exception getting hash results", e);\r
+ }\r
+ }\r
+\r
+ executor.shutdown(); \r
+ \r
+ pout.println("</component>\n");\r
+ } catch (InterruptedException e) {\r
+ Log.w("Wait for hashing thread pool interrupted", e);\r
+ } finally {\r
+ pout.close();\r
+ } \r
+ }\r
+ \r
+ private void addFolder(Set<String> globalResources, String sourceFolder, String targetFolder) {\r
+ File folder = new File(rootFolder+"/"+sourceFolder);\r
+ if (folder.exists()) {\r
+ for (File f : folder.listFiles()) {\r
+ if (f.isDirectory()) {\r
+ addFolder(globalResources, sourceFolder+'/'+f.getName(), targetFolder+'/'+f.getName());\r
+ } else {\r
+ globalResources.add(targetFolder+"/"+f.getName());\r
+ }\r
+ }\r
+ } else {\r
+ Log.w("Folder not found: " + folder.getAbsolutePath());\r
+ }\r
+ }\r
+ \r
+ protected void analyzeResources(String folder, FileInfo info) throws IOException {\r
+ String text = FileUtil.read(new File(folder, info.getFilename()));\r
+ for (String line : text.split("\\\n")) {\r
+ String parts[] = line.trim().split(" ");\r
+ \r
+ if (parts.length >= 2) {\r
+ if (parts[0].equals("bgload")) {\r
+ info.addBackground(parts[1]);\r
+ } else if (parts[0].equals("setimg")) {\r
+ info.addForeground(parts[1]);\r
+ } else if (parts[0].equals("sound")) {\r
+ info.addSound(parts[1]);\r
+ } else if (parts[0].equals("music")) {\r
+ info.addMusic(parts[1]);\r
+ } else if (parts[0].equals("jump")) {\r
+ info.addJump(parts[1]);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ protected void setRoute(Map<String, FileInfo> resources, Map<String, GraphNode> nodes,\r
+ String filename, int route)\r
+ {\r
+ if (filename.equals("main.scr") || filename.startsWith("special/")) {\r
+ return;\r
+ }\r
+ \r
+ FileInfo info = resources.get(filename);\r
+ GraphNode node = nodes.get(filename);\r
+ \r
+ if (info == null || info.getRoute() == route) {\r
+ return;\r
+ }\r
+ if (info.getRoute() != 0) {\r
+ System.err.println(filename+" route="+info.getRoute()+"+"+route);\r
+ } \r
+ \r
+ info.setRoute(route);\r
+ for (GraphNode out : node.outgoing) {\r
+ setRoute(resources, nodes, out.getName(), route);\r
+ }\r
+ }\r
+ \r
+ //Inner Classes\r
+ private static class FileInfo {\r
+ \r
+ private int route;\r
+ \r
+ private String filename;\r
+ private Set<String> background;\r
+ private Set<String> foreground;\r
+ private Set<String> sound;\r
+ private Set<String> music;\r
+ private Set<String> jumpsTo;\r
+ \r
+ public FileInfo(String filename) {\r
+ this.filename = filename;\r
+ \r
+ background = new HashSet<String>();\r
+ foreground = new HashSet<String>();\r
+ sound = new HashSet<String>();\r
+ music = new HashSet<String>();\r
+ jumpsTo = new HashSet<String>();\r
+ }\r
+ \r
+ //Functions\r
+ public void addBackground(String filename) {\r
+ if (!filename.startsWith("special/")) {\r
+ background.add(filename);\r
+ }\r
+ }\r
+ public void addForeground(String filename) {\r
+ if (!filename.startsWith("special/")) {\r
+ foreground.add(filename);\r
+ }\r
+ }\r
+ public void addSound(String filename) {\r
+ if (!filename.equals("~") && !filename.startsWith("special/")) {\r
+ sound.add(filename);\r
+ }\r
+ }\r
+ public void addMusic(String filename) {\r
+ if (!filename.equals("~") && !filename.startsWith("special/")) {\r
+ music.add(filename);\r
+ }\r
+ }\r
+ public void addJump(String filename) {\r
+ if (!filename.startsWith("special/")) {\r
+ jumpsTo.add(filename);\r
+ }\r
+ }\r
+ \r
+ //Getters\r
+ public int getRoute() { return route; }\r
+ public String getFilename() { return filename; }\r
+ \r
+ //Setters\r
+ public void setRoute(int r) { this.route = r; }\r
+\r
+ //Save Support\r
+ public void save(String filename) throws IOException {\r
+ StringBuilder sb = new StringBuilder();\r
+ \r
+ sb.append(String.format("Filename: %s\n", filename));\r
+ \r
+ sb.append("\n\njumps:\n");\r
+ for (String s : jumpsTo) { sb.append(s); sb.append('\n'); }\r
+ sb.append("\n\nbackgrounds:\n");\r
+ for (String s : background) { sb.append(s); sb.append('\n'); }\r
+ sb.append("\n\nforegrounds:\n");\r
+ for (String s : foreground) { sb.append(s); sb.append('\n'); }\r
+ sb.append("\n\nsounds & voices:\n");\r
+ for (String s : sound) { sb.append(s); sb.append('\n'); }\r
+ sb.append("\n\nmusic:\n");\r
+ for (String s : music) { sb.append(s); sb.append('\n'); }\r
+ \r
+ FileUtil.write(new File(filename), sb.toString());\r
+ }\r
+ }\r
+ \r
+ private static class GraphNode {\r
+ \r
+ private String name;\r
+ \r
+ public Set<GraphNode> incoming;\r
+ public Set<GraphNode> outgoing;\r
+ \r
+ public GraphNode(String name) {\r
+ this.name = name;\r
+ \r
+ incoming = new HashSet<GraphNode>();\r
+ outgoing = new HashSet<GraphNode>();\r
+ }\r
+ \r
+ public String getName() { return name; }\r
+ }\r
+ \r
+ private static class FileHashingTask implements Callable<String> {\r
+ \r
+ private boolean hash = true;\r
+ private String files[];\r
+ private String rootFolder;\r
+ private FileMapper nameMapping;\r
+ \r
+ public FileHashingTask(String files[], String rootFolder, FileMapper nameMapping) {\r
+ this.files = files;\r
+ this.rootFolder = rootFolder;\r
+ this.nameMapping = nameMapping;\r
+ }\r
+ \r
+ @Override\r
+ public String call() throws Exception {\r
+ StringBuilder sb = new StringBuilder(64 << 10);\r
+ for (String file : files) {\r
+ try {\r
+ String hash = hash(file);\r
+ if (hash != null) {\r
+ sb.append(hash);\r
+ }\r
+ } catch (Exception e) {\r
+ Log.w("Exception generating hash: " + file, e);\r
+ }\r
+ }\r
+ return sb.toString();\r
+ }\r
+ \r
+ private String hash(String filename) throws Exception {\r
+ try {\r
+ int fsize = 0;\r
+ String fhash = "";\r
+ if (hash) {\r
+ String rname = nameMapping.getOriginal(filename.substring(filename.lastIndexOf('/')+1));\r
+ if (rname == null) {\r
+ rname = filename;\r
+ }\r
+ \r
+ File file = new File(rootFolder+"/"+rname);\r
+ fsize = (int)file.length();\r
+ fhash = HashUtil.hashToString(HashUtil.generateHash(file));\r
+ }\r
+ \r
+ filename = filename.replaceAll("background/", "background.zip/background/");\r
+ filename = filename.replaceAll("foreground/", "foreground.zip/foreground/");\r
+ filename = filename.replaceAll("sound/", "sound.zip/sound/");\r
+ filename = filename.replaceAll("script/", "script.zip/script/");\r
+ filename = filename.replaceAll("music/", "sound/");\r
+ \r
+ if (hash) {\r
+ return String.format("\t<file url=\"base_install/%s\" size=\"%d\" hash=\"%s\" path=\"%s\"/>\n", filename, fsize, fhash, filename);\r
+ } else {\r
+ return String.format("\t<file url=\"base_install/%s\" path=\"%s\"/>\n", filename, filename);\r
+ }\r
+ } catch (FileNotFoundException fnfe) {\r
+ //System.err.println(fnfe);\r
+ }\r
+ \r
+ return null;\r
+ }\r
+\r
+ }\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.krkr.fate;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.InputStreamReader;\r
+import java.util.Map;\r
+\r
+import nl.weeaboo.common.StringUtil;\r
+\r
+public class RouteParser {\r
+ \r
+ private FateScriptConverter fateConv;\r
+ \r
+ public RouteParser(FateScriptConverter fc) {\r
+ this.fateConv = fc;\r
+ }\r
+ \r
+ //Functions\r
+ public void parse(File file, Map<String, String> appendMap) {\r
+ try {\r
+ BufferedReader in = new BufferedReader(new InputStreamReader(\r
+ new FileInputStream(file), fateConv.getSourceFileEncoding()));\r
+\r
+ String filenameNoExt = StringUtil.stripExtension(file.getName());\r
+ if (!isIncludedInThisRun(filenameNoExt)) {\r
+ return;\r
+ }\r
+ \r
+ String text;\r
+ while ((text = in.readLine()) != null) {\r
+ parse(scenarioFileRename(fateConv, filenameNoExt), text, appendMap);\r
+ }\r
+ \r
+ in.close(); \r
+ } catch (Exception e) {\r
+ System.err.println(file.getAbsolutePath());\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ \r
+ protected void parse(String filenameNoExt, String line, Map<String, String> appendMap) {\r
+ if (line.indexOf(';') < 0) {\r
+ return;\r
+ }\r
+ \r
+ String primeSplit[] = line.split(";");\r
+ int sceneNum = Integer.parseInt(primeSplit[0]);\r
+ String pm[] = primeSplit[1].split("\'");\r
+ \r
+ if (pm[0].equals("SCENE")) {\r
+ StringBuilder sb = new StringBuilder();\r
+\r
+ //String title = pm[10]; \r
+ int numberOfOperations = Integer.parseInt(pm[11]);\r
+ \r
+ //First do the operations...\r
+ String last = pm[12 + numberOfOperations];\r
+ if (!last.equals("0")) {\r
+ String pm2[] = last.split(":");\r
+ \r
+ int numberOfOperations2 = Integer.parseInt(pm2[0]);\r
+ for (int j = 0; j < numberOfOperations2; j++) {\r
+ sb.append(new FlowFlag(pm2[1+j], "o").operate()+"\n");\r
+ }\r
+ }\r
+ \r
+ //..and then jump, not the other way around :(\r
+ for (int j = 0; j < numberOfOperations; j++) {\r
+ String parts[] = pm[12+j].split(":");\r
+ \r
+ int numberOfPaths = Integer.parseInt(parts[0]);\r
+ int link = Integer.parseInt(parts[numberOfPaths + 1]); //1=Logical AND, 2=Logical OR\r
+ int target = Integer.parseInt(parts[numberOfPaths + 2]);\r
+\r
+ String jumpStr = "jump " + getScript(filenameNoExt, target) + "\n";\r
+ if (link == 1) {\r
+ String spaces = "";\r
+ for (int i = 0; i < numberOfPaths; i++) { \r
+ sb.append(spaces);\r
+ sb.append("if ");\r
+ sb.append(new FlowFlag(parts[1+i], "d").decide());\r
+ sb.append("\n");\r
+ spaces += " ";\r
+ }\r
+ sb.append(spaces + jumpStr);\r
+ for (int i = 0; i < numberOfPaths; i++) {\r
+ sb.append(spaces.substring(Math.min(spaces.length(), 2 * (i+1))));\r
+ sb.append("fi\n");\r
+ } \r
+ } else {\r
+ for (int i = 0; i < numberOfPaths; i++) {\r
+ sb.append("if ");\r
+ sb.append(new FlowFlag(parts[1+i], "d").decide());\r
+ sb.append("\n");\r
+ sb.append(" " + jumpStr + "\n");\r
+ sb.append("fi\n");\r
+ }\r
+ }\r
+ }\r
+ if (numberOfOperations == 0) {\r
+ sb.append("jump main.scr\n");\r
+ }\r
+ \r
+ appendMap.put(getAppendKey(filenameNoExt, sceneNum), sb.toString());\r
+ } else if (pm[0].equals("SELECTER")) {\r
+ //String title = pm[10];\r
+ int numberOfOperations = Integer.parseInt(pm[11]);\r
+ \r
+ String choices[] = new String[numberOfOperations];\r
+ for (int j = 0; j < numberOfOperations; j++) {\r
+ //Choice Display Text|scene number\r
+ choices[j] = pm[12 + j*3 + 2] + "|" + getScript(filenameNoExt, Integer.parseInt(pm[12+j*3]));\r
+ }\r
+ \r
+ String string = "jump main.scr\n";\r
+ if (numberOfOperations > 0) {\r
+ string = fateConv.createJump(choices);\r
+ }\r
+ \r
+ appendMap.put(getAppendKey(filenameNoExt, sceneNum), string);\r
+ } else if (pm[0].equals("OUTERLABEL")) {\r
+ //String title = (pm.length > 12 ? pm[12] : "");\r
+ String file = scenarioFileRename(fateConv, pm[10]);\r
+ \r
+ String jump;\r
+ if (file != null) { \r
+ int target = Integer.parseInt(pm[11]); \r
+ jump = "jump " + getScript(file, target) + "\n";\r
+ } else {\r
+ jump = "text <ERROR>\njump special/ulw.scr\n";\r
+ throw new RuntimeException("RouteParser :: Error parsing: " + filenameNoExt + " (" + pm[10] + ")");\r
+ }\r
+ appendMap.put(getAppendKey(filenameNoExt, sceneNum), jump);\r
+ }\r
+ }\r
+ \r
+ public boolean isIncludedInThisRun(String filename) {\r
+ filename = scenarioFileRename(fateConv.getAllowedRoutes(), filename);\r
+ \r
+ String prefixes[] = new String[] {"prologue", "fate", "ubw", "hf"};\r
+ for (int n = 0; n < fateConv.getAllowedRoutes(); n++) {\r
+ if (filename.startsWith(prefixes[n])) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+ public static String scenarioFileRename(FateScriptConverter fc, String file) {\r
+ return scenarioFileRename(fc.getAllowedRoutes(), file);\r
+ }\r
+ public static String scenarioFileRename(int allowedRoutes, String file) {\r
+ file = StringUtil.stripExtension(file);\r
+ \r
+ String replace[] = new String[] {\r
+ "プロローグ", "prologue",\r
+ "セイバールート", "fate",\r
+ "セイバーエピローグ", "fate-ending",\r
+ "凛ルート", "ubw",\r
+ "凛エピローグ", "ubw-ending",\r
+ "桜ルート", "hf",\r
+ "桜エピローグ", "hf-ending",\r
+ };\r
+ int L = allowedRoutes;\r
+ if (L >= 4) L++;\r
+ if (L >= 3) L++;\r
+ if (L >= 2) L++;\r
+ \r
+ int n;\r
+ for (n = 0; n < L * 2; n+=2) {\r
+ if (file.startsWith(replace[n])) {\r
+ String r = scenarioFileRename2(file.substring(replace[n].length(), file.length()));\r
+ file = replace[n+1];\r
+ if (r != null) file += r; \r
+ return file;\r
+ }\r
+ }\r
+ return file;\r
+ }\r
+ private static String scenarioFileRename2(String part) {\r
+ String replace[] = new String[] {\r
+ "1日目", "00",\r
+ "2日目", "01",\r
+ "3日目", "02",\r
+ "一日目", "01",\r
+ "二日目", "02",\r
+ "三日目", "03",\r
+ "四日目", "04",\r
+ "五日目", "05",\r
+ "六日目", "06",\r
+ "七日目", "07",\r
+ "八日目", "08",\r
+ "九日目", "09", \r
+ "十日目", "10",\r
+ "十一日目", "11",\r
+ "十二日目", "12",\r
+ "十三日目", "13",\r
+ "十四日目", "14",\r
+ "十五日目", "15",\r
+ "十六日目", "16"\r
+ };\r
+ \r
+ for (int n = replace.length-2; n >= 0; n -= 2) {\r
+ int index = part.indexOf(replace[n]);\r
+ if (index >= 0) {\r
+ part = part.substring(0, index) + replace[n+1] +\r
+ part.substring(index+replace[n].length());\r
+ }\r
+ }\r
+ return part;\r
+ }\r
+ \r
+ //Getters\r
+ private String getAppendKey(String filenameNoExt, int target) {\r
+ return filenameNoExt + "-" + (target<10?"0":"") + target + ".ks";\r
+ }\r
+ private String getScript(String filenameNoExt, int target) {\r
+ return filenameNoExt + "-" + (target<10?"0":"") + target + ".scr";\r
+ }\r
+ \r
+ //Setters\r
+ \r
+ //Inner Classes\r
+ private static class FlowFlag {\r
+ \r
+ String name;\r
+ int value0, value1;\r
+\r
+ public FlowFlag(String str, String type) {\r
+ try {\r
+ int pos;\r
+ name = str.substring(0, pos = str.indexOf("//"));\r
+ str = str.substring(pos+2);\r
+ value0 = Integer.parseInt(str.substring(0, pos = str.indexOf("//")));\r
+ str = str.substring(pos+2);\r
+ value1 = Integer.parseInt(str); \r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ public String decide() {\r
+ String operators[] = new String[] {"==", "!=", "<", ">", "<=", ">="};\r
+ return name + " " + operators[value0] + " " + value1;\r
+ }\r
+\r
+ public String operate() {\r
+ String command = "setvar";\r
+ if (name.startsWith("g")) {\r
+ command = "gsetvar";\r
+ }\r
+ \r
+ if (value1 == 1) { //Add\r
+ return command + " " + name + " + " + value0;\r
+ } else if (value1 == 2) { //Substract\r
+ return command + " " + name + " - " + value0;\r
+ } else if (value1 == 3) { //Set\r
+ return command + " " + name + " = " + value0;\r
+ }\r
+ return "#Error in FlowFlag.operate()";\r
+ }\r
+\r
+ }\r
+\r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Dimension;\r
+import java.awt.GridLayout;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.awt.image.BufferedImage;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.OutputStream;\r
+import java.io.PrintStream;\r
+import java.io.UnsupportedEncodingException;\r
+import java.net.URL;\r
+import java.nio.ByteBuffer;\r
+import java.util.ArrayDeque;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.concurrent.TimeUnit;\r
+\r
+import javax.imageio.ImageIO;\r
+import javax.swing.Box;\r
+import javax.swing.BoxLayout;\r
+import javax.swing.JButton;\r
+import javax.swing.JCheckBox;\r
+import javax.swing.JComboBox;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JOptionPane;\r
+import javax.swing.JPanel;\r
+import javax.swing.JScrollBar;\r
+import javax.swing.JScrollPane;\r
+import javax.swing.JSpinner;\r
+import javax.swing.JTextArea;\r
+import javax.swing.SpinnerNumberModel;\r
+import javax.swing.SwingUtilities;\r
+import javax.swing.border.EmptyBorder;\r
+\r
+import nl.weeaboo.awt.AwtUtil;\r
+import nl.weeaboo.awt.FileBrowseField;\r
+import nl.weeaboo.awt.IconListCellRenderer;\r
+import nl.weeaboo.awt.Sash;\r
+import nl.weeaboo.common.Dim;\r
+import nl.weeaboo.common.ScaleUtil;\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.vnds.tools.ImageConverter.ConvertType;\r
+import nl.weeaboo.vnds.tools.SoundConverter;\r
+\r
+@SuppressWarnings("serial")\r
+public abstract class AbstractConversionGUI extends JFrame implements Runnable {\r
+\r
+ protected final String gameFolderName;\r
+ protected final boolean hasVoices;\r
+ private Dim originalSize;\r
+ \r
+ private JPanel propertyPanel;\r
+ protected final FileBrowseField gameFolderField;\r
+ protected final FileBrowseField outputFolderField;\r
+ protected final JComboBox targetCombo;\r
+ protected final JCheckBox cleanTempFilesButton;\r
+ protected final JSpinner threadsSpinner;\r
+ protected final JComboBox pngQuantCombo;\r
+ protected final JSpinner musicVolumeSpinner;\r
+ protected final JSpinner sfxVolumeSpinner;\r
+ protected final JCheckBox voiceCheck;\r
+ \r
+ private final JSpinner jpgQualitySpinner;\r
+ private final JLabel mp3AverageBitrateLabel;\r
+ private final JSpinner mp3AverageBitrateSpinner;\r
+ private final JLabel mp3MaxBitrateLabel;\r
+ private final JSpinner mp3MaxBitrateSpinner;\r
+ private final JLabel aacQualityLabel;\r
+ private final JSpinner aacQualitySpinner;\r
+ private final JLabel vorbisQualityLabel;\r
+ private final JComboBox vorbisQualityCombo;\r
+ \r
+ private JTextArea outputField;\r
+ private JScrollPane outputFieldScrollPane;\r
+ private JButton startButton;\r
+ \r
+ public AbstractConversionGUI(String title, URL iconURL, File defaultSrcF, File defaultDstF,\r
+ String gameFolderName, boolean hasVoices, Dim originalSize)\r
+ {\r
+ this.gameFolderName = gameFolderName;\r
+ this.hasVoices = hasVoices;\r
+ this.originalSize = originalSize;\r
+ \r
+ setTitle(title);\r
+ try {\r
+ BufferedImage icon = ImageIO.read(iconURL);\r
+ AwtUtil.setFrameIcon(this, icon);\r
+ } catch (IOException ioe) {\r
+ Log.w("Exception settings frame icon", ioe);\r
+ }\r
+ \r
+ outputField = new JTextArea(15, 60);\r
+ outputField.setEditable(false);\r
+ outputFieldScrollPane = new JScrollPane(outputField);\r
+ outputFieldScrollPane.setVisible(false); \r
+ \r
+ startButton = new JButton("Start");\r
+ startButton.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent e) {\r
+ if (!preConvertCheck(gameFolderField.getFile(),\r
+ outputFolderField.getFile()))\r
+ {\r
+ return;\r
+ }\r
+ \r
+ OutputStream outstream = new OutputStream() {\r
+ private final int CHUNKS_LEN_MAX = 128 << 10;\r
+ private ArrayDeque<Integer> chunkLengths = new ArrayDeque<Integer>();\r
+ private int chunksLengthsTotal = 0;\r
+ private ByteBuffer buf = ByteBuffer.allocate(1024);\r
+ \r
+ public synchronized void write(int b) throws IOException {\r
+ buf.put((byte)b);\r
+ if (b == '\n' || !buf.hasRemaining()) {\r
+ {\r
+ final String line = StringUtil.fromUTF8(buf.array(), buf.arrayOffset(), buf.position());\r
+ chunkLengths.add(line.length());\r
+ chunksLengthsTotal += line.length();\r
+ buf.rewind();\r
+ \r
+ SwingUtilities.invokeLater(new Runnable() {\r
+ public void run() {\r
+ outputField.append(line);\r
+ \r
+ JScrollBar vertical = outputFieldScrollPane.getVerticalScrollBar();\r
+ if (vertical != null) {\r
+ vertical.setValue(vertical.getMaximum());\r
+ }\r
+ }\r
+ }); \r
+ }\r
+\r
+ int removed = 0;\r
+ while (chunkLengths.size() > 1 && chunksLengthsTotal > CHUNKS_LEN_MAX) {\r
+ removed += chunkLengths.removeFirst();\r
+ }\r
+ chunksLengthsTotal -= removed;\r
+ \r
+ final int endpos = removed;\r
+ SwingUtilities.invokeLater(new Runnable() {\r
+ public void run() {\r
+ outputField.replaceRange("", 0, endpos);\r
+ }\r
+ });\r
+ } \r
+ }\r
+ };\r
+ \r
+ try {\r
+ System.setOut(new PrintStream(outstream, true, "UTF-8"));\r
+ System.setErr(new PrintStream(outstream, true, "UTF-8")); \r
+ } catch (UnsupportedEncodingException uee) {\r
+ Log.w("Changing sysout and syserr not supported", uee);\r
+ \r
+ System.setOut(new PrintStream(outstream));\r
+ System.setErr(new PrintStream(outstream));\r
+ }\r
+ Log.initDefaultHandler(); //Needs to be done since System.err has changed\r
+\r
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);\r
+ propertyPanel.setVisible(false);\r
+ outputFieldScrollPane.setVisible(true);\r
+ startButton.setVisible(false);\r
+ pack();\r
+ \r
+ Thread t = new Thread(AbstractConversionGUI.this);\r
+ t.start();\r
+ }\r
+ });\r
+ \r
+ gameFolderField = FileBrowseField.writeFolder("", defaultSrcF); \r
+ outputFolderField = FileBrowseField.writeFolder("", defaultDstF);\r
+\r
+ targetCombo = new JComboBox(TargetPlatform.values());\r
+ targetCombo.setSelectedItem(TargetPlatform.NINTENDO_DS);\r
+ targetCombo.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent e) {\r
+ onTargetChanged((TargetPlatform)targetCombo.getSelectedItem());\r
+ }\r
+ });\r
+ \r
+ pngQuantCombo = new JComboBox(new Object[] {\r
+ ConvertType.TYPE_PNG, ConvertType.TYPE_PNG_256_NEUQUANT, ConvertType.TYPE_PNG_256_MEDIAN\r
+ });\r
+ pngQuantCombo.setRenderer(new IconListCellRenderer() {\r
+ public String getLabelFor(Object object) {\r
+ if (object instanceof ConvertType) {\r
+ ConvertType ct = (ConvertType)object;\r
+ if (ct == ConvertType.TYPE_PNG) return "Full Color (Default)";\r
+ else if (ct == ConvertType.TYPE_PNG_256_NEUQUANT) return "256 Colors (Neuquant)";\r
+ else if (ct == ConvertType.TYPE_PNG_256_MEDIAN) return "256 Colors (Median)";\r
+ }\r
+ return null;\r
+ }\r
+ });\r
+ //pngQuantCombo.setSelectedItem(ConvertType.TYPE_PNG_256_NEUQUANT);\r
+ pngQuantCombo.setSelectedItem(ConvertType.TYPE_PNG);\r
+ \r
+ jpgQualitySpinner = new JSpinner(new SpinnerNumberModel(97, 0, 100, 1));\r
+ mp3AverageBitrateLabel = new JLabel("MP3 Avg Bitrate");\r
+ mp3AverageBitrateSpinner = new JSpinner(new SpinnerNumberModel(64, 16, 128, 16));\r
+ mp3MaxBitrateLabel = new JLabel("MP3 Max Bitrate");\r
+ mp3MaxBitrateSpinner = new JSpinner(new SpinnerNumberModel(96, 16, 128, 16));\r
+ aacQualityLabel = new JLabel("AAC Quality Factor");\r
+ aacQualitySpinner = new JSpinner(new SpinnerNumberModel(SoundConverter.AAC_Q_HIGH, 10, 200, 5)); \r
+ vorbisQualityLabel = new JLabel("Vorbis Audio Quality");\r
+ vorbisQualityCombo = new JComboBox(new Object[] {SoundConverter.VORBIS_Q_LOW, SoundConverter.VORBIS_Q_MED, SoundConverter.VORBIS_Q_HIGH});\r
+ vorbisQualityCombo.setSelectedItem(SoundConverter.VORBIS_Q_MED);\r
+ vorbisQualityCombo.setRenderer(new IconListCellRenderer() {\r
+ public String getLabelFor(Object object) {\r
+ if (object instanceof Number) {\r
+ int val = ((Number)object).intValue();\r
+ if (val == SoundConverter.VORBIS_Q_LOW) return "Low (-aq " + val + ")";\r
+ if (val == SoundConverter.VORBIS_Q_MED) return "Med (-aq " + val + ")";\r
+ if (val == SoundConverter.VORBIS_Q_HIGH) return "High (-aq " + val + ")";\r
+ }\r
+ return super.getLabelFor(object);\r
+ }\r
+ });\r
+ \r
+ musicVolumeSpinner = new JSpinner(new SpinnerNumberModel(100, 10, 500, 25));\r
+ sfxVolumeSpinner = new JSpinner(new SpinnerNumberModel(100, 10, 500, 25));\r
+ \r
+ voiceCheck = new JCheckBox();\r
+ voiceCheck.setSelected(hasVoices);\r
+ \r
+ cleanTempFilesButton = new JCheckBox();\r
+ cleanTempFilesButton.setSelected(true);\r
+ threadsSpinner = new JSpinner(new SpinnerNumberModel(Runtime.getRuntime().availableProcessors(), 1, 8, 1));\r
+ \r
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);\r
+ setMinimumSize(new Dimension(375, 300));\r
+ setLayout(new BorderLayout());\r
+ }\r
+ \r
+ //Functions\r
+ public void create() {\r
+ propertyPanel = createPropertyPanel();\r
+\r
+ add(propertyPanel, BorderLayout.NORTH);\r
+ add(outputFieldScrollPane, BorderLayout.CENTER);\r
+ add(startButton, BorderLayout.SOUTH);\r
+ \r
+ onTargetChanged((TargetPlatform)targetCombo.getSelectedItem());\r
+ \r
+ pack();\r
+ setLocationRelativeTo(null);\r
+ setVisible(true); \r
+ }\r
+ \r
+ protected void onTargetChanged(TargetPlatform platform) {\r
+ boolean android = (platform != null && platform.isAndroid());\r
+ \r
+ mp3AverageBitrateLabel.setEnabled(!android);\r
+ mp3AverageBitrateSpinner.setEnabled(!android);\r
+ mp3MaxBitrateLabel.setEnabled(!android);\r
+ mp3MaxBitrateSpinner.setEnabled(!android);\r
+ aacQualityLabel.setEnabled(!android);\r
+ aacQualitySpinner.setEnabled(!android);\r
+ vorbisQualityLabel.setEnabled(android);\r
+ vorbisQualityCombo.setEnabled(android); \r
+ }\r
+ \r
+ private JPanel createPropertyPanel() { \r
+ JPanel panel1 = new JPanel(new GridLayout(-1, 2, 5, 5));\r
+ fillPathsPanel(panel1);\r
+ \r
+ JPanel settingsPanel = new JPanel(new GridLayout(-1, 2, 5, 5));\r
+ fillSettingsPanel(settingsPanel);\r
+\r
+ JPanel converterSettingsPanel = new JPanel(new GridLayout(-1, 2, 5, 5));\r
+ fillConverterSettingsPanel(converterSettingsPanel);\r
+ \r
+ JPanel otherPanel = new JPanel(new GridLayout(-1, 2, 5, 5));\r
+ otherPanel.add(new JLabel("Clean intermediate files?")); otherPanel.add(cleanTempFilesButton);\r
+ otherPanel.add(new JLabel("Threads")); otherPanel.add(threadsSpinner);\r
+ \r
+ JPanel panel = new JPanel();\r
+ panel.setBorder(new EmptyBorder(10, 10, 10, 10));\r
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));\r
+ panel.add(panel1);\r
+ panel.add(Box.createVerticalStrut(10));\r
+ panel.add(new Sash(Sash.HORIZONTAL));\r
+ panel.add(Box.createVerticalStrut(10));\r
+ panel.add(settingsPanel);\r
+ panel.add(Box.createVerticalStrut(15));\r
+ panel.add(converterSettingsPanel);\r
+ panel.add(Box.createVerticalStrut(10));\r
+ panel.add(new Sash(Sash.HORIZONTAL));\r
+ panel.add(Box.createVerticalStrut(10));\r
+ panel.add(otherPanel);\r
+ \r
+ JPanel outerPanel = new JPanel(new BorderLayout());\r
+ outerPanel.setBackground(panel.getBackground().darker());\r
+ outerPanel.add(panel);\r
+ return outerPanel;\r
+ }\r
+ \r
+ protected void fillPathsPanel(JPanel panel) {\r
+ panel.add(new JLabel("Game Folder")); panel.add(gameFolderField);\r
+ panel.add(new JLabel("Output Folder")); panel.add(outputFolderField);\r
+ }\r
+ \r
+ protected void fillSettingsPanel(JPanel panel) {\r
+ panel.add(new JLabel("Target Platform")); panel.add(targetCombo); \r
+ \r
+ if (hasVoices) {\r
+ panel.add(new JLabel("Enable Voices")); panel.add(voiceCheck);\r
+ }\r
+ }\r
+ \r
+ protected void fillConverterSettingsPanel(JPanel panel) {\r
+ panel.add(new JLabel("Music Volume")); panel.add(musicVolumeSpinner);\r
+ panel.add(new JLabel("SFX Volume")); panel.add(sfxVolumeSpinner);\r
+ panel.add(new JLabel("PNG Type")); panel.add(pngQuantCombo);\r
+ panel.add(new JLabel("JPG Quality %")); panel.add(jpgQualitySpinner);\r
+ panel.add(mp3AverageBitrateLabel); panel.add(mp3AverageBitrateSpinner);\r
+ panel.add(mp3MaxBitrateLabel); panel.add(mp3MaxBitrateSpinner);\r
+ panel.add(aacQualityLabel); panel.add(aacQualitySpinner);\r
+ panel.add(vorbisQualityLabel); panel.add(vorbisQualityCombo);\r
+ }\r
+ \r
+ protected boolean preConvertCheck(File gameFolder, File outputFolder) {\r
+ String gameFolderS = outputFolder.getAbsolutePath();\r
+ for (int n = 0; n < gameFolderS.length(); n++) {\r
+ if (gameFolderS.charAt(n) > 127) {\r
+ JOptionPane.showMessageDialog(AbstractConversionGUI.this,\r
+ "Game folder contains non-ASCII characters, conversion may fail",\r
+ "Warning", JOptionPane.WARNING_MESSAGE);\r
+ break;\r
+ }\r
+ }\r
+\r
+ String outputFolderS = outputFolder.getAbsolutePath();\r
+ for (int n = 0; n < outputFolderS.length(); n++) {\r
+ if (outputFolderS.charAt(n) > 127) {\r
+ JOptionPane.showMessageDialog(AbstractConversionGUI.this,\r
+ "Please use an ASCII-only path (like C:\\temp) for the temp folder",\r
+ "Error", JOptionPane.ERROR_MESSAGE);\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ return true;\r
+ }\r
+ \r
+ @Override\r
+ public final void run() { \r
+ try {\r
+ convert();\r
+ } finally { \r
+ SwingUtilities.invokeLater(new Runnable() {\r
+ public void run() {\r
+ //propertyPanel.setVisible(true);\r
+ //outputFieldScrollPane.setVisible(true); \r
+ //startButton.setVisible(true);\r
+ pack();\r
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);\r
+ }\r
+ });\r
+ }\r
+ }\r
+ \r
+ protected void convert() {\r
+ File gameFolder = gameFolderField.getFile();\r
+ File outputFolder = outputFolderField.getFile();\r
+ TargetPlatform target = (TargetPlatform)targetCombo.getSelectedItem();\r
+ \r
+ convert(gameFolder, outputFolder, target);\r
+ }\r
+ \r
+ protected void convert(File srcFolder, File dstFolder, TargetPlatform target) {\r
+ long startTime = System.currentTimeMillis();\r
+ \r
+ List<String> list = new ArrayList<String>();\r
+ \r
+ Dim imageSize = ScaleUtil.scaleProp(256, 192, target.getWidth(), target.getHeight());\r
+ if (imageSize.w > originalSize.w || imageSize.h > originalSize.h) {\r
+ imageSize = ScaleUtil.scaleProp(256, 192, originalSize.w, originalSize.h);\r
+ }\r
+\r
+ boolean noVoice = !hasVoices || !voiceCheck.isSelected();\r
+ int musicVolume = (Integer)musicVolumeSpinner.getValue();\r
+ int sfxVolume = (Integer)sfxVolumeSpinner.getValue();\r
+ ConvertType pngQuant = (ConvertType)pngQuantCombo.getSelectedItem();\r
+ int jpgQuality = (Integer)jpgQualitySpinner.getValue();\r
+ int mp3_avgb = (Integer)mp3AverageBitrateSpinner.getValue();\r
+ int mp3_minb = 8;\r
+ int mp3_maxb = (Integer)mp3MaxBitrateSpinner.getValue();\r
+ int aacQuality = (Integer)aacQualitySpinner.getValue();\r
+ int vorbisQuality = (Integer)vorbisQualityCombo.getSelectedItem();\r
+\r
+ boolean cleanTempFiles = cleanTempFilesButton.isSelected();\r
+ int threads = (Integer)threadsSpinner.getValue();\r
+ \r
+ { //Extract data from game install\r
+ list.clear();\r
+\r
+ if (originalSize != null) {\r
+ list.add("-sourceSize");\r
+ list.add(""+originalSize.w);\r
+ list.add(""+originalSize.h);\r
+ }\r
+ \r
+ if (target != null) {\r
+ list.add("-targetSize");\r
+ list.add(""+imageSize.w);\r
+ list.add(""+imageSize.h);\r
+ }\r
+ \r
+ if (target.isAndroid()) {\r
+ list.add("-android");\r
+ }\r
+ \r
+ if (noVoice) {\r
+ list.add("-novoice");\r
+ }\r
+\r
+ list.add("-musicvol");\r
+ list.add(""+musicVolume);\r
+\r
+ list.add("-sfxvol");\r
+ list.add(""+sfxVolume);\r
+\r
+ if (pngQuant != null && pngQuant != ConvertType.TYPE_PNG) {\r
+ if (pngQuant == ConvertType.TYPE_PNG_256_MEDIAN) {\r
+ list.add("-pngquant");\r
+ list.add("median"); \r
+ } else if (pngQuant == ConvertType.TYPE_PNG_256_NEUQUANT) {\r
+ list.add("-pngquant");\r
+ list.add("neuquant"); \r
+ } else {\r
+ Log.w("Unknown PNG quantization type: " + pngQuant);\r
+ }\r
+ }\r
+ \r
+ list.add("-jpg");\r
+ list.add(""+jpgQuality); \r
+\r
+ list.add("-mp3");\r
+ list.add(""+mp3_avgb); \r
+ list.add(""+mp3_minb); \r
+ list.add(""+mp3_maxb); \r
+ \r
+ list.add("-aac");\r
+ list.add(""+aacQuality); \r
+\r
+ list.add("-vorbis");\r
+ list.add(""+vorbisQuality); \r
+\r
+ list.add("-threads");\r
+ list.add(""+threads);\r
+ \r
+ callResourceConverter(\r
+ getTemplateFolder(gameFolderName).getAbsolutePath(),\r
+ srcFolder.getAbsolutePath(),\r
+ dstFolder.getAbsolutePath(), list.toArray(new String[0]));\r
+ }\r
+ \r
+ System.gc();\r
+ \r
+ { //Convert script\r
+ callScriptConverter(new File(dstFolder, "_original").toString(),\r
+ new File(dstFolder, "_generated").toString());\r
+ }\r
+\r
+ System.gc();\r
+\r
+ { //Create installation package\r
+ callPacker(new File(dstFolder, "_generated").toString(),\r
+ getPackerTargetFolder(dstFolder, gameFolderName).toString());\r
+ }\r
+ \r
+ if (cleanTempFiles) {\r
+ Log.v("Cleaning temp files...");\r
+ FileUtil.deleteFolder(new File(dstFolder, "_original"));\r
+ FileUtil.deleteFolder(new File(dstFolder, "_generated"));\r
+ }\r
+\r
+ Log.v("========================================");\r
+ Log.v(StringUtil.formatTime(System.currentTimeMillis()-startTime, TimeUnit.MILLISECONDS)\r
+ + " Entire conversion finished."); \r
+ }\r
+ \r
+ protected abstract void callResourceConverter(String templateFolder, String srcFolder,\r
+ String dstFolder, String... args);\r
+\r
+ protected abstract void callScriptConverter(String srcFolder, String dstFolder);\r
+ \r
+ protected abstract void callPacker(String srcFolder, String dstFolder);\r
+ \r
+ protected File getTemplateFolder(String gameFolderName) {\r
+ return new File("template", gameFolderName); \r
+ }\r
+ \r
+ protected File getPackerTargetFolder(File base, String gameFolderName) {\r
+ return new File(base, gameFolderName); \r
+ }\r
+ \r
+ protected Dim getOriginalSize() {\r
+ return originalSize;\r
+ }\r
+ \r
+ protected void setOriginalSize(int w, int h) {\r
+ originalSize = new Dim(w, h);\r
+ }\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileNotFoundException;\r
+import java.io.IOException;\r
+import java.io.InputStreamReader;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.TreeMap;\r
+\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileCollectFilter;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.zip.ZipUtil;\r
+import nl.weeaboo.zip.ZipUtil.Compression;\r
+\r
+public abstract class AbstractPacker {\r
+\r
+ protected final File srcF;\r
+ protected final File dstF;\r
+ protected ResourcesUsed resUsed;\r
+ \r
+ public AbstractPacker(File srcF, File dstF) {\r
+ this.srcF = srcF;\r
+ this.dstF = dstF;\r
+ \r
+ resUsed = new ResourcesUsed();\r
+ resUsed.load(srcF, false);\r
+ }\r
+ \r
+ //Functions\r
+ \r
+ public void pack() {\r
+ Log.v("Packing " + dstF.getName() + "...");\r
+ \r
+ FileUtil.deleteFolder(dstF);\r
+ dstF.mkdirs();\r
+ \r
+ Map<String, File> files = new TreeMap<String, File>();\r
+ \r
+ //Foreground\r
+ Log.v("Sprites...");\r
+ final File srcForegroundF = new File(srcF, "foreground");\r
+ final File dstForegroundF = new File(dstF, "foreground");\r
+ dstForegroundF.mkdirs();\r
+ FileUtil.collectFiles(files, srcForegroundF, false, false, new FileCollectFilter() {\r
+ public boolean accept(String relpath, File f) {\r
+ return f.isDirectory() || relpath.startsWith("special") || resUsed.isForegroundUsed(relpath);\r
+ }\r
+ });\r
+ resourceZip(new File(dstF, "foreground.zip"), files);\r
+ files.clear();\r
+ \r
+ //Background\r
+ Log.v("Backgrounds...");\r
+ final File srcBackgroundF = new File(srcF, "background");\r
+ final File dstBackgroundF = new File(dstF, "background");\r
+ dstBackgroundF.mkdirs();\r
+ FileUtil.collectFiles(files, srcBackgroundF, false, false, new FileCollectFilter() {\r
+ public boolean accept(String relpath, File f) {\r
+ return f.isDirectory() || relpath.startsWith("special") || resUsed.isBackgroundUsed(relpath);\r
+ }\r
+ });\r
+ resourceZip(new File(dstF, "background.zip"), files);\r
+ files.clear();\r
+\r
+ //Script\r
+ Log.v("Scripts...");\r
+ final File srcScriptF = new File(srcF, "script");\r
+ final File dstScriptF = new File(dstF, "script");\r
+ dstScriptF.mkdirs();\r
+ FileUtil.collectFiles(files, srcScriptF, false, false, new FileCollectFilter() {\r
+ public boolean accept(String relpath, File file) {\r
+ return file.isDirectory() || relpath.endsWith(".scr");\r
+ }\r
+ });\r
+ for (Entry<String, File> entry : files.entrySet()) {\r
+ String relpath = entry.getKey();\r
+ File a = entry.getValue();\r
+ File b = new File(dstScriptF, relpath);\r
+ b.getParentFile().mkdirs();\r
+ try {\r
+ optimizeScript(relpath, a, b);\r
+ } catch (IOException e) {\r
+ Log.w("Error copying file: " + a + " to " + b);\r
+ }\r
+ } \r
+ files.clear();\r
+ \r
+ FileUtil.collectFiles(files, dstScriptF, false);\r
+ resourceZip(new File(dstF, "script.zip"), files);\r
+ for (File file : files.values()) {\r
+ file.delete();\r
+ }\r
+ files.clear();\r
+ \r
+ //Sound\r
+ Log.v("Music...");\r
+ final File srcSoundF = new File(srcF, "sound");\r
+ final File dstSoundF = new File(dstF, "sound");\r
+ dstSoundF.mkdirs();\r
+ FileUtil.collectFiles(files, srcSoundF, false, false, new FileCollectFilter() {\r
+ public boolean accept(String relpath, File f) {\r
+ if (f.isDirectory()) return true;\r
+ \r
+ if (relpath.endsWith(".mp3")) {\r
+ //Big fat hack: sneakily copy any music files we encounter, but don't\r
+ //include them in the ZIP\r
+ tryCopy(srcSoundF, relpath, dstSoundF);\r
+ return false;\r
+ }\r
+ \r
+ return relpath.startsWith("special/")\r
+ || resUsed.isSoundUsed(relpath)\r
+ || resUsed.isMusicUsed(relpath);\r
+ }\r
+ });\r
+\r
+ Log.v("Sound...");\r
+ resourceZip(new File(dstF, "sound.zip"), files);\r
+ files.clear();\r
+ \r
+ //Other\r
+ tryCopy(srcF, "img.ini", dstF);\r
+ tryCopy(srcF, "info.txt", dstF);\r
+ tryCopy(srcF, "default.ttf", dstF);\r
+ \r
+ Map<String, File> icons = new HashMap<String, File>();\r
+ FileUtil.collectFiles(icons, srcF, false, false, new FileCollectFilter() {\r
+ @Override\r
+ public boolean accept(String relpath, File file) {\r
+ String fext = StringUtil.getExtension(relpath);\r
+ return fext.equalsIgnoreCase("jpg") || fext.equalsIgnoreCase("png");\r
+ }\r
+ });\r
+ for (Entry<String, File> entry : icons.entrySet()) {\r
+ tryCopy(srcF, entry.getKey(), dstF);\r
+ }\r
+ \r
+ try {\r
+ packMore();\r
+ } catch (IOException e) {\r
+ Log.e("Error in packMore()", e);\r
+ }\r
+ \r
+ Log.v("Done");\r
+ }\r
+ \r
+ protected void packMore() throws IOException { \r
+ }\r
+ \r
+ protected void resourceZip(File dst, Map<String, File> files) {\r
+ String prefix = StringUtil.stripExtension(dst.getName()) + '/';\r
+ \r
+ Map<String, File> newFiles = new HashMap<String, File>();\r
+ for (Entry<String, File> entry : files.entrySet()) {\r
+ newFiles.put(prefix+entry.getKey(), entry.getValue());\r
+ }\r
+ \r
+ if (!newFiles.isEmpty()) {\r
+ try {\r
+ ZipUtil.zip(dst, newFiles, Compression.NONE);\r
+ } catch (IOException ioe) {\r
+ Log.w("Error zipping file: " + dst, ioe);\r
+ }\r
+ }\r
+ }\r
+ \r
+ protected void optimizeScript(String relpath, File src, File dst) throws IOException {\r
+ if (relpath.equals("main.scr") || relpath.startsWith("special/")) {\r
+ FileUtil.copyFile(src, dst);\r
+ } else {\r
+ StringBuilder sb = new StringBuilder((int)src.length());\r
+ \r
+ BufferedReader in = new BufferedReader(new InputStreamReader(\r
+ new FileInputStream(src), "UTF-8"));\r
+ try {\r
+ String line;\r
+ while ((line = in.readLine()) != null) {\r
+ if (line.startsWith("#") || line.trim().isEmpty()) {\r
+ continue;\r
+ }\r
+ \r
+ if (line.startsWith("text ")) {\r
+ line = optimizeTextLine(line);\r
+ if (line != null && line.length() > 0) {\r
+ sb.append(line);\r
+ if (!line.endsWith("\n")) {\r
+ sb.append('\n');\r
+ }\r
+ }\r
+ } else {\r
+ sb.append(line);\r
+ sb.append('\n');\r
+ }\r
+ }\r
+ } finally {\r
+ in.close();\r
+ }\r
+ \r
+ FileUtil.write(dst, sb.toString());\r
+ }\r
+ }\r
+ \r
+ protected String optimizeTextLine(String line) {\r
+ return line.replaceAll("\\s+", " ");\r
+ }\r
+\r
+ protected static void deleteFiles(Map<String, File> files) {\r
+ for (File file : files.values()) {\r
+ if (!file.isDirectory()) {\r
+ file.delete();\r
+ }\r
+ }\r
+ }\r
+ \r
+ protected static boolean tryCopy(File srcFolder, String relpath, File dstFolder) {\r
+ File srcF = new File(srcFolder, relpath);\r
+ if (!srcF.exists()) return false;\r
+ \r
+ File dstF = new File(dstFolder, relpath);\r
+ dstF.getParentFile().mkdirs();\r
+ try {\r
+ FileUtil.copyFile(srcF, dstF);\r
+ return true;\r
+ } catch (FileNotFoundException e) {\r
+ //Ignore\r
+ //Log.w("Error copying file: " + srcF + " to " + dstF);\r
+ } catch (IOException e) {\r
+ Log.w("Error copying file: " + srcF + " to " + dstF);\r
+ }\r
+ return false;\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import nl.weeaboo.common.Dim;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.vnds.tools.ImageConverter;\r
+import nl.weeaboo.vnds.tools.ImageConverter.ConvertType;\r
+import nl.weeaboo.vnds.tools.ImageConverter.DitheringType;\r
+import nl.weeaboo.vnds.tools.ImageConverter.ScalingType;\r
+import nl.weeaboo.vnds.tools.SoundConverter;\r
+\r
+public abstract class AbstractResourceConverter {\r
+\r
+ private boolean dither = true;\r
+ private int pngQuant = 0; //1=median, 2=neuquant\r
+ private int threads = Runtime.getRuntime().availableProcessors();\r
+ private int jpgQuality = 98;\r
+ private int mp3avgb = 48, mp3minb = 8, mp3maxb = 64;\r
+ private int aacQuality = SoundConverter.AAC_Q_HIGH;\r
+ private int vorbisQuality = SoundConverter.VORBIS_Q_MED;\r
+ private int musicVolume = 100;\r
+ private int sfxVolume = 100;\r
+ \r
+ protected boolean convertVoice = true; \r
+ protected Dim sourceImageSize = new Dim(800, 600);\r
+ protected Dim targetImageSize = new Dim(256, 192);\r
+ protected FileExts fileExts = new FileExts();\r
+ \r
+ public AbstractResourceConverter() {\r
+ }\r
+ \r
+ //Functions\r
+ protected void parseCommandLine(String[] args, int off) throws IOException {\r
+ try {\r
+ for (int n = off; n < args.length; n++) {\r
+ if (args[n].startsWith("-mp3")) {\r
+ mp3avgb = Integer.parseInt(args[++n]);\r
+ mp3minb = Integer.parseInt(args[++n]);\r
+ mp3maxb = Integer.parseInt(args[++n]);\r
+ } else if (args[n].startsWith("-aac")) {\r
+ aacQuality = Integer.parseInt(args[++n]);\r
+ } else if (args[n].startsWith("-vorbis")) {\r
+ vorbisQuality = Integer.parseInt(args[++n]);\r
+ } else if (args[n].startsWith("-jpg")) {\r
+ jpgQuality = Integer.parseInt(args[++n]);\r
+ } else if (args[n].startsWith("-pngquant")) {\r
+ pngQuant = args[++n].equalsIgnoreCase("median") ? 1 : 2;\r
+ } else if (args[n].startsWith("-novoice")) {\r
+ convertVoice = false;\r
+ } else if (args[n].startsWith("-sfxvol")) {\r
+ sfxVolume = Integer.parseInt(args[++n]);\r
+ } else if (args[n].startsWith("-musicvol")) {\r
+ musicVolume = Integer.parseInt(args[++n]);\r
+ } else if (args[n].startsWith("-sourceSize")) {\r
+ sourceImageSize = new Dim(Integer.parseInt(args[++n]), Integer.parseInt(args[++n]));\r
+ } else if (args[n].startsWith("-targetSize")) {\r
+ targetImageSize = new Dim(Integer.parseInt(args[++n]), Integer.parseInt(args[++n]));\r
+ } else if (args[n].startsWith("-android")) {\r
+ fileExts = FileExts.ANDROID;\r
+ dither = false;\r
+ } else if (args[n].startsWith("-threads")) {\r
+ threads = Integer.parseInt(args[++n]);\r
+ }\r
+ }\r
+ } catch (RuntimeException re) {\r
+ throw new IOException(re);\r
+ } \r
+ }\r
+ \r
+ protected static void printUsage(Class<?> clazz) {\r
+ System.err.printf("Usage: java %s <template-folder> <game-folder> <dst-folder> <flags>\nflags:"\r
+ + "\n\t-musicvol <volume%%>"\r
+ + "\n\t-sfxvol <volume%%>"\r
+ + "\n\t-sourceSize <width> <height>"\r
+ + "\n\t-targetSize <width> <height>"\r
+ + "\n\t-novoice"\r
+ + "\n\t-android"\r
+ + "\n\t-threads <num>"\r
+ \r
+ + "\n\t-pngquant <median|neuquant>"\r
+ + "\n\t-jpg <quality (0-100)>"\r
+ + "\n\t-mp3 <avgbitrate> <minbitrate> <maxbitrate>"\r
+ + "\n\t-aac <quality (default=70)>"\r
+ + "\n\t-vorbis <quality (default=2)>"\r
+ + "\n", clazz.getName()); \r
+ \r
+ }\r
+ \r
+ protected void initOutputFolder(File dstF) throws IOException {\r
+ FileUtil.deleteFolder(new File(dstF, "foreground"));\r
+ FileUtil.deleteFolder(new File(dstF, "background"));\r
+ FileUtil.deleteFolder(new File(dstF, "sound"));\r
+ FileUtil.deleteFolder(new File(dstF, "script"));\r
+ dstF.mkdirs();\r
+ \r
+ FileUtil.write(new File(dstF, "img.ini"),\r
+ String.format("width=%d\nheight=%d\n", targetImageSize.w, targetImageSize.h));\r
+\r
+ fileExts.write(new File(dstF, "exts.ini")); \r
+ }\r
+ \r
+ protected BatchProcess createBatch() {\r
+ BatchProcess bp = new BatchProcess();\r
+ bp.setTaskSize(100);\r
+ bp.setThreads(threads);\r
+ bp.setThreadPriority(Thread.MIN_PRIORITY);\r
+ bp.addProgressListener(new ProgressListener() {\r
+ public void onFinished(String message) {\r
+ }\r
+ public void onProgress(int value, int max, String message) {\r
+ Log.v(String.format("Processing (%d/%d) %s", value, max, message));\r
+ }\r
+ });\r
+ \r
+ return bp;\r
+ }\r
+ \r
+ private ImageConverter createImageConverter() {\r
+ ImageConverter ic = new ImageConverter();\r
+ ic.setMaxThreads(threads);\r
+ ic.setQuality(jpgQuality);\r
+ ic.setSourceScreenSize(sourceImageSize.w, sourceImageSize.h);\r
+ ic.setTargetScreenSize(targetImageSize.w, targetImageSize.h);\r
+ return ic;\r
+ }\r
+ protected ImageConverter createBackgroundConverter() {\r
+ ImageConverter ic = createImageConverter();\r
+ if (dither) {\r
+ ic.setDitheringType(ImageConverter.DitheringType.FLOYD_STEINBERG);\r
+ } else {\r
+ ic.setDitheringType(DitheringType.NONE); \r
+ }\r
+ ic.setScalingType(ScalingType.BACKGROUND);\r
+ ic.setMode(ImageConverter.ConvertType.TYPE_JPG);\r
+ return ic;\r
+ }\r
+ protected ImageConverter createForegroundConverter() {\r
+ ImageConverter ic = createImageConverter();\r
+ ic.setDitheringType(DitheringType.NONE);\r
+ ic.setScalingType(ScalingType.NONE); //ic.setScalingType(ScalingType.SPRITE);\r
+ ic.setMode(ConvertType.TYPE_PNG);\r
+ if (pngQuant == 1) {\r
+ ic.setMode(ConvertType.TYPE_PNG_256_MEDIAN);\r
+ } else if (pngQuant == 2) {\r
+ ic.setMode(ConvertType.TYPE_PNG_256_NEUQUANT);\r
+ }\r
+ return ic;\r
+ }\r
+ \r
+ private SoundConverter createSoundEncoder() {\r
+ SoundConverter sc = new SoundConverter();\r
+ sc.setMaxThreads(threads);\r
+ sc.setAacQuality(aacQuality);\r
+ sc.setMp3Quality(mp3minb, mp3maxb, mp3avgb);\r
+ sc.setVorbisQuality(vorbisQuality);\r
+ return sc;\r
+ }\r
+ protected SoundConverter createMusicEncoder() {\r
+ SoundConverter sc = createSoundEncoder();\r
+ sc.setVolume(musicVolume);\r
+ sc.setMode(SoundConverter.ConvertType.fromExt(fileExts.music));\r
+ return sc;\r
+ }\r
+ protected SoundConverter createSFXEncoder() {\r
+ SoundConverter sc = createSoundEncoder();\r
+ sc.setVolume(sfxVolume);\r
+ sc.setMode(SoundConverter.ConvertType.fromExt(fileExts.sound));\r
+ return sc;\r
+ }\r
+ protected SoundConverter createVoiceEncoder() {\r
+ SoundConverter sc = createSoundEncoder();\r
+ sc.setVolume(sfxVolume);\r
+ sc.setMode(SoundConverter.ConvertType.fromExt(fileExts.voice));\r
+ return sc;\r
+ }\r
+ \r
+ protected void copyTemplate(File templateF, File dstF) throws IOException {\r
+ FileUtil.copyFolderContents(templateF, dstF, false, null);\r
+ \r
+ Map<String, File> scripts = new HashMap<String, File>();\r
+ FileUtil.collectFiles(scripts, new File(dstF, "script"), false);\r
+ for (File f : scripts.values()) {\r
+ fixScriptFileExts(f);\r
+ }\r
+ scaleSpecialImages(dstF);\r
+ }\r
+ \r
+ protected void fixScriptFileExts(File file) throws IOException {\r
+ String text = FileUtil.read(file);\r
+ text = text.replaceAll("music ([^\\.\\s]+)\\.\\S+", "music $1." + fileExts.music);\r
+ text = text.replaceAll("sound ([^\\.\\s]+)\\.\\S+", "sound $1." + fileExts.sound);\r
+ FileUtil.write(file, text);\r
+ }\r
+ \r
+ protected void scaleSpecialImages(File root) {\r
+ ImageConverter ic = createForegroundConverter();\r
+ \r
+ Map<String, File> files = new HashMap<String, File>();\r
+ FileUtil.collectFiles(files, new File(root, "background/special"), false, false, false);\r
+ ic.setScalingType(ScalingType.STRETCH);\r
+ for (File file : files.values()) {\r
+ ic.setMode(file.getName().endsWith(".jpg") ? ConvertType.TYPE_JPG : ConvertType.TYPE_PNG);\r
+ ic.convertFile(file);\r
+ }\r
+ files.clear();\r
+ \r
+ FileUtil.collectFiles(files, new File(root, "foreground/special"), false, false, false);\r
+ ic.setScalingType(ScalingType.SPRITE);\r
+ for (File file : files.values()) {\r
+ ic.setMode(file.getName().endsWith(".jpg") ? ConvertType.TYPE_JPG : ConvertType.TYPE_PNG);\r
+ ic.convertFile(file);\r
+ }\r
+ files.clear();\r
+ } \r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import java.io.File;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.concurrent.Callable;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.Executors;\r
+import java.util.concurrent.ThreadFactory;\r
+import java.util.concurrent.TimeUnit;\r
+\r
+public class BatchProcess {\r
+\r
+ private int threads;\r
+ private int threadPriority;\r
+ private int taskSize;\r
+ private List<ProgressListener> listeners;\r
+ \r
+ private ExecutorService executor;\r
+ private volatile int progress;\r
+ private int maxProgress;\r
+ \r
+ public BatchProcess() {\r
+ this(1, 8);\r
+ }\r
+ public BatchProcess(int threads, int tasksize) {\r
+ this.threads = threads;\r
+ this.threadPriority = Thread.NORM_PRIORITY;\r
+ this.taskSize = tasksize;\r
+ this.listeners = new ArrayList<ProgressListener>(2);\r
+ }\r
+ \r
+ //Functions\r
+ public void addProgressListener(ProgressListener pl) {\r
+ listeners.add(pl);\r
+ }\r
+ public void removeProgressListener(ProgressListener pl) {\r
+ listeners.remove(pl);\r
+ }\r
+ \r
+ public void run(Map<String, File> fileMap, FileOp op) throws InterruptedException {\r
+ start(fileMap, op);\r
+ stop();\r
+ }\r
+\r
+ public void start(Map<String, File> fileMap, FileOp op) {\r
+ try {\r
+ stop();\r
+ } catch (InterruptedException e) {\r
+ \r
+ }\r
+ \r
+ executor = Executors.newFixedThreadPool(threads, new ThreadFactory() {\r
+ public Thread newThread(Runnable r) {\r
+ Thread t = new Thread(r);\r
+ t.setPriority(threadPriority);\r
+ return t;\r
+ }\r
+ });\r
+ \r
+ progress = 0;\r
+ maxProgress = fileMap.size();\r
+ \r
+ int stepSize = taskSize;\r
+ if (stepSize * threads > fileMap.size()) {\r
+ stepSize = Math.max(1, fileMap.size() / threads); \r
+ }\r
+\r
+ ProgressListener ls[] = listeners.toArray(new ProgressListener[listeners.size()]);\r
+ \r
+ Iterator<Entry<String, File>> i = fileMap.entrySet().iterator();\r
+ while (i.hasNext()) { \r
+ final String relpaths[] = new String[stepSize];\r
+ final File files[] = new File[stepSize];\r
+ \r
+ int t = 0;\r
+ while (t < stepSize && i.hasNext()) {\r
+ Entry<String, File> entry = i.next();\r
+ relpaths[t] = entry.getKey();\r
+ files[t] = entry.getValue();\r
+ t++;\r
+ }\r
+ \r
+ executor.submit(new Task(op, relpaths, files, t, ls));\r
+ }\r
+ }\r
+ \r
+ public void stop() throws InterruptedException {\r
+ if (executor != null) {\r
+ executor.shutdown();\r
+ executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);\r
+ \r
+ ProgressListener ls[] = listeners.toArray(new ProgressListener[listeners.size()]);\r
+ for (ProgressListener pl : ls) {\r
+ pl.onFinished("Finished");\r
+ }\r
+ \r
+ executor = null;\r
+ }\r
+ }\r
+ \r
+ //Getters\r
+ public int getThreads() { return threads; }\r
+ public int getThreadPriority() { return threadPriority; }\r
+ public int getTaskSize() { return taskSize; }\r
+ \r
+ //Setters\r
+ public void setThreads(int t) {\r
+ if (t <= 0) {\r
+ t = Runtime.getRuntime().availableProcessors();\r
+ }\r
+ threads = t;\r
+ }\r
+ public void setThreadPriority(int p) {\r
+ threadPriority = p;\r
+ }\r
+ public void setTaskSize(int ts) {\r
+ taskSize = ts;\r
+ }\r
+\r
+ //Inner Classes\r
+ private class Task implements Callable<Integer> {\r
+\r
+ private final FileOp fop;\r
+ private final String relpaths[];\r
+ private final File files[];\r
+ private final int len;\r
+ private final ProgressListener listeners[];\r
+ \r
+ public Task(FileOp op, String rps[], File fs[], int t, ProgressListener pls[]) {\r
+ fop = op;\r
+ relpaths = rps;\r
+ files = fs;\r
+ len = t;\r
+ listeners = pls;\r
+ }\r
+\r
+ @Override\r
+ public Integer call() throws Exception {\r
+ int t = 0;\r
+ try {\r
+ while (t < len) {\r
+ try {\r
+ fop.execute(relpaths[t], files[t]);\r
+ } catch (Throwable e) {\r
+ Log.w("Uncaught exception processing: " + relpaths[t], e);\r
+ //throw e;\r
+ }\r
+ t++;\r
+ }\r
+ } finally { \r
+ synchronized (BatchProcess.this) {\r
+ progress += len;\r
+ if (listeners.length > 0 && len > 0) {\r
+ for (ProgressListener pl : listeners) {\r
+ pl.onProgress(progress, maxProgress, "");\r
+ }\r
+ }\r
+ }\r
+ }\r
+ return t;\r
+ }\r
+ \r
+ }\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+\r
+import nl.weeaboo.settings.INIFile;\r
+\r
+public final class FileExts {\r
+ \r
+ private static String DEFAULT_MUSIC = "mp3";\r
+ private static String DEFAULT_SOUND = "aac";\r
+ private static String DEFAULT_VOICE = "aac";\r
+ \r
+ public static final FileExts ANDROID = new FileExts("ogg", "ogg", "ogg");\r
+ \r
+ public final String music;\r
+ public final String sound;\r
+ public final String voice;\r
+\r
+ public FileExts() {\r
+ this(DEFAULT_MUSIC, DEFAULT_SOUND, DEFAULT_VOICE);\r
+ }\r
+ public FileExts(String m, String s, String v) {\r
+ this.music = m;\r
+ this.sound = s;\r
+ this.voice = v;\r
+ }\r
+ \r
+ public static FileExts fromFile(File file) throws IOException {\r
+ INIFile iniFile = new INIFile();\r
+ iniFile.read(file);\r
+ return new FileExts(\r
+ iniFile.getString("music", DEFAULT_MUSIC),\r
+ iniFile.getString("sound", DEFAULT_SOUND),\r
+ iniFile.getString("voice", DEFAULT_VOICE)\r
+ );\r
+ }\r
+ \r
+ public void write(File file) throws IOException {\r
+ INIFile iniFile = new INIFile();\r
+ iniFile.put("music", music);\r
+ iniFile.put("sound", sound);\r
+ iniFile.put("voice", voice);\r
+ iniFile.write(file);\r
+ }\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import static nl.weeaboo.string.StringUtil2.*;\r
+import java.io.BufferedOutputStream;\r
+import java.io.BufferedReader;\r
+import java.io.FileInputStream;\r
+import java.io.FileNotFoundException;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStreamReader;\r
+import java.io.OutputStream;\r
+import java.util.Hashtable;\r
+import java.util.Iterator;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.Random;\r
+\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileUtil;\r
+\r
+public class FileMapper implements Iterable<Entry<String, String>> {\r
+\r
+ private Map<String, String> hash2file;\r
+ private Map<String, String> file2hash;\r
+ \r
+ public FileMapper() {\r
+ hash2file = new Hashtable<String, String>();\r
+ file2hash = new Hashtable<String, String>();\r
+ }\r
+ \r
+ //Functions\r
+ public String add(String filename) {\r
+ return add(filename, "");\r
+ }\r
+ public String add(String filename, String hashPrefix) {\r
+ return add(filename, hashPrefix, StringUtil.getExtension(filename));\r
+ }\r
+ public String add(String key, String hashPrefix, String fext) {\r
+ key = key.toLowerCase();\r
+ String hash = getHashed(key);\r
+ if (hash != null) return hash;\r
+ \r
+ Random random = new Random(key.hashCode());\r
+ \r
+ String ext = "." + fext;\r
+ String shortname = getShortname(key);\r
+ if (shortname != null) {\r
+ hash = (hashPrefix + shortname + ext).toLowerCase();\r
+ }\r
+ \r
+ while (hash == null || hash2file.containsKey(hash)) {\r
+ hash = (hashPrefix + getRandomString(5, random) + ext).toLowerCase();\r
+ };\r
+ \r
+ hash2file.put(hash, key);\r
+ file2hash.put(key, hash);\r
+ return hash;\r
+ }\r
+ public String getHashed(String original) {\r
+ return file2hash.get(original.toLowerCase());\r
+ }\r
+ public String getOriginal(String hashed) {\r
+ return hash2file.get(hashed.toLowerCase());\r
+ }\r
+ \r
+ private String getShortname(String filename) {\r
+ filename = StringUtil.stripExtension(filename.substring(filename.lastIndexOf('/')+1));\r
+ filename = filename.replaceAll("[?\\[\\]/\\=+<>:;\",*| ]", "_");\r
+ \r
+ //Remove double file extensions\r
+ int index = filename.indexOf('.');\r
+ if (index > 0) {\r
+ filename = filename.substring(0, index);\r
+ }\r
+ \r
+ //Truncate long filenames\r
+ if (filename.length() > 12) {\r
+ filename = filename.substring(0, 12);\r
+ }\r
+ \r
+ //Return null if the filename is invalid\r
+ if (!FileUtil.isValidFilename(filename)) {\r
+ return null;\r
+ }\r
+ \r
+ //Return null if the filename is non-ascii\r
+ for (int n = 0; n < filename.length(); n++) {\r
+ if (filename.charAt(n) > 127) {\r
+ return null;\r
+ }\r
+ }\r
+\r
+ return filename;\r
+ }\r
+ \r
+ public void load(String file) throws IOException {\r
+ try {\r
+ BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));\r
+ String line;\r
+ while ((line = in.readLine()) != null) {\r
+ String split[] = line.split(" > ");\r
+ hash2file.put(split[0], split[1]);\r
+ file2hash.put(split[1], split[0]);\r
+ }\r
+ in.close();\r
+ } catch (FileNotFoundException fnfe) { \r
+ }\r
+ }\r
+ public void save(String file) throws IOException {\r
+ OutputStream fout = new BufferedOutputStream(new FileOutputStream(file));\r
+ for (Entry<String, String> entry : hash2file.entrySet()) {\r
+ fout.write(entry.getKey().getBytes("UTF-8"));\r
+ fout.write(" > ".getBytes("UTF-8"));\r
+ fout.write(entry.getValue().getBytes("UTF-8"));\r
+ fout.write('\n');\r
+ }\r
+ fout.close();\r
+ }\r
+ \r
+ public void put(String hash, String original) {\r
+ hash2file.put(hash, original);\r
+ file2hash.put(original, hash);\r
+ }\r
+ \r
+ /** Returns the entries in <hash, file> form */\r
+ public Iterator<Entry<String, String>> iterator() {\r
+ return hash2file.entrySet().iterator();\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+\r
+public interface FileOp {\r
+\r
+ public void execute(String relpath, File file) throws IOException;\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.security.MessageDigest;\r
+import java.security.NoSuchAlgorithmException;\r
+\r
+public class HashUtil {\r
+ \r
+ public static String generateHash(String string) throws NoSuchAlgorithmException, IOException {\r
+ return hashToString(generateHash(string.getBytes("UTF-8")));\r
+ }\r
+ public static byte[] generateHash(File file) throws IOException, NoSuchAlgorithmException {\r
+ InputStream in = new BufferedInputStream(new FileInputStream(file));\r
+ byte hash[] = null;\r
+ \r
+ try {\r
+ hash = generateHash(in);\r
+ } finally {\r
+ in.close();\r
+ }\r
+ \r
+ return hash;\r
+ }\r
+ public static byte[] generateHash(InputStream in) throws IOException, NoSuchAlgorithmException {\r
+ MessageDigest md = MessageDigest.getInstance("MD5");\r
+ \r
+ byte buffer[] = new byte[16 * 1024];\r
+ while (true) {\r
+ int read = in.read(buffer, 0, buffer.length);\r
+ if (read == -1) {\r
+ break;\r
+ }\r
+ md.update(buffer, 0, read);\r
+ }\r
+ \r
+ return md.digest();\r
+ }\r
+ \r
+ public static byte[] generateHash(byte data[]) throws NoSuchAlgorithmException {\r
+ return generateHash(data, 0, data.length);\r
+ }\r
+ public static byte[] generateHash(byte data[], int offset, int length) throws NoSuchAlgorithmException {\r
+ MessageDigest md = MessageDigest.getInstance("MD5");\r
+ md.update(data, offset, length);\r
+ return md.digest();\r
+ }\r
+ \r
+ public static String hashToString(byte hash[]) {\r
+ StringBuilder sb = new StringBuilder();\r
+ for (byte b : hash) {\r
+ int nibble1 = (b & 0xF0) >> 4;\r
+ int nibble2 = b & 0x0F;\r
+ sb.append(Integer.toHexString(nibble1));\r
+ sb.append(Integer.toHexString(nibble2));\r
+ }\r
+ return sb.toString();\r
+ }\r
+ \r
+}
\ No newline at end of file
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import java.io.PrintWriter;\r
+import java.io.StringWriter;\r
+import java.util.logging.ConsoleHandler;\r
+import java.util.logging.Formatter;\r
+import java.util.logging.Handler;\r
+import java.util.logging.Level;\r
+import java.util.logging.LogRecord;\r
+import java.util.logging.Logger;\r
+\r
+public final class Log {\r
+\r
+ private static final Logger INSTANCE;\r
+ private static Handler handler;\r
+ \r
+ static {\r
+ INSTANCE = Logger.getLogger("nl.weeaboo.vnds");\r
+ INSTANCE.setUseParentHandlers(false);\r
+ INSTANCE.setLevel(Level.CONFIG);\r
+\r
+ initDefaultHandler();\r
+ }\r
+ \r
+ public static Logger getLogger() {\r
+ return INSTANCE;\r
+ }\r
+ \r
+ public static void initDefaultHandler() {\r
+ if (handler != null) {\r
+ INSTANCE.removeHandler(handler);\r
+ }\r
+ \r
+ handler = new ConsoleHandler();\r
+ handler.setLevel(Level.ALL);\r
+ handler.setFormatter(new Formatter() {\r
+ @Override\r
+ public String format(LogRecord record) {\r
+ StringWriter sw = new StringWriter();\r
+ PrintWriter pw = new PrintWriter(sw);\r
+ \r
+ String msg = record.getMessage();\r
+ if (msg != null) {\r
+ pw.println(msg);\r
+ }\r
+ \r
+ Throwable t = record.getThrown();\r
+ if (t != null) {\r
+ t.printStackTrace(pw);\r
+ }\r
+ return sw.toString();\r
+ }\r
+ });\r
+ INSTANCE.addHandler(handler); \r
+ }\r
+ \r
+ public static void v(String message) {\r
+ v(message, null);\r
+ }\r
+ public static void v(String message, Throwable t) {\r
+ INSTANCE.logp(Level.CONFIG, null, null, message, t);\r
+ }\r
+\r
+ public static void fnf(String message) {\r
+ fnf(message, null);\r
+ }\r
+ public static void fnf(String message, Throwable t) {\r
+ INSTANCE.logp(Level.INFO, null, null, "FileNotFound: " + message, t);\r
+ }\r
+ \r
+ public static void w(String message) {\r
+ w(message, null);\r
+ }\r
+ public static void w(String message, Throwable t) {\r
+ INSTANCE.logp(Level.WARNING, null, null, message, t);\r
+ }\r
+\r
+ public static void e(String message) {\r
+ e(message, null);\r
+ }\r
+ public static void e(String message, Throwable t) {\r
+ INSTANCE.logp(Level.SEVERE, null, null, message, t);\r
+ }\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import java.util.Map;\r
+\r
+public class Patcher {\r
+\r
+ protected static final String RM_TEXT = "#removed by patcher";\r
+ \r
+ //Functions\r
+ public void fillAppendMap(Map<String, String> appendMap) {\r
+ }\r
+ public void patchPre(Map<String, Map<Integer, String>> patch) {\r
+ }\r
+ public void patchPost(Map<String, Map<Integer, String>> patch) {\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+public interface ProgressListener {\r
+\r
+ //Functions\r
+ \r
+ /**\r
+ * @param max Maximum progress, <code>-1</code> if unknown.\r
+ */\r
+ public void onProgress(int value, int max, String message);\r
+ \r
+ public void onFinished(String message);\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+public interface ProgressRunnable {\r
+\r
+ //Functions\r
+ public void run(ProgressListener pl);\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.PrintWriter;\r
+import java.util.HashSet;\r
+import java.util.Set;\r
+\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.vnds.Log;\r
+\r
+public class ResourcesUsed {\r
+\r
+ private static final String FN_FOREGROUND = "resused-foreground.txt";\r
+ private static final String FN_BACKGROUND = "resused-background.txt";\r
+ private static final String FN_SOUND = "resused-sound.txt";\r
+ private static final String FN_MUSIC = "resused-music.txt";\r
+ \r
+ private Set<String> foreground;\r
+ private Set<String> background;\r
+ private Set<String> sound;\r
+ private Set<String> music;\r
+ \r
+ public ResourcesUsed() {\r
+ foreground = new HashSet<String>();\r
+ background = new HashSet<String>();\r
+ sound = new HashSet<String>();\r
+ music = new HashSet<String>();\r
+ }\r
+ \r
+ //Functions\r
+ public void load(File folder, boolean suppressWarnings) {\r
+ load(foreground, new File(folder, FN_FOREGROUND), suppressWarnings);\r
+ load(background, new File(folder, FN_BACKGROUND), suppressWarnings);\r
+ load(sound, new File(folder, FN_SOUND), suppressWarnings);\r
+ load(music, new File(folder, FN_MUSIC), suppressWarnings);\r
+ }\r
+ \r
+ protected static boolean load(Set<String> out, File file, boolean suppressWarnings) {\r
+ try {\r
+ for (String line : FileUtil.read(file).split("(\\\r)?\\\n")) {\r
+ out.add(line.trim());\r
+ }\r
+ return true;\r
+ } catch (IOException ioe) {\r
+ if (!suppressWarnings) {\r
+ Log.w("Exception reading: " + file, ioe);\r
+ }\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ public void save(File folder) {\r
+ folder.mkdirs();\r
+ save(foreground, new File(folder, FN_FOREGROUND));\r
+ save(background, new File(folder, FN_BACKGROUND));\r
+ save(sound, new File(folder, FN_SOUND));\r
+ save(music, new File(folder, FN_MUSIC));\r
+ }\r
+ \r
+ protected static boolean save(Set<String> set, File file) {\r
+ try {\r
+ PrintWriter pout = new PrintWriter(file, "UTF-8");\r
+ try {\r
+ for (String line : set) {\r
+ pout.println(line);\r
+ }\r
+ } finally {\r
+ pout.close(); \r
+ }\r
+ return true;\r
+ } catch (IOException ioe) {\r
+ Log.w("Exception writing: " + file, ioe);\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ //Getters\r
+ public boolean isForegroundUsed(String filename) {\r
+ return foreground.contains(filename);\r
+ }\r
+ public boolean isBackgroundUsed(String filename) {\r
+ return background.contains(filename);\r
+ }\r
+ public boolean isSoundUsed(String filename) {\r
+ return sound.contains(filename);\r
+ }\r
+ public boolean isMusicUsed(String filename) {\r
+ return music.contains(filename);\r
+ }\r
+ \r
+ //Setters\r
+ public void setForegroundUsed(String filename) {\r
+ foreground.add(filename);\r
+ }\r
+ public void setBackgroundUsed(String filename) {\r
+ background.add(filename);\r
+ }\r
+ public void setSoundUsed(String filename) {\r
+ sound.add(filename);\r
+ }\r
+ public void setMusicUsed(String filename) {\r
+ music.add(filename);\r
+ }\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+public enum TargetPlatform {\r
+ NINTENDO_DS("Nintendo DS", 256, 192, false),\r
+ ANDROID_QVGA("Android QVGA", 320, 240, true),\r
+ ANDROID_HVGA("Android HVGA", 480, 320, true),\r
+ ANDROID_WVGA("Android WVGA", 800, 480, true),\r
+ ANDROID_WSVGA("Android WSVGA", 1024, 600, true),\r
+ ANDROID_WXGA("Android WXGA", 1280, 800, true);\r
+ \r
+ private final String label;\r
+ private final int w, h;\r
+ private final boolean android;\r
+ \r
+ private TargetPlatform(String label, int w, int h, boolean android) {\r
+ this.label = label;\r
+ this.w = w;\r
+ this.h = h;\r
+ this.android = android;\r
+ }\r
+ \r
+ public int getWidth() { return w; }\r
+ public int getHeight() { return h; }\r
+ public boolean isAndroid() { return android; }\r
+ \r
+ public String toString() {\r
+ return String.format("%s (%dx%d)", label, w, h);\r
+ }\r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Dimension;\r
+\r
+import javax.swing.JDialog;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JProgressBar;\r
+import javax.swing.SwingUtilities;\r
+import javax.swing.border.EmptyBorder;\r
+\r
+import nl.weeaboo.awt.AwtUtil;\r
+\r
+public class VNDSProgressDialog {\r
+\r
+ private JPanel panel;\r
+ private JLabel messageLabel;\r
+ private JProgressBar progressBar;\r
+ \r
+ public VNDSProgressDialog() { \r
+ messageLabel = new JLabel("Initializing...");\r
+ progressBar = new JProgressBar();\r
+\r
+ panel = new JPanel(new BorderLayout(5, 5));\r
+ panel.setBorder(new EmptyBorder(10, 10, 10, 10));\r
+ panel.add(messageLabel, BorderLayout.CENTER);\r
+ panel.add(progressBar, BorderLayout.SOUTH);\r
+ }\r
+ \r
+ public void showDialog(final ProgressRunnable task, final ProgressListener pl) {\r
+ final JDialog dialog = new JDialog();\r
+ dialog.setResizable(false);\r
+ dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);\r
+ dialog.setAlwaysOnTop(true);\r
+ \r
+ panel.setPreferredSize(new Dimension(400, 60));\r
+ dialog.add(panel); \r
+ dialog.pack();\r
+ dialog.setLocationRelativeTo(null);\r
+ dialog.setVisible(true);\r
+\r
+ final ProgressListener progressListener = new ProgressListener() {\r
+ public void onProgress(final int value, final int max, final String message) {\r
+ SwingUtilities.invokeLater(new Runnable() {\r
+ public void run() {\r
+ progressBar.setMaximum(max);\r
+ progressBar.setValue(value); \r
+ messageLabel.setText(message);\r
+ \r
+ if (pl != null) {\r
+ pl.onProgress(value, max, message);\r
+ }\r
+ }\r
+ });\r
+ }\r
+ public void onFinished(final String message) {\r
+ SwingUtilities.invokeLater(new Runnable() {\r
+ public void run() {\r
+ progressBar.setValue(progressBar.getMaximum());\r
+ \r
+ if (pl != null) {\r
+ pl.onFinished(message);\r
+ }\r
+ }\r
+ });\r
+ }\r
+ };\r
+ \r
+ Thread t = new Thread(new Runnable() {\r
+ public void run() {\r
+ try {\r
+ ((ProgressRunnable)task).run(progressListener);\r
+ } catch (Exception e) {\r
+ AwtUtil.showError(e);\r
+ }\r
+ SwingUtilities.invokeLater(new Runnable() {\r
+ public void run() {\r
+ dialog.dispose();\r
+ }\r
+ });\r
+ }\r
+ });\r
+ t.start();\r
+ }\r
+ \r
+ //Functions\r
+ //Getters\r
+ //Setters\r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import java.awt.Insets;\r
+import java.awt.image.BufferedImage;\r
+import java.io.BufferedInputStream;\r
+import java.io.EOFException;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.nio.channels.FileChannel;\r
+\r
+import javax.imageio.ImageIO;\r
+\r
+import nl.weeaboo.awt.ImageUtil;\r
+import nl.weeaboo.image.ImageInfo;\r
+import nl.weeaboo.image.ImageInfo.Result;\r
+\r
+public final class VNImageUtil {\r
+\r
+ private VNImageUtil() { \r
+ }\r
+ \r
+ public static int checkHugeImage(File file) throws IOException {\r
+ ImageInfo ii = new ImageInfo();\r
+ Result r = null;\r
+ try {\r
+ r = ii.readHeader(file);\r
+ } catch (IOException ioe) {\r
+ //Ignore\r
+ }\r
+ \r
+ if (r == null) {\r
+ return -1;\r
+ }\r
+\r
+ if (r.width > 4096 || r.height > 4096) {\r
+ String msg = String.format("Detected huge image (%d x %d) %s",\r
+ r.width, r.height, file.getAbsolutePath());\r
+ Log.w(msg);\r
+ return 1;\r
+ } \r
+ return 0;\r
+ }\r
+ \r
+ public static BufferedImage readBMP(File file) throws IOException {\r
+ final int headerSize = 0x36;\r
+ if (file.length() <= headerSize) {\r
+ return ImageIO.read(file); \r
+ }\r
+ \r
+ int bpp = 0;\r
+ \r
+ //Convert 32-bit bitmaps to a more sensible format\r
+ FileInputStream fin = new FileInputStream(file);\r
+ FileChannel fc = fin.getChannel();\r
+ try {\r
+ fc.position(0x12);\r
+ byte temp[] = new byte[4];\r
+ fin.read(temp);\r
+ int width = ((temp[3]&0xFF)<<24)|((temp[2]&0xFF)<<16)|((temp[1]&0xFF)<<8)|(temp[0]&0xFF);\r
+ fin.read(temp);\r
+ int height = ((temp[3]&0xFF)<<24)|((temp[2]&0xFF)<<16)|((temp[1]&0xFF)<<8)|(temp[0]&0xFF);\r
+ fc.position(fc.position()+2);\r
+ fin.read(temp);\r
+ bpp = ((temp[3]&0xFF)<<24)|((temp[2]&0xFF)<<16)|((temp[1]&0xFF)<<8)|(temp[0]&0xFF);\r
+ \r
+ if (bpp != 32) {\r
+ fc.position(0);\r
+ return ImageIO.read(new BufferedInputStream(fin));\r
+ }\r
+ \r
+ fc.position(headerSize);\r
+ BufferedInputStream bin = new BufferedInputStream(fin, 4096);\r
+ int argb[] = new int[width*height];\r
+ for (int y = height-1; y >= 0; y--) {\r
+ int t = width * y;\r
+ for (int x = 0; x < width; x++) {\r
+ bin.read(temp);\r
+ argb[t++] = ((temp[3]&0xFF)<<24)|((temp[2]&0xFF)<<16)|((temp[1]&0xFF)<<8)|(temp[0]&0xFF);\r
+ }\r
+ }\r
+ \r
+ return ImageUtil.createBufferedImage(width, height, argb, false);\r
+ } catch (EOFException eof) {\r
+ Log.w("Unreadable bitmap (filename="+file.getName()+", bpp="+bpp+")");\r
+ throw eof;\r
+ } catch (IOException ioe) {\r
+ throw ioe;\r
+ } finally {\r
+ fc.close();\r
+ fin.close();\r
+ }\r
+ }\r
+ \r
+ protected static boolean rowEmpty(int argb[], int iw, int ih, int y) {\r
+ int offset = y * iw;\r
+ for (int x = 0; x < iw; x++) {\r
+ if (((argb[offset]>>24) & 0xFF) >= 16) {\r
+ return false;\r
+ }\r
+ offset++;\r
+ }\r
+ return true;\r
+ }\r
+ \r
+ protected static boolean colEmpty(int argb[], int iw, int ih, int x) {\r
+ int offset = x;\r
+ for (int y = 0; y < ih; y++) {\r
+ if (((argb[offset]>>24) & 0xFF) >= 16) {\r
+ return false;\r
+ }\r
+ offset += iw;\r
+ }\r
+ return true;\r
+ }\r
+ \r
+ public static void calculateImageInsets(int[] argb, int iw, int ih, Insets i) {\r
+ for (int x = 0; x < iw && colEmpty(argb, iw, ih, x); x++) i.left++;\r
+ for (int x = iw-1; x >= 0 && colEmpty(argb, iw, ih, x); x--) i.right++;\r
+ for (int y = 0; y < ih && rowEmpty(argb, iw, ih, y); y++) i.top++;\r
+ for (int y = ih-1; y >= 0 && rowEmpty(argb, iw, ih, y); y--) i.bottom++; \r
+ }\r
+\r
+}\r
--- /dev/null
+package nl.weeaboo.vnds;\r
+\r
+import static nl.weeaboo.common.StringUtil.stripExtension;\r
+\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.nio.ByteBuffer;\r
+import java.nio.ByteOrder;\r
+import java.nio.ShortBuffer;\r
+\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.system.ProcessUtil;\r
+\r
+public final class VNSoundUtil {\r
+\r
+\r
+ public static String ffmpeg = "ffmpeg";\r
+\r
+ private VNSoundUtil() { \r
+ }\r
+ \r
+ public static File padAudioFile(File src, File dst) throws IOException {\r
+ File tempF = new File(dst.getParent(), stripExtension(dst.getName())+".temp");\r
+ tempF.getParentFile().mkdirs();\r
+ \r
+ try {\r
+ //Decode to raw PCM\r
+\r
+ Process p = null;\r
+\r
+ redo_transcode: try {\r
+ p = ProcessUtil.execInDir(String.format(\r
+ "%s -y -i \"%s\" -f s16le -acodec pcm_s16le -ac 2 -ar 44100 \"%s\"",\r
+ ffmpeg, src.getAbsolutePath(), tempF.getAbsolutePath()),\r
+ ".");\r
+ }\r
+ catch (Exception e) {\r
+ ffmpeg = "avconv";\r
+ break redo_transcode;\r
+ }\r
+ ProcessUtil.waitFor(p);\r
+ ProcessUtil.kill(p);\r
+ \r
+ {\r
+ byte[] bytes = FileUtil.readBytes(tempF);\r
+ boolean isShort = bytes.length <= 10 * (44100*2*2);\r
+ float fadeSeconds = (isShort ? 1 : 3);\r
+ float padSeconds = (isShort ? 3 : 0);\r
+ \r
+ ShortBuffer[] samples = deinterleave(bytes, 2);\r
+ fadeInOut(samples, Math.round(fadeSeconds * 44100),\r
+ Math.round(fadeSeconds * 44100));\r
+ bytes = interleave(samples);\r
+ \r
+ //Pad raw PCM with data \r
+ FileOutputStream fout = new FileOutputStream(tempF);\r
+ try {\r
+ fout.write(bytes);\r
+ fout.write(new byte[Math.round(padSeconds * 44100 * 2 * 2)]);\r
+ fout.flush();\r
+ } finally {\r
+ fout.close();\r
+ }\r
+ }\r
+ \r
+ //Encode to target format\r
+ p = ProcessUtil.execInDir(String.format(\r
+ "ffmpeg -y -f s16le -acodec pcm_s16le -ac 2 -ar 44100 -i \"%s\" \"%s\"",\r
+ tempF.getAbsolutePath(), dst.getAbsolutePath()),\r
+ "tools");\r
+ ProcessUtil.waitFor(p);\r
+ ProcessUtil.kill(p);\r
+ } finally {\r
+ tempF.delete();\r
+ }\r
+ \r
+ return dst;\r
+ }\r
+ \r
+ public static void fadeInOut(ShortBuffer[] channels, int inDuration, int outDuration) {\r
+ for (ShortBuffer ch : channels) {\r
+ inDuration = Math.min(ch.limit()/2, inDuration);\r
+ }\r
+\r
+ for (ShortBuffer ch : channels) {\r
+ outDuration = Math.min(ch.limit()-inDuration, outDuration);\r
+ }\r
+\r
+ for (ShortBuffer ch : channels) {\r
+ int a = 0;\r
+ for (int n = 0; n < inDuration; n++) {\r
+ float frac = n / (float)inDuration;\r
+ ch.put(a, (short)Math.round(frac * ch.get(a)));\r
+ a++;\r
+ }\r
+\r
+ int b = ch.limit()-1;\r
+ for (int n = 0; n < outDuration; n++) {\r
+ float frac = n / (float)outDuration;\r
+ ch.put(b, (short)Math.round(frac * ch.get(b)));\r
+ b--;\r
+ }\r
+ }\r
+ }\r
+ \r
+ protected static ShortBuffer[] deinterleave(byte[] srcArray, int channels) {\r
+ ByteBuffer src = ByteBuffer.wrap(srcArray);\r
+ src.order(ByteOrder.LITTLE_ENDIAN);\r
+ ShortBuffer ssrc = src.asShortBuffer();\r
+ \r
+ ShortBuffer[] dst = new ShortBuffer[channels];\r
+ for (int n = 0; n < channels; n++) {\r
+ dst[n] = ShortBuffer.allocate(ssrc.limit() / channels);\r
+ }\r
+ \r
+ while (ssrc.remaining() > 0) {\r
+ for (ShortBuffer buf : dst) {\r
+ buf.put(ssrc.get());\r
+ }\r
+ }\r
+\r
+ for (ShortBuffer buf : dst) {\r
+ buf.rewind();\r
+ }\r
+ \r
+ return dst;\r
+ }\r
+\r
+ protected static byte[] interleave(ShortBuffer[] src) {\r
+ int len = 0;\r
+ int[] oldpos = new int[src.length];\r
+ for (int n = 0; n < oldpos.length; n++) {\r
+ oldpos[n] = src[n].position();\r
+ len += src[n].limit();\r
+ }\r
+ \r
+ ByteBuffer dst = ByteBuffer.allocate(2 * len);\r
+ dst.order(ByteOrder.LITTLE_ENDIAN);\r
+ ShortBuffer sdst = dst.asShortBuffer();\r
+ \r
+ try {\r
+ while (sdst.remaining() > 0) {\r
+ for (ShortBuffer buf : src) {\r
+ sdst.put(buf.get());\r
+ }\r
+ }\r
+ } finally {\r
+ for (int n = 0; n < oldpos.length; n++) {\r
+ src[n].position(oldpos[n]);\r
+ }\r
+ }\r
+ dst.rewind();\r
+ \r
+ return dst.array();\r
+ }\r
+\r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.installer;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+\r
+import nl.weeaboo.vnds.Log;\r
+import nl.weeaboo.xml.XmlElement;\r
+import nl.weeaboo.xml.XmlReader;\r
+\r
+import org.xml.sax.SAXException;\r
+\r
+public class AddCommand extends PackCommand {\r
+\r
+ private String xmlPath;\r
+ \r
+ public AddCommand(String xmlPath, String outputFolder) {\r
+ super(outputFolder);\r
+ \r
+ this.xmlPath = xmlPath;\r
+ }\r
+ \r
+ //Functions\r
+ public void execute() {\r
+ try {\r
+ File file = new File(xmlPath);\r
+ XmlReader xmlReader = new XmlReader();\r
+ XmlElement componentE = xmlReader.read(file).getChild("component");\r
+ \r
+ Component c = Component.fromXml(file.getParentFile().getAbsolutePath(), componentE);\r
+ c.save(new File(getOutputFolder(), file.getName()));\r
+ } catch (SAXException e) {\r
+ Log.e("Exception executing add command: " + xmlPath, e);\r
+ } catch (IOException ioe) {\r
+ Log.e("Exception executing add command: " + xmlPath, ioe);\r
+ }\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.installer;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.HashSet;\r
+import java.util.Set;\r
+\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.vnds.Log;\r
+import nl.weeaboo.xml.XmlElement;\r
+\r
+public class Component {\r
+\r
+ private String name;\r
+ private String desc;\r
+ private Set<FileListEntry> files;\r
+ \r
+ public Component(String name, String desc) {\r
+ this.name = name;\r
+ this.desc = desc;\r
+ \r
+ files = new HashSet<FileListEntry>();\r
+ }\r
+ \r
+ //Functions\r
+ public void addFile(FileListEntry file) {\r
+ files.add(file); \r
+ }\r
+ public void removeFile(FileListEntry file) {\r
+ files.remove(file); \r
+ }\r
+ \r
+ //Getters\r
+ public Set<FileListEntry> getFiles() {\r
+ return files;\r
+ }\r
+ public String getName() {\r
+ return name;\r
+ }\r
+ public String getDesc() {\r
+ return desc;\r
+ }\r
+ \r
+ //Setters\r
+\r
+ //Save Support\r
+ public void save(File file) throws IOException {\r
+ XmlElement componentE = new XmlElement("component");\r
+ componentE.addAttribute("name", name);\r
+ componentE.addAttribute("desc", desc);\r
+ \r
+ for (FileListEntry fle : files) {\r
+ XmlElement fileE = componentE.addChild("file");\r
+ fileE.addAttribute("path", fle.getPath());\r
+ fileE.addAttribute("size", fle.getSize());\r
+ if (fle.getHash() != null) fileE.addAttribute("hash", fle.getHash());\r
+ fileE.addAttribute("url", fle.getURL());\r
+ }\r
+\r
+ FileUtil.write(file, componentE.toXmlString());\r
+ }\r
+ \r
+ public static Component fromXml(String baseFolder, XmlElement componentE) {\r
+ Component c = new Component(componentE.getAttribute("name"), componentE.getAttribute("desc"));\r
+ for (XmlElement fileE : componentE.getChildren("file")) {\r
+ try {\r
+ String fpath = fileE.getAttribute("path");\r
+ long fsize = Long.parseLong(fileE.getAttribute("size"));\r
+ String fhash = fileE.getAttribute("hash");\r
+ if (fhash.equals("")) fhash = null;\r
+ String furl = fileE.getAttribute("url");\r
+\r
+ String filePath = baseFolder+"/"+furl;\r
+ if (filePath.contains(".zip")) {\r
+ filePath = filePath.substring(0, filePath.indexOf(".zip")+4);\r
+ }\r
+ File file = new File(filePath);\r
+ \r
+ c.addFile(new FileListEntry(fpath, fsize, fhash, furl, file));\r
+ } catch (NumberFormatException nfe) {\r
+ Log.w("Exception parsing installer component", nfe);\r
+ }\r
+ }\r
+ return c;\r
+ }\r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.installer;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.vnds.Log;\r
+\r
+public class CreateCommand extends PackCommand {\r
+\r
+ private String baseFolder;\r
+ \r
+ public CreateCommand(String baseFolder, String outputFolder) {\r
+ super(outputFolder);\r
+ \r
+ this.baseFolder = baseFolder;\r
+ }\r
+ \r
+ //Functions\r
+ public void execute() {\r
+ File target = new File(getOutputFolder() + "/base_install");\r
+ if (target.exists()) {\r
+ FileUtil.deleteFolder(target);\r
+ }\r
+ \r
+ target.mkdirs();\r
+ try {\r
+ FileUtil.copyFolderContents(new File(baseFolder), target);\r
+ } catch (IOException e) {\r
+ Log.e("Exception executing create command", e);\r
+ }\r
+\r
+ new File(target.getAbsolutePath()+"/background").mkdirs();\r
+ new File(target.getAbsolutePath()+"/foreground").mkdirs();\r
+ new File(target.getAbsolutePath()+"/script").mkdirs();\r
+ new File(target.getAbsolutePath()+"/save").mkdirs();\r
+ new File(target.getAbsolutePath()+"/sound").mkdirs();\r
+ }\r
+ \r
+ //Getters\r
+ public String getBaseFolder() {\r
+ return baseFolder;\r
+ }\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.installer;\r
+\r
+import java.io.File;\r
+\r
+public class FileListEntry {\r
+\r
+ private final String path;\r
+ private final long size;\r
+ private final String url;\r
+ private String hash;\r
+ \r
+ private File file;\r
+ \r
+ public FileListEntry(String path, long size, String hash, String url) {\r
+ this(path, size, hash, url, null);\r
+ }\r
+ public FileListEntry(String path, long size, String hash, String url, File file) {\r
+ this.path = path;\r
+ this.size = size;\r
+ this.hash = hash;\r
+ this.url = url;\r
+ \r
+ this.file = file;\r
+ }\r
+ \r
+ //Functions\r
+ public String toString() {\r
+ return String.format("%s %d %s %s", path, size, hash, url);\r
+ }\r
+ \r
+ public int hashCode() {\r
+ return path.hashCode();\r
+ }\r
+ \r
+ public boolean equals(Object o) {\r
+ if (o instanceof FileListEntry) {\r
+ return equals((FileListEntry)o);\r
+ }\r
+ return false;\r
+ }\r
+ public boolean equals(FileListEntry entry) {\r
+ return path.equals(entry.getPath()) \r
+ && size == entry.getSize()\r
+ && ((getHash() == null && entry.getHash() == null) || getHash().equals(entry.getHash()));\r
+ //Don't compare URL's\r
+ }\r
+ \r
+ //Getters\r
+ public File getFile() {\r
+ return file;\r
+ }\r
+ \r
+ public String getPath() {\r
+ return path;\r
+ } \r
+ public long getSize() {\r
+ return size;\r
+ }\r
+ public String getHash() {\r
+ return hash;\r
+ }\r
+ public String getURL() {\r
+ return url;\r
+ }\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.installer;\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.BufferedOutputStream;\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Hashtable;\r
+import java.util.Iterator;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.zip.CRC32;\r
+import java.util.zip.ZipEntry;\r
+import java.util.zip.ZipFile;\r
+import java.util.zip.ZipOutputStream;\r
+\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.vnds.Log;\r
+import nl.weeaboo.vnds.ProgressListener;\r
+import nl.weeaboo.xml.XmlElement;\r
+import nl.weeaboo.xml.XmlReader;\r
+\r
+public class Installer {\r
+\r
+ //Functions\r
+ public static void main(String args[]) {\r
+ if (args.length < 2) {\r
+ System.err.println("Usage: java -jar Installer.jar <output-path> <component-xml-1> <component-xml-2>");\r
+ System.exit(1);\r
+ }\r
+ \r
+ String paths[] = Arrays.copyOfRange(args, 1, args.length);\r
+ install(args[0], null, paths);\r
+ }\r
+ \r
+ public static String install(String outputFolder, ProgressListener pl, String... componentPaths) {\r
+ StringBuilder sb = new StringBuilder();\r
+ List<FileListEntry> files = new LinkedList<FileListEntry>();\r
+ \r
+ int t = 0;\r
+ for (String path : componentPaths) {\r
+ if (pl != null) {\r
+ t++;\r
+ pl.onProgress(t, componentPaths.length, String.format("Reading %s...", path));\r
+ } \r
+\r
+ try {\r
+ File file = new File(path);\r
+ //String parentPath = file.getAbsoluteFile().getParent();\r
+ \r
+ XmlReader xmlReader = new XmlReader();\r
+ XmlElement componentE = xmlReader.read(file).getChild("component");\r
+ Component component = Component.fromXml(new File(".").getAbsolutePath(), componentE);\r
+ \r
+ for (Iterator<FileListEntry> i = files.iterator(); i.hasNext(); ) {\r
+ FileListEntry f = i.next();\r
+ for (FileListEntry nf : component.getFiles()) {\r
+ if (f.getPath().equals(nf.getPath())) {\r
+ i.remove(); //Overwrite old file\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ \r
+ files.addAll(component.getFiles());\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ sb.append(e);\r
+ sb.append('\n');\r
+ }\r
+ }\r
+ \r
+ installFileList(outputFolder, pl, files);\r
+ \r
+ if (pl != null) pl.onFinished("");\r
+ return sb.toString();\r
+ }\r
+ \r
+ public static void installFileList(String outputFolder, ProgressListener pl,\r
+ Collection<FileListEntry> files)\r
+ {\r
+ FileUtil.deleteFolder(new File(outputFolder));\r
+ \r
+ new File(outputFolder+"/foreground").mkdirs();\r
+ new File(outputFolder+"/background").mkdirs();\r
+ new File(outputFolder+"/script").mkdirs();\r
+ new File(outputFolder+"/save").mkdirs();\r
+ new File(outputFolder+"/sound").mkdirs();\r
+ \r
+ byte readBuffer[] = new byte[10 * 1024 * 1024]; \r
+ Map<File, ZipFile> zipFiles = new Hashtable<File, ZipFile>();\r
+ Map<File, ZipOutputStream> zipOutStreams = new Hashtable<File, ZipOutputStream>();\r
+ \r
+ int t = 0;\r
+ for (FileListEntry entry : files) {\r
+ t++;\r
+ \r
+ try {\r
+ File file = entry.getFile();\r
+ String path = entry.getPath();\r
+ if (pl != null) {\r
+ pl.onProgress(t, files.size(), String.format("Installing %s...", path));\r
+ }\r
+\r
+ if (StringUtil.getExtension(file.getName()).equals("zip")) {\r
+ try {\r
+ ZipFile inZip = zipFiles.get(file);\r
+ if (inZip == null) {\r
+ zipFiles.put(file, inZip = new ZipFile(file));\r
+ }\r
+ ZipOutputStream zout = zipOutStreams.get(file);\r
+ if (zout == null) {\r
+ String zipPath = outputFolder+"/"+path.substring(0, path.lastIndexOf(".zip")+4);\r
+ zipOutStreams.put(file, zout = new ZipOutputStream(new BufferedOutputStream(\r
+ new FileOutputStream(zipPath))));\r
+ zout.setMethod(ZipOutputStream.STORED);\r
+ } \r
+\r
+ String entryName = path.substring(path.indexOf(file.getName())+file.getName().length()+1);\r
+ \r
+ int fsize = 0;\r
+ {\r
+ //Read file data \r
+ ZipEntry zipEntry = inZip.getEntry(entryName);\r
+ if (zipEntry == null) continue; \r
+ InputStream in = new BufferedInputStream(inZip.getInputStream(zipEntry));\r
+\r
+ int r;\r
+ while ((r = in.read()) >= 0) {\r
+ readBuffer[fsize++] = (byte)r;\r
+ }\r
+ \r
+ in.close(); \r
+ }\r
+ {\r
+ //Write file data\r
+ \r
+ //Create ZIP Entry\r
+ ZipEntry zipEntry = new ZipEntry(entryName); \r
+ zipEntry.setSize(fsize);\r
+ zipEntry.setCompressedSize(fsize);\r
+ CRC32 crc = new CRC32();\r
+ crc.update(readBuffer, 0, fsize);\r
+ zipEntry.setCrc(crc.getValue());\r
+ zout.putNextEntry(zipEntry);\r
+ \r
+ //Write File contents to ZIP\r
+ zout.write(readBuffer, 0, fsize);\r
+ \r
+ zout.flush();\r
+ zout.closeEntry();\r
+ }\r
+ } catch (IOException ioe) {\r
+ Log.e("Exception during installation", ioe);\r
+ }\r
+ } else {\r
+ FileUtil.copyFile(file, new File(outputFolder+"/"+path));\r
+ }\r
+ } catch (Exception e) {\r
+ Log.e("Exception during installation", e);\r
+ }\r
+ }\r
+ \r
+ //Close open files\r
+ for (ZipFile file : zipFiles.values()) {\r
+ try { file.close(); } catch (IOException e) {}\r
+ }\r
+ for (ZipOutputStream zout : zipOutStreams.values()) {\r
+ try { zout.close(); } catch (IOException e) {}\r
+ }\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.installer;\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.util.ArrayList;\r
+import java.util.Enumeration;\r
+import java.util.HashSet;\r
+import java.util.Hashtable;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.Set;\r
+import java.util.zip.ZipEntry;\r
+import java.util.zip.ZipException;\r
+import java.util.zip.ZipFile;\r
+\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.vnds.HashUtil;\r
+import nl.weeaboo.vnds.Log;\r
+\r
+public class InstallerPacker {\r
+\r
+ //Functions\r
+ private static void printUsage() {\r
+ System.err.println("Usage: java -jar InstallPacker.jar <command> <output path>\n"\r
+ + "\t\tcommands:\n"\r
+ + "\tcreate <path>\n"\r
+ + "\tpatch <patch-name> <path>\n"\r
+ + "\tadd <component-xml-path>\n" \r
+ + "\n");\r
+ }\r
+ \r
+ public static PackCommand parseCommand(String args[]) {\r
+ if (args[0].equals("create")) {\r
+ return new CreateCommand(args[1], args[2]);\r
+ } else if (args[0].equals("patch")) {\r
+ return new PatchCommand(args[1], args[2], args[3]);\r
+ } else if (args[0].equals("add")) {\r
+ return new AddCommand(args[1], args[2]);\r
+ }\r
+ Log.w("Unknown Command: " + args[0]);\r
+ return null;\r
+ }\r
+ \r
+ public static void main(String args[]) {\r
+ if (args.length < 1) {\r
+ printUsage();\r
+ return;\r
+ }\r
+ \r
+ PackCommand c = parseCommand(args);\r
+ if (c == null) {\r
+ printUsage();\r
+ return;\r
+ }\r
+ \r
+ c.execute();\r
+ }\r
+ \r
+ public static void execute(String command) {\r
+ List<String> args = new ArrayList<String>();\r
+ \r
+ String parts[] = command.split(" ");\r
+ \r
+ boolean inString = false;\r
+ StringBuilder buffer = new StringBuilder();\r
+ for (int n = 0; n < parts.length; n++) {\r
+ parts[n] = parts[n].trim();\r
+ \r
+ if (!inString && parts[n].startsWith("\"")) {\r
+ inString = true;\r
+ parts[n] = parts[n].substring(1);\r
+ }\r
+ \r
+ if (inString) {\r
+ if (buffer.length() > 0) buffer.append(' ');\r
+ buffer.append(parts[n]);\r
+ } else {\r
+ args.add(parts[n]);\r
+ }\r
+ \r
+ if (inString && parts[n].endsWith("\"")) {\r
+ args.add(buffer.substring(0, buffer.length()-1)); //Don't include trailing (")\r
+ buffer.delete(0, buffer.length());\r
+ inString = false;\r
+ }\r
+ }\r
+ \r
+ parseCommand(args.toArray(new String[args.size()])).execute();\r
+ }\r
+\r
+ public static void collectFiles(Map<String, File> map, File file, boolean includeRootFolder) {\r
+ if (!file.isDirectory() && !StringUtil.getExtension(file.getName()).equals("zip")) {\r
+ map.put(file.getName(), file);\r
+ } else {\r
+ if (includeRootFolder) {\r
+ collectFiles(map, file, "");\r
+ } else {\r
+ if (file.isDirectory()) {\r
+ for (File f : file.listFiles()) {\r
+ collectFiles(map, f, "");\r
+ }\r
+ } else if (StringUtil.getExtension(file.getName()).equals("zip")) {\r
+ collectZippedFiles(map, file, "");\r
+ }\r
+ }\r
+ }\r
+ }\r
+ public static void collectZippedFiles(Map<String, File> map, File file, String relPath) {\r
+ try {\r
+ ZipFile zipFile = new ZipFile(file);\r
+ \r
+ Enumeration<? extends ZipEntry> enumeration = zipFile.entries();\r
+ while (enumeration.hasMoreElements()) {\r
+ ZipEntry entry = enumeration.nextElement();\r
+ map.put(relPath + '/' + entry.getName(), file);\r
+ }\r
+ \r
+ zipFile.close();\r
+ } catch (ZipException e) {\r
+ e.printStackTrace();\r
+ } catch (IOException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ private static void collectFiles(Map<String, File> map, File file, String relPath) {\r
+ String path;\r
+ if (relPath.length() > 0) {\r
+ path = relPath + '/' + file.getName();\r
+ } else {\r
+ path = file.getName();\r
+ } \r
+\r
+ if (file.isDirectory()) {\r
+ for (File f : file.listFiles()) {\r
+ collectFiles(map, f, path);\r
+ }\r
+ } else if (StringUtil.getExtension(file.getName()).equals("zip")) {\r
+ collectZippedFiles(map, file, path);\r
+ } else {\r
+ map.put(path, file);\r
+ }\r
+ }\r
+\r
+ public static Set<FileListEntry> generateFileList(Map<String, File> files, String baseURL, boolean skipHash) { \r
+ byte readBuffer[] = new byte[10 * 1024 * 1024];\r
+ \r
+ Map<File, ZipFile> zipFiles = new Hashtable<File, ZipFile>();\r
+ Set<FileListEntry> entries = new HashSet<FileListEntry>();\r
+ \r
+ int t = 0;\r
+ for (Entry<String, File> entry : files.entrySet()) {\r
+ t++;\r
+ //System.out.printf("(%d/%d) %s\n", t, files.size(), entry.getKey());\r
+ if ((t & 0xFF) == 0) {\r
+ System.out.printf("Files Hashed: %d/%d\n", t, files.size());\r
+ }\r
+ \r
+ File file = entry.getValue();\r
+ if (!file.exists()) {\r
+ continue;\r
+ }\r
+ \r
+ try {\r
+ String fpath = entry.getKey();\r
+ long fsize = file.length();\r
+ String fhash = null;\r
+ if (StringUtil.getExtension(file.getName()).equals("zip")) {\r
+ try {\r
+ if (!zipFiles.containsKey(file)) {\r
+ zipFiles.put(file, new ZipFile(file));\r
+ }\r
+ ZipFile zip = zipFiles.get(file);\r
+ ZipEntry zipEntry = zip.getEntry(fpath.substring(fpath.indexOf(file.getName())\r
+ + file.getName().length() + 1));\r
+ \r
+ InputStream in = new BufferedInputStream(zip.getInputStream(zipEntry));\r
+ if (!skipHash) {\r
+ fsize = readFromStream(readBuffer, in);\r
+ fhash = HashUtil.hashToString(HashUtil.generateHash(readBuffer, 0, (int)fsize));\r
+ }\r
+ in.close(); \r
+ } catch (IOException ioe) {\r
+ Log.e("Error hashing file", ioe);\r
+ }\r
+ }\r
+ if (fhash == null) {\r
+ if (!skipHash) {\r
+ BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));\r
+ fsize = readFromStream(readBuffer, in);\r
+ fhash = HashUtil.hashToString(HashUtil.generateHash(readBuffer, 0, (int)fsize));\r
+ in.close();\r
+ }\r
+ }\r
+ String furl = baseURL + "/" + fpath;\r
+ \r
+ entries.add(new FileListEntry(fpath, fsize, fhash, furl, file));\r
+ } catch (Exception e) {\r
+ Log.e("Exception generating file list", e);\r
+ }\r
+ }\r
+ \r
+ for (ZipFile file : zipFiles.values()) {\r
+ try {\r
+ file.close();\r
+ } catch (IOException e) {}\r
+ }\r
+ return entries;\r
+ }\r
+ \r
+ protected static int readFromStream(byte[] out, InputStream in) throws IOException {\r
+ int off = 0;\r
+ while (off < out.length) {\r
+ int r = in.read(out, off, out.length-off);\r
+ if (r < 0) break;\r
+ off += r;\r
+ }\r
+ return off;\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.installer;\r
+\r
+abstract class PackCommand {\r
+ \r
+ private String outputFolder;\r
+ \r
+ public PackCommand(String outputFolder) {\r
+ this.outputFolder = outputFolder;\r
+ }\r
+ \r
+ //Functions\r
+ public abstract void execute();\r
+ \r
+ //Getters\r
+ public String getOutputFolder() {\r
+ return outputFolder;\r
+ }\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.installer;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.ArrayList;\r
+import java.util.Hashtable;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.vnds.Log;\r
+\r
+public class PatchCommand extends PackCommand {\r
+\r
+ private String patchName;\r
+ private String patchedFolder;\r
+ \r
+ public PatchCommand(String patchName, String patchedFolder, String outputFolder) {\r
+ super(outputFolder);\r
+ \r
+ this.patchName = patchName;\r
+ this.patchedFolder = patchedFolder;\r
+ }\r
+ \r
+ //Functions\r
+ public void execute() {\r
+ boolean skipHash = false;\r
+ String baseURL = "patch/" + patchName;\r
+ \r
+ Map<String, File> patchedFiles = new Hashtable<String, File>();\r
+ InstallerPacker.collectFiles(patchedFiles, new File(patchedFolder), false);\r
+ Set<FileListEntry> patchedEntries = InstallerPacker.generateFileList(patchedFiles, baseURL, skipHash);\r
+ \r
+ Map<String, File> baseFiles = new Hashtable<String, File>();\r
+ InstallerPacker.collectFiles(baseFiles, new File(getOutputFolder() + "/base_install"), false);\r
+ Set<FileListEntry> baseEntries = InstallerPacker.generateFileList(baseFiles, baseURL, skipHash);\r
+\r
+ List<FileListEntry> changedList = new ArrayList<FileListEntry>();\r
+ for (FileListEntry entry : patchedEntries) {\r
+ if (!baseEntries.contains(entry)) {\r
+ changedList.add(entry); \r
+ }\r
+ }\r
+ \r
+ try {\r
+ Component component = new Component(patchName, "");\r
+ String patchFolder = getOutputFolder() + "/" + patchName;\r
+ for (FileListEntry changed : changedList) {\r
+ component.addFile(changed);\r
+ \r
+ File target = new File(patchFolder + '/' + changed.getPath());\r
+ if (StringUtil.getExtension(changed.getFile().getName()).equals("zip")) {\r
+ target = new File(patchFolder + '/' + changed.getPath().substring(0,\r
+ changed.getPath().lastIndexOf(changed.getFile().getName())\r
+ +changed.getFile().getName().length()));\r
+ }\r
+ if (!target.exists() || target.length() != changed.getFile().length()) {\r
+ FileUtil.copyFile(changed.getFile(), target);\r
+ }\r
+ }\r
+ \r
+ component.save(new File(getOutputFolder(), patchName + ".xml"));\r
+ } catch (IOException e) {\r
+ Log.e("Exception executing patch command", e);\r
+ }\r
+ }\r
+\r
+ //Getters\r
+ \r
+ //Setters\r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.tools;\r
+\r
+import java.awt.Dimension;\r
+import java.awt.Graphics2D;\r
+import java.awt.Image;\r
+import java.awt.image.BufferedImage;\r
+import java.awt.image.IndexColorModel;\r
+import java.io.BufferedOutputStream;\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.FileWriter;\r
+import java.io.IOException;\r
+import java.io.PrintWriter;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.Random;\r
+\r
+import javax.imageio.ImageIO;\r
+\r
+import nl.weeaboo.awt.ImageUtil;\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.system.ProcessUtil;\r
+import nl.weeaboo.tga.TGAUtil;\r
+import nl.weeaboo.vnds.BatchProcess;\r
+import nl.weeaboo.vnds.FileOp;\r
+import nl.weeaboo.vnds.Log;\r
+import nl.weeaboo.vnds.ProgressListener;\r
+import nl.weeaboo.vnds.VNImageUtil;\r
+\r
+public class ImageConverter {\r
+ \r
+ public enum ConvertType {\r
+ TYPE_RAW_RGBA("RAW RGBA", "dta"),\r
+ TYPE_RAW_RGB256("RAW RGB256", "dta"),\r
+ TYPE_JPG("JPG", "jpg"),\r
+ TYPE_PNG("PNG", "png"),\r
+ TYPE_PNG_256_NEUQUANT("PNG 256 (neuquant)", "png"),\r
+ TYPE_PNG_256_MEDIAN("PNG 256 (median)", "png");\r
+ \r
+ private String label;\r
+ private String fileExt;\r
+ \r
+ private ConvertType(String label, String fileExt) {\r
+ this.label = label;\r
+ this.fileExt = fileExt;\r
+ }\r
+ \r
+ public String getFileExt() { return fileExt; }\r
+ public String toString() { return label; }\r
+ }\r
+ \r
+ public enum ScalingType {\r
+ NONE("None"), BACKGROUND("Background"), SPRITE("Sprite"), STRETCH("Stretch");\r
+\r
+ String label;\r
+ \r
+ private ScalingType(String label) {\r
+ this.label = label;\r
+ }\r
+ \r
+ public String toString() { return label; }\r
+ }\r
+ \r
+ public enum DitheringType {\r
+ NONE("None"), RANDOM("Random"), FLOYD_STEINBERG("Floyd-Steinberg");\r
+ \r
+ String label;\r
+ \r
+ private DitheringType(String label) {\r
+ this.label = label;\r
+ }\r
+ \r
+ public String toString() { return label; }\r
+ }\r
+ \r
+ private int maxThreads = 8;\r
+ private ConvertType mode = ConvertType.TYPE_RAW_RGBA;\r
+ private ScalingType scaling = ScalingType.NONE;\r
+ private Dimension targetScreenSize = new Dimension(256, 192);\r
+ private Dimension srcScreenSize = new Dimension(800, 600); //Matters for sprites only\r
+ private int quality = 98; //JPG Only\r
+ private DitheringType dithering = DitheringType.NONE;\r
+ private boolean log;\r
+ \r
+ //Temporary vars\r
+ private Map<File, StringBuilder> processLogs;\r
+ \r
+ //Functions\r
+ protected static void printUsage() {\r
+ System.err.printf("Usage: java -jar ImageConverter.jar <flags> <file>\nflags:"\r
+ + "\n\t-threads <num>"\r
+ + "\n\t-raw <RGBA|256>"\r
+ + "\n\t-png <RGBA|256>"\r
+ + "\n\t-jpg <quality (0-100)>"\r
+// + "\n\t-size <width> <height>"\r
+ + "\n"); \r
+ }\r
+\r
+ public static void main(String args[]) throws IOException {\r
+ ImageConverter ic = new ImageConverter();\r
+\r
+ String filename = null;\r
+ try {\r
+ for (int n = 0; n < args.length; n++) {\r
+ if (args[n].startsWith("-jpg")) {\r
+ ic.setMode(ConvertType.TYPE_JPG);\r
+ ic.quality = Integer.parseInt(args[++n]);\r
+ } else if (args[n].startsWith("-png")) {\r
+ if (!args[++n].equals("256")) {\r
+ ic.setMode(ConvertType.TYPE_PNG);\r
+ } else {\r
+ ic.setDitheringType(DitheringType.FLOYD_STEINBERG);\r
+ ic.setMode(ConvertType.TYPE_PNG_256_NEUQUANT);\r
+ }\r
+ } else if (args[n].startsWith("-raw")) {\r
+ if (!args[++n].equals("256")) {\r
+ ic.setMode(ConvertType.TYPE_RAW_RGBA);\r
+ } else {\r
+ ic.setDitheringType(DitheringType.FLOYD_STEINBERG);\r
+ ic.setMode(ConvertType.TYPE_RAW_RGB256);\r
+ }\r
+ } else if (args[n].startsWith("-threads")) {\r
+ ic.maxThreads = Integer.parseInt(args[++n]);\r
+/* \r
+ } else if (args[n].startsWith("-size")) {\r
+ int w = Integer.parseInt(args[++n]);\r
+ int h = Integer.parseInt(args[++n]);\r
+ ic.setBackgroundSize(w, h);\r
+*/ \r
+ } else if (filename == null) {\r
+ filename = args[n];\r
+ } else {\r
+ throw new IllegalArgumentException("Error parsing arg: " + args[n]);\r
+ }\r
+ }\r
+ } catch (RuntimeException re) {\r
+ printUsage();\r
+ return;\r
+ }\r
+ \r
+ if (filename == null) {\r
+ printUsage();\r
+ return;\r
+ }\r
+ \r
+ ic.convertFolder(filename, new ProgressListener() {\r
+ public void onFinished(String message) {\r
+ System.out.printf("%s\n", message);\r
+ }\r
+ public void onProgress(int value, int max, String message) {\r
+ System.out.printf("Processing (%d/%d) %s...\n", value, max, message);\r
+ }\r
+ });\r
+ }\r
+ \r
+ public void convertFolder(String folder, final ProgressListener pl) throws IOException {\r
+ convertFolder(folder, pl, 1);\r
+ }\r
+ public void convertFolder(String folder, final ProgressListener pl, int maxDepth) throws IOException {\r
+ processLogs = new HashMap<File, StringBuilder>();\r
+\r
+ Map<String, File> files = new HashMap<String, File>();\r
+ File folderF = new File(folder).getCanonicalFile();\r
+ if (folderF.isDirectory()) {\r
+ for (File file : folderF.listFiles()) {\r
+ if (!file.isDirectory()) files.put(file.getName(), file);\r
+ }\r
+ } else {\r
+ files.put(folderF.getName(), folderF);\r
+ }\r
+ \r
+ BatchProcess bp = new BatchProcess();\r
+ bp.setTaskSize(32);\r
+ bp.setThreads(maxThreads);\r
+ bp.setThreadPriority(Thread.MIN_PRIORITY);\r
+ bp.addProgressListener(pl);\r
+ try {\r
+ bp.run(files, new FileOp() {\r
+ @Override\r
+ public void execute(String relpath, File file) throws IOException {\r
+ convertFile(file);\r
+ }\r
+ });\r
+ } catch (InterruptedException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ \r
+ public File convertFile(File file) {\r
+ return convertFile(file, null);\r
+ }\r
+ public File convertFile(File file, File targetFolder) { \r
+ String filenameNoExt = file.getName(); \r
+ if (filenameNoExt.lastIndexOf('.') > 0) {\r
+ filenameNoExt = filenameNoExt.substring(0, filenameNoExt.lastIndexOf('.'));\r
+ }\r
+\r
+ StringBuilder log = new StringBuilder();\r
+ if (isLogging()) processLogs.put(file, log);\r
+ \r
+ try { \r
+ BufferedImage result = null;\r
+ try {\r
+ if (file.getName().endsWith(".bmp")) {\r
+ result = VNImageUtil.readBMP(file);\r
+ } else if (file.getName().endsWith(".tga")) {\r
+ result = TGAUtil.readTGA(file);\r
+ } else {\r
+ result = ImageIO.read(file);\r
+ }\r
+ } catch (RuntimeException re) {\r
+ Log.w("Exception while reading image", re);\r
+ }\r
+ if (result == null) {\r
+ Log.e("Unreadable image: " + file.getAbsolutePath());\r
+ return null;\r
+ }\r
+ \r
+ int w = result.getWidth();\r
+ int h = result.getHeight();\r
+\r
+ //Scaling \r
+ if (scaling == ScalingType.STRETCH) {\r
+ w = targetScreenSize.width;\r
+ h = targetScreenSize.height;\r
+ result = ImageUtil.getScaledImage(result, w, h, Image.SCALE_AREA_AVERAGING);\r
+ } else {\r
+ int scaledW = w;\r
+ int scaledH = h;\r
+ float scale = Math.min(targetScreenSize.width / (float)srcScreenSize.width,\r
+ targetScreenSize.height / (float)srcScreenSize.height);\r
+ if (scaling == ScalingType.SPRITE || scaling == ScalingType.BACKGROUND) {\r
+ scaledW = Math.max(1, Math.round(w * scale));\r
+ scaledH = Math.max(1, Math.round(h * scale));\r
+ }\r
+ \r
+ Image scaled = ImageUtil.getScaledImage(result, scaledW, scaledH, Image.SCALE_AREA_AVERAGING);\r
+ \r
+ if (scaling == ScalingType.BACKGROUND) {\r
+ w = targetScreenSize.width;\r
+ h = targetScreenSize.height;\r
+ } else {\r
+ w = scaledW;\r
+ h = scaledH;\r
+ }\r
+ \r
+ result = ImageUtil.createCompatibleImage(w, h, scaled);\r
+ if (mode == ConvertType.TYPE_JPG) {\r
+ result = ImageUtil.asOpaqueImage(result);\r
+ }\r
+ \r
+ Graphics2D g = (Graphics2D)result.getGraphics();\r
+ if (scaling == ScalingType.BACKGROUND) {\r
+ int sw = Math.round(scale*srcScreenSize.width);\r
+ int sh = Math.round(scale*srcScreenSize.height);\r
+ g.clipRect((w-sw)/2, (h-sh)/2, sw, sh);\r
+ }\r
+ g.drawImage(scaled, (w-scaledW)/2, (h-scaledH)/2, null);\r
+ g.dispose();\r
+ }\r
+ \r
+ //Dithering\r
+ if (dithering == DitheringType.RANDOM) {\r
+ int[] rgb = result.getRGB(0, 0, w, h, null, 0, w);\r
+ \r
+ Random rnd = new Random(0x13371337);\r
+ int t = 0;\r
+ for (int y = 0; y < h; y++) {\r
+ for (int x = 0; x < w; x++) {\r
+ int c = rgb[t];\r
+ \r
+ double r = ((c>>16)&0xFF) * 31.0 / 255.0;\r
+ double g = ((c>>8 )&0xFF) * 31.0 / 255.0;\r
+ double b = ((c )&0xFF) * 31.0 / 255.0;\r
+ \r
+ boolean ceil = rnd.nextFloat() < (r-Math.floor(r) + g-Math.floor(g) + b-Math.floor(b)) / 3.0;\r
+ \r
+ int ri = (int)(ceil ? Math.ceil(r) : Math.floor(r));\r
+ int gi = (int)(ceil ? Math.ceil(g) : Math.floor(g));\r
+ int bi = (int)(ceil ? Math.ceil(b) : Math.floor(b));\r
+ \r
+ rgb[t] = (c&0xFF000000) | (((ri<<3)&0xFF)<<16) | (((gi<<3)&0xFF)<<8) | ((bi<<3)&0xFF);\r
+ t++;\r
+ }\r
+ }\r
+ \r
+ result.setRGB(0, 0, w, h, rgb, 0, w);\r
+ } else if (dithering == DitheringType.FLOYD_STEINBERG) {\r
+ int[] rgb = result.getRGB(0, 0, w, h, null, 0, w);\r
+ floydSteinberg(rgb, w, h);\r
+ result.setRGB(0, 0, w, h, rgb, 0, w);\r
+ }\r
+ \r
+ if (targetFolder == null) {\r
+ targetFolder = file.getParentFile();\r
+ file.delete();\r
+ }\r
+\r
+ //Create unique hash (multiple threads are writing temp files in the same folder)\r
+ String threadHash = String.valueOf(hashCode() ^ file.hashCode() ^ Thread.currentThread().hashCode());\r
+ \r
+ file = new File(String.format("%s/%s.%s",\r
+ targetFolder, filenameNoExt, mode.getFileExt().toLowerCase()));\r
+ if (file.getParentFile() != null) {\r
+ file.getParentFile().mkdirs();\r
+ }\r
+ \r
+ if (mode.getFileExt().equalsIgnoreCase("jpg")) {\r
+ String bmpFile = String.format("%s/__%s.bmp", targetFolder, threadHash);\r
+ String tmpFile = String.format("%s/__%s.jpg", targetFolder, threadHash);\r
+\r
+ ImageIO.write(result, "bmp", new File(bmpFile));\r
+ \r
+ Process p = ProcessUtil.execInDir(String.format(\r
+ "cjpeg -quality %d -optimize -dct fast \"%s\" \"%s\"",\r
+ quality, bmpFile, tmpFile),\r
+ "tools/");\r
+ ProcessUtil.waitFor(p);\r
+ ProcessUtil.kill(p);\r
+ file.delete();\r
+ new File(bmpFile).delete();\r
+ new File(tmpFile).renameTo(file);\r
+ } else if (mode.getFileExt().equalsIgnoreCase("png")) { \r
+ String tmpFile = String.format("%s/__%s.png", targetFolder, threadHash);\r
+ ImageIO.write(result, "png", new File(tmpFile));\r
+ \r
+ if (mode == ConvertType.TYPE_PNG_256_MEDIAN) {\r
+ Process p = ProcessUtil.execInDir(String.format(\r
+ "pngquant 256 \"%s\"", tmpFile),\r
+ "tools/pngquant-0.95/");\r
+ ProcessUtil.waitFor(p);\r
+ ProcessUtil.kill(p);\r
+ file.delete();\r
+ new File(StringUtil.stripExtension(tmpFile)+"-fs8.png").renameTo(file);\r
+ } else if (mode == ConvertType.TYPE_PNG_256_NEUQUANT) {\r
+ Process p = ProcessUtil.execInDir(String.format(\r
+ "pngnq \"%s\"", tmpFile),\r
+ "tools/pngnq-0.5-i386/");\r
+ ProcessUtil.waitFor(p);\r
+ ProcessUtil.kill(p);\r
+ file.delete();\r
+ new File(StringUtil.stripExtension(tmpFile)+"-nq8.png").renameTo(file);\r
+ } else {\r
+ String crushedName = StringUtil.stripExtension(tmpFile)+".crushed.png";\r
+ Process p = ProcessUtil.execInDir(String.format(\r
+ "pngcrush -fix \"%s\" \"%s\"", tmpFile, crushedName),\r
+ "tools/pngcrush-1.6.10/");\r
+ ProcessUtil.waitFor(p);\r
+ ProcessUtil.kill(p);\r
+ file.delete();\r
+ new File(crushedName).renameTo(file);\r
+ }\r
+ \r
+ new File(tmpFile).delete();\r
+ } else if (mode.getFileExt().equalsIgnoreCase("dta")) {\r
+ if (mode == ConvertType.TYPE_RAW_RGBA) {\r
+ int[] rgb = result.getRGB(0, 0, w, h, null, 0, w);\r
+ \r
+ BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(file));\r
+ for (int c : rgb) {\r
+ int a = ((c>>>24) >= 127 ? (1<<15) : 0);\r
+ int r = (c>>19) & 31;\r
+ int g = (c>>11) & 31;\r
+ int b = (c>>3) & 31;\r
+ c = a | (b<<10) | (g<<5) | (r);\r
+ \r
+ bout.write(c&0xFF);\r
+ bout.write((c>>8)&0xFF);\r
+ }\r
+ bout.flush();\r
+ bout.close();\r
+ } else if (mode == ConvertType.TYPE_RAW_RGB256) {\r
+ File tmpFile = new File(String.format("%s/__%s.png", targetFolder, threadHash));\r
+ ImageIO.write(result, "png", tmpFile);\r
+\r
+ Process p = ProcessUtil.execInDir(String.format(\r
+ "pngnq \"%s\"", tmpFile.getAbsolutePath()),\r
+ "tools/pngnq-0.5-i386/");\r
+ ProcessUtil.waitFor(p);\r
+ ProcessUtil.kill(p);\r
+ tmpFile.delete();\r
+ new File(StringUtil.stripExtension(tmpFile.getAbsolutePath())+"-nq8.png").renameTo(tmpFile);\r
+\r
+ result = ImageIO.read(tmpFile);\r
+ IndexColorModel icm = (IndexColorModel)result.getColorModel();\r
+\r
+ BufferedOutputStream bout;\r
+ \r
+ bout = new BufferedOutputStream(new FileOutputStream(\r
+ StringUtil.stripExtension(file.getAbsolutePath())+".pal"));\r
+ for (int n = 0; n < icm.getMapSize(); n++) {\r
+ int a = (1<<15);\r
+ int r = icm.getRed(n) >> 3;\r
+ int g = icm.getGreen(n) >> 3;\r
+ int b = icm.getBlue(n) >> 3;\r
+ int c = a | (b<<10) | (g<<5) | (r);\r
+\r
+ bout.write(c&0xFF);\r
+ bout.write((c>>8)&0xFF);\r
+ }\r
+ bout.close();\r
+\r
+ bout = new BufferedOutputStream(new FileOutputStream(file));\r
+ int dta[] = new int[result.getWidth() * result.getHeight()];\r
+ result.getRaster().getPixels(0, 0, result.getWidth(), result.getHeight(), dta);\r
+ for (int n = 0; n < dta.length; n++) {\r
+ bout.write(dta[n]);\r
+ }\r
+ bout.close();\r
+ \r
+ tmpFile.delete();\r
+ }\r
+ } else { \r
+ throw new IllegalArgumentException("Invalid file-ext: " + mode.getFileExt()); \r
+ }\r
+ return file;\r
+ } catch (Exception e) {\r
+ Log.w(file.getName() + " " + e);\r
+ log.append(e.toString());\r
+ }\r
+\r
+ return null;\r
+ }\r
+ \r
+\r
+ public File dumpLog(String filename) throws IOException {\r
+ File file = new File(filename);\r
+ PrintWriter out = new PrintWriter(new FileWriter(file));\r
+ \r
+ out.println("----------------------------------------");\r
+ out.println("----------------------------------------");\r
+ for (Entry<File, StringBuilder> entry : getLogs().entrySet()) {\r
+ out.println();\r
+ out.println("Log for file:" + entry.getKey().getAbsolutePath());\r
+ out.println();\r
+ \r
+ out.println(entry.getValue().toString());\r
+ \r
+ out.println("----------------------------------------");\r
+ out.println("----------------------------------------");\r
+ }\r
+ \r
+ out.close();\r
+ \r
+ return file;\r
+ }\r
+\r
+ private static int round(float f) {\r
+ return (int)f;\r
+ }\r
+ \r
+ private static float saturate255(float f) {\r
+ if (f < 0f) return 0f;\r
+ if (f > 255f) return 255f;\r
+ return f;\r
+ }\r
+ \r
+ /** Does dithering for 5-bit colors (equivalent to 8-bit / 8.0) */\r
+ private static void floydSteinberg(int rgb[], int w, int h) {\r
+ int L = w * h;\r
+\r
+ float rgba[][] = new float[4][L];\r
+ for (int t = 0; t < L; t++) {\r
+ rgba[0][t] = ((rgb[t]>>>16)&0xFF);\r
+ rgba[1][t] = ((rgb[t]>>>8 )&0xFF);\r
+ rgba[2][t] = ((rgb[t] )&0xFF);\r
+ rgba[3][t] = ((rgb[t]>>>24)&0xFF);\r
+ }\r
+\r
+ final float DIV_8 = .125f;\r
+ final float DIV_16 = .0625f;\r
+ final int RIGHT = 1;\r
+ final int DOWN = w;\r
+ final int DOWN_LEFT = DOWN-1;\r
+ final int DOWN_RIGHT = DOWN+1;\r
+ \r
+ for (int c = 0; c < 3; c++) {\r
+ float[] p = rgba[c];\r
+ int t = 0;\r
+ for (int y = 0; y < h; y++) {\r
+ for (int x = 0; x < w; x++) { \r
+ float oldc = p[t];\r
+ float newc = p[t] = round(oldc * DIV_8) << 3;\r
+ float error = oldc - newc;\r
+\r
+ if (x+1 < w) {\r
+ p[t+RIGHT] += (7 * error) * DIV_16;\r
+ p[t+RIGHT] = saturate255(p[t+RIGHT]);\r
+ }\r
+ if (y+1 < h) {\r
+ if (x-1 >= 0) {\r
+ p[t+DOWN_LEFT] += (3 * error) * DIV_16;\r
+ p[t+DOWN_LEFT] = saturate255(p[t+DOWN_LEFT]);\r
+ }\r
+ p[t+DOWN] += (5 * error) * DIV_16;\r
+ p[t+DOWN] = saturate255(p[t+DOWN]);\r
+ if (x+1 < w) {\r
+ p[t+DOWN_RIGHT] += error * DIV_16;\r
+ p[t+DOWN_RIGHT] = saturate255(p[t+DOWN_RIGHT]);\r
+ }\r
+ }\r
+ \r
+ t++;\r
+ } \r
+ }\r
+ }\r
+ \r
+ int t = 0;\r
+ for (int y = 0; y < h; y++) {\r
+ for (int x = 0; x < w; x++) {\r
+ int a = Math.min(255, Math.max(0, (int)Math.round(rgba[3][t])));\r
+ int r = Math.min(255, Math.max(0, (int)Math.round(rgba[0][t])));\r
+ int g = Math.min(255, Math.max(0, (int)Math.round(rgba[1][t])));\r
+ int b = Math.min(255, Math.max(0, (int)Math.round(rgba[2][t])));\r
+ rgb[t] = (a<<24)|(r<<16)|(g<<8)|(b);\r
+ t++;\r
+ }\r
+ }\r
+ }\r
+ \r
+ // Getters\r
+ public Map<File, StringBuilder> getLogs() { return processLogs; }\r
+ public boolean isLogging() { return log; }\r
+ public ConvertType getMode() { return mode; }\r
+ public ScalingType getScaling() { return scaling; }\r
+ public Dimension getTargetScreenSize() { return new Dimension(targetScreenSize); }\r
+ public Dimension getSourceScreenSize() { return new Dimension(srcScreenSize); }\r
+ public int getQuality() { return quality; }\r
+ public DitheringType getDitheringType() { return dithering; }\r
+ public int getMaxThreads() { return maxThreads; }\r
+ \r
+ // Setters\r
+ public void setMode(ConvertType mode) { this.mode = mode; }\r
+ public void setScalingType(ScalingType s) { this.scaling = s; }\r
+ public void setLogging(boolean l) { this.log = l; }\r
+ public void setSourceScreenSize(int w, int h) { srcScreenSize.width = w; srcScreenSize.height = h; }\r
+ public void setTargetScreenSize(int w, int h) { targetScreenSize.width = w; targetScreenSize.height = h; }\r
+ public void setQuality(int quality) { this.quality = quality; }\r
+ public void setDitheringType(DitheringType dithering) { this.dithering = dithering; }\r
+ public void setMaxThreads(int mt) { this.maxThreads = mt; }\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.tools;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Dimension;\r
+import java.awt.GridLayout;\r
+import java.awt.datatransfer.DataFlavor;\r
+import java.awt.datatransfer.Transferable;\r
+import java.awt.datatransfer.UnsupportedFlavorException;\r
+import java.awt.dnd.DnDConstants;\r
+import java.awt.dnd.DropTarget;\r
+import java.awt.dnd.DropTargetDragEvent;\r
+import java.awt.dnd.DropTargetDropEvent;\r
+import java.awt.dnd.DropTargetEvent;\r
+import java.awt.dnd.DropTargetListener;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.List;\r
+\r
+import javax.swing.JButton;\r
+import javax.swing.JCheckBox;\r
+import javax.swing.JComboBox;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JOptionPane;\r
+import javax.swing.JPanel;\r
+import javax.swing.JSpinner;\r
+import javax.swing.JTextField;\r
+import javax.swing.SpinnerNumberModel;\r
+import javax.swing.border.EmptyBorder;\r
+\r
+import nl.weeaboo.awt.AwtUtil;\r
+import nl.weeaboo.awt.DirectValidatingField;\r
+import nl.weeaboo.awt.FileBrowseField;\r
+import nl.weeaboo.awt.Sash;\r
+import nl.weeaboo.vnds.Log;\r
+import nl.weeaboo.vnds.ProgressListener;\r
+import nl.weeaboo.vnds.ProgressRunnable;\r
+import nl.weeaboo.vnds.VNDSProgressDialog;\r
+import nl.weeaboo.vnds.tools.ImageConverter.ConvertType;\r
+import nl.weeaboo.vnds.tools.ImageConverter.DitheringType;\r
+import nl.weeaboo.vnds.tools.ImageConverter.ScalingType;\r
+\r
+/*\r
+ * Changes:\r
+ *\r
+ * 2009/11/08 -- v1.0.5\r
+ * - Code cleanup\r
+ * - Converts input files even if they're already the correct file type\r
+ *\r
+ * 2009/05/01 -- v1.0.4\r
+ * - Added RAW_RGB256 mode\r
+ *\r
+ * 2009/02/28 -- v1.0.3\r
+ * - Using external programs to create better compressed images that also\r
+ * actually work on vnds. (cjpeg, pngquant, pngnq, pngcrush)\r
+ *\r
+ * 2009/02/17 -- v1.0.2\r
+ * - Fixed bug in Floyd-Steinberg dithering\r
+ * - Maybe fixed JPG's not working in vnds\r
+ *\r
+ * 2008/12/20 -- v1.0.1\r
+ * - Drag images into the window to convert\r
+ *\r
+ * 2008/11/25 -- v1.0\r
+ * - Initial Release\r
+ * \r
+ */\r
+@SuppressWarnings("serial")\r
+public class ImageConverterGUI extends JFrame {\r
+\r
+ private ImageConverter converter;\r
+ \r
+ private JComboBox modeCombo;\r
+ private JComboBox scalingCombo;\r
+ private JTextField widthField;\r
+ private JTextField heightField;\r
+ private JSpinner qualitySpinner;\r
+ private JComboBox ditheringCombo;\r
+ private JCheckBox loggingCheck;\r
+ private JSpinner threadsSpinner;\r
+ private FileBrowseField browseField;\r
+ private JButton convertButton;\r
+ \r
+ public ImageConverterGUI() {\r
+ converter = new ImageConverter();\r
+ \r
+ setTitle("VNDS Image Converter v1.0.5");\r
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);\r
+ \r
+ setLayout(new BorderLayout());\r
+ add(createCenterPanel(), BorderLayout.CENTER);\r
+ \r
+ reset();\r
+ \r
+ setMinimumSize(new Dimension(300, 100));\r
+ pack();\r
+ setLocationRelativeTo(null);\r
+ setVisible(true);\r
+ }\r
+ \r
+ //Functions\r
+ public static void main(String args[]) {\r
+ AwtUtil.setDefaultLAF();\r
+ \r
+ if (args.length > 0) {\r
+ try {\r
+ ImageConverter.main(args);\r
+ } catch (IOException e) {\r
+ e.printStackTrace();\r
+ }\r
+ } else {\r
+ new ImageConverterGUI();\r
+ }\r
+ }\r
+ \r
+ protected JPanel createCenterPanel() {\r
+ JLabel modeLabel = new JLabel("Output Type");\r
+ modeCombo = new JComboBox(ImageConverter.ConvertType.values());\r
+ modeCombo.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent e) {\r
+ ConvertType mode = (ConvertType)modeCombo.getSelectedItem();\r
+ converter.setMode(mode);\r
+ qualitySpinner.setEnabled(mode == ConvertType.TYPE_JPG);\r
+ }\r
+ });\r
+\r
+ JLabel scalingLabel = new JLabel("Scaling Mode");\r
+ scalingCombo = new JComboBox(ImageConverter.ScalingType.values());\r
+ scalingCombo.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent e) {\r
+ ScalingType s = (ScalingType)scalingCombo.getSelectedItem();\r
+ converter.setScalingType(s);\r
+ }\r
+ });\r
+ \r
+ JLabel sizeLabel = new JLabel("Scaling Mode Size");\r
+ widthField = new DirectValidatingField() {\r
+ public boolean isValid(String text) {\r
+ try {\r
+ int i = Integer.parseInt(text);\r
+ return (i > 0 && i <= 2048);\r
+ } catch (NumberFormatException nfe) { \r
+ }\r
+ return false;\r
+ }\r
+ protected void onValidTextEntered(String text) {\r
+ int w = Integer.parseInt(text);\r
+ converter.setSourceScreenSize(w, converter.getSourceScreenSize().height);\r
+ }\r
+ };\r
+ heightField = new DirectValidatingField() {\r
+ public boolean isValid(String text) {\r
+ try {\r
+ int i = Integer.parseInt(text);\r
+ return (i > 0 && i <= 2048);\r
+ } catch (NumberFormatException nfe) { \r
+ }\r
+ return false;\r
+ }\r
+ protected void onValidTextEntered(String text) {\r
+ int h = Integer.parseInt(text);\r
+ converter.setSourceScreenSize(converter.getSourceScreenSize().width, h);\r
+ }\r
+ };\r
+ JLabel qualityLabel = new JLabel("JPEG Quality");\r
+ qualitySpinner = new JSpinner(new SpinnerNumberModel(98, 0, 100, 1));\r
+ qualitySpinner.setEnabled(false);\r
+\r
+ JLabel ditheringLabel = new JLabel("Dithering");\r
+ ditheringCombo = new JComboBox(new Object[] {DitheringType.NONE,\r
+ DitheringType.FLOYD_STEINBERG, DitheringType.RANDOM});\r
+ ditheringCombo.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent e) {\r
+ DitheringType dt = (DitheringType)ditheringCombo.getSelectedItem();\r
+ converter.setDitheringType(dt);\r
+ }\r
+ });\r
+\r
+ JLabel loggingLabel = new JLabel("Write logfile");\r
+ loggingCheck = new JCheckBox();\r
+ JLabel threadsLabel = new JLabel("Threads");\r
+ threadsSpinner = new JSpinner(new SpinnerNumberModel(8, 1, 128, 1));\r
+ \r
+ JPanel sizePanel = new JPanel(new GridLayout(-1, 3, 5, 5));\r
+ sizePanel.add(widthField);\r
+ sizePanel.add(new JLabel(" x ", JLabel.CENTER));\r
+ sizePanel.add(heightField);\r
+ \r
+ JPanel panel = new JPanel(new GridLayout(-1, 2, 15, 5));\r
+ panel.add(modeLabel); panel.add(modeCombo);\r
+ panel.add(scalingLabel); panel.add(scalingCombo);\r
+ panel.add(sizeLabel); panel.add(sizePanel);\r
+ panel.add(qualityLabel); panel.add(qualitySpinner);\r
+ panel.add(ditheringLabel); panel.add(ditheringCombo);\r
+ panel.add(loggingLabel); panel.add(loggingCheck);\r
+ panel.add(threadsLabel); panel.add(threadsSpinner);\r
+ \r
+ browseField = FileBrowseField.readFolder("Folder", new File(""));\r
+ convertButton = new JButton("Convert");\r
+ convertButton.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent e) {\r
+ convert();\r
+ }\r
+ });\r
+ \r
+ JPanel bottomPanel = new JPanel(new BorderLayout(10, 10));\r
+ bottomPanel.add(new Sash(Sash.HORIZONTAL), BorderLayout.NORTH);\r
+ bottomPanel.add(browseField, BorderLayout.CENTER);\r
+ bottomPanel.add(convertButton, BorderLayout.EAST);\r
+ \r
+ JPanel panel2 = new JPanel(new BorderLayout(0, 10));\r
+ panel2.setBorder(new EmptyBorder(10, 10, 10, 10));\r
+ panel2.add(panel, BorderLayout.NORTH);\r
+ panel2.add(bottomPanel, BorderLayout.SOUTH);\r
+\r
+ new DropTarget(panel2, new DropTargetListener() {\r
+ public void dragEnter(DropTargetDragEvent dtde) {\r
+ }\r
+ public void dragExit(DropTargetEvent dte) {\r
+ }\r
+ public void dragOver(DropTargetDragEvent dtde) {\r
+ for (DataFlavor df : dtde.getCurrentDataFlavorsAsList()) {\r
+ if (df.isFlavorJavaFileListType()) {\r
+ dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ @SuppressWarnings("unchecked")\r
+ public void drop(DropTargetDropEvent dtde) {\r
+ dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);\r
+ \r
+ Transferable t = dtde.getTransferable(); \r
+ for (DataFlavor df : t.getTransferDataFlavors()) {\r
+ if (df.isFlavorJavaFileListType()) {\r
+ try {\r
+ List<File> list = (List<File>)t.getTransferData(df);\r
+ convert(list);\r
+ } catch (UnsupportedFlavorException e) {\r
+ Log.w("Drag&Drop Exception", e);\r
+ } catch (IOException e) {\r
+ Log.w("Drag&Drop Exception", e);\r
+ } \r
+ } \r
+ }\r
+ }\r
+ public void dropActionChanged(DropTargetDragEvent dtde) {\r
+ }\r
+ });\r
+\r
+ return panel2;\r
+ }\r
+ \r
+ public void convert() {\r
+ convert(null);\r
+ } \r
+ public void convert(final List<File> files) {\r
+ final File folder = browseField.getFile();\r
+ if (files == null) {\r
+ if (!folder.exists() || !folder.isDirectory()) {\r
+ JOptionPane.showMessageDialog(null, "Invalid directory: \"" + folder + "\"",\r
+ "Error", JOptionPane.ERROR_MESSAGE);\r
+ return;\r
+ }\r
+ }\r
+\r
+ converter.setMode((ConvertType)modeCombo.getSelectedItem());\r
+ converter.setScalingType((ScalingType)scalingCombo.getSelectedItem());\r
+ converter.setQuality((Integer)qualitySpinner.getValue());\r
+ converter.setLogging(loggingCheck.isSelected());\r
+ converter.setMaxThreads((Integer)threadsSpinner.getValue());\r
+ \r
+ ProgressListener pl = new ProgressListener() {\r
+ public void onFinished(String message) {\r
+ String logPath = "";\r
+ String logPart = "";\r
+ \r
+ try {\r
+ if (converter.isLogging()) {\r
+ logPath = converter.dumpLog("conversion.log").getAbsolutePath();\r
+ logPart = String.format("<br><br> Log dumped to %s", logPath);\r
+ }\r
+ \r
+ JOptionPane.showMessageDialog(null, String.format(\r
+ "<html>Conversion finished.%s</html>", logPart),\r
+ "Finished", JOptionPane.PLAIN_MESSAGE);\r
+ } catch (IOException e) {\r
+ AwtUtil.showError(e);\r
+ } \r
+ }\r
+ public void onProgress(int value, int max, String message) {\r
+ }\r
+ };\r
+ \r
+ ProgressRunnable task = new ProgressRunnable() {\r
+ public void run(ProgressListener pl) {\r
+ if (files == null) {\r
+ try {\r
+ converter.convertFolder(folder.toString(), pl);\r
+ } catch (IOException e) {\r
+ e.printStackTrace();\r
+ }\r
+ } else {\r
+ for (File f : files) {\r
+ converter.convertFile(f);\r
+ }\r
+ }\r
+ }\r
+ };\r
+ \r
+ VNDSProgressDialog dialog = new VNDSProgressDialog();\r
+ dialog.showDialog(task, pl);\r
+ }\r
+ \r
+ public void reset() {\r
+ widthField.setText("800");\r
+ heightField.setText("600");\r
+ qualitySpinner.setValue(converter.getQuality());\r
+ threadsSpinner.setValue(converter.getMaxThreads());\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.tools;\r
+\r
+import java.awt.image.BufferedImage;\r
+import java.io.File;\r
+import java.io.IOException;\r
+\r
+import javax.imageio.ImageIO;\r
+\r
+public class MaskConverter {\r
+\r
+ //Functions\r
+ public static void main(String args[]) throws IOException {\r
+ if (args.length < 1) {\r
+ System.err.println("Usage: MaskConverter <folder>");\r
+ return;\r
+ }\r
+ \r
+ process(new File(args[0]));\r
+ }\r
+ \r
+ private static void process(File file) {\r
+ if (file.isDirectory()) {\r
+ for (File f : file.listFiles()) {\r
+ if (f.getName().toLowerCase().endsWith("jpg")\r
+ || f.getName().toLowerCase().endsWith("bmp"))\r
+ {\r
+ process(f);\r
+ }\r
+ }\r
+ } else {\r
+ try {\r
+ BufferedImage image = ImageIO.read(file);\r
+ int w = image.getWidth();\r
+ int w2 = w / 2;\r
+ int h = image.getHeight();\r
+ \r
+ int rgb[] = image.getRGB(0, 0, w, h, new int[w * h], 0, w);\r
+ for (int y = 0; y < h; y++) {\r
+ int offset = y * w;\r
+ for (int x = 0; x < w2; x++) {\r
+ int c = rgb[offset]; \r
+ int alpha = rgb[offset + w2]; \r
+ alpha = 255 - (int)(0.30f * ((alpha >> 16) & 0xFF)\r
+ + 0.59f * ((alpha >> 8) & 0xFF)\r
+ + 0.11f * (alpha & 0xFF));\r
+ alpha = Math.max(0, Math.min(255, alpha));\r
+ \r
+ rgb[offset] = (alpha<<24) | (c & 0xFFFFFF);\r
+ offset++;\r
+ }\r
+ }\r
+ \r
+ //Write back to an image\r
+ BufferedImage result = new BufferedImage(w2, h, BufferedImage.TYPE_INT_ARGB);\r
+ result.setRGB(0, 0, w2, h, rgb, 0, w);\r
+ \r
+ //Write to disc\r
+ String path = file.getAbsolutePath();\r
+ path = path.substring(0, path.length() - 3) + "png";\r
+ ImageIO.write(result, "png", new File(path));\r
+ \r
+ //Delete original\r
+ file.delete();\r
+ \r
+ System.out.println("Writing: " + path);\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ }\r
+ \r
+ //Getters\r
+\r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.tools;\r
+\r
+/**\r
+ * Octree.java -----------\r
+ * \r
+ * @author Sascha L. Teichmann (sascha@intevation.de)\r
+ * @version 0.1 date 21:31:02 Mi 07-Mrz-2001\r
+ */\r
+\r
+public class Octree {\r
+ \r
+ static public class Node {\r
+ Node parent;\r
+ Node children[] = new Node[8];\r
+\r
+ int midRed, midGreen, midBlue;\r
+\r
+ int totalRed, totalGreen, totalBlue;\r
+\r
+ byte id; // which child of parent?\r
+ byte census; // used children\r
+ byte level; // level in tree\r
+\r
+ int colorNumber; // index in color table\r
+\r
+ int numberUnique;\r
+ int numberColors;\r
+\r
+ public Node() {\r
+ }\r
+\r
+ public Node(int midRed, int midGreen, int midBlue, Node parent, byte id) {\r
+ this.midRed = midRed;\r
+ this.midGreen = midGreen;\r
+ this.midBlue = midBlue;\r
+\r
+ this.parent = parent;\r
+ this.level = (byte) (parent == null ? 0 : parent.level + 1);\r
+ this.id = id;\r
+ }\r
+ }\r
+\r
+ Node root;\r
+\r
+ public static final int MAX_TREE_DEPTH = 8;\r
+ public static final int MAX_NODES = 266817;\r
+ public static final int MAX_RGB = 255;\r
+\r
+ // prepare square tables\r
+ public static final int SQUARES[] = new int[MAX_RGB * 2 + 1];\r
+\r
+ public int shift[] = new int[MAX_TREE_DEPTH + 1];\r
+\r
+ static {\r
+ for (int i = -MAX_RGB; i <= MAX_RGB; ++i)\r
+ SQUARES[i + MAX_RGB] = i * i;\r
+\r
+ }\r
+\r
+ int depth; // depth of the tree\r
+ int nodes; // number of nodes in tree\r
+ int colors; // nodes of colors\r
+\r
+ int pruningTreshold;\r
+ int nextPruningTreshold;\r
+\r
+ int colormap[];\r
+\r
+ int cdistance;\r
+ int cred, cgreen, cblue;\r
+ int colorNumber;\r
+\r
+ public Octree() {\r
+ root = new Node((MAX_RGB + 1) >> 1, (MAX_RGB + 1) >> 1, (MAX_RGB + 1) >> 1, null, (byte) 0);\r
+\r
+ root.parent = root;\r
+ root.numberColors = Integer.MAX_VALUE;\r
+ }\r
+\r
+ public void quantize(int pixels[], int width, int maxColors) {\r
+\r
+ depth = 1;\r
+ for (int mc = maxColors; mc != 0; mc >>= 2, ++depth)\r
+ ;\r
+\r
+ int numberPixels = pixels.length;\r
+ int maxShift;\r
+\r
+ for (maxShift = 4 * 8; numberPixels != 0; maxShift--)\r
+ numberPixels >>= 1;\r
+\r
+ for (int level = 0; level <= depth; level++) {\r
+ shift[level] = maxShift;\r
+ if (maxShift != 0)\r
+ maxShift--;\r
+ }\r
+\r
+ sortInImage(pixels);\r
+\r
+ reduction(maxColors);\r
+ assignment(pixels, maxColors);\r
+ }\r
+\r
+ void sortInImage(int pixels[]) {\r
+\r
+ int r, g, b, color;\r
+ for (int i = 0; i < pixels.length; ++i) {\r
+ color = pixels[i];\r
+ r = (color >> 16) & 0xff;\r
+ g = (color >> 8) & 0xff;\r
+ b = color & 0xff;\r
+ sortInRGB(r, g, b);\r
+ }\r
+ }\r
+\r
+ void sortInRGB(int red, int green, int blue) {\r
+\r
+ // prune one level if tree is too large\r
+ if (nodes > MAX_NODES) {\r
+ pruneLevel(root);\r
+ --depth;\r
+ }\r
+\r
+ // descent the tree, start with root\r
+ Node node = root;\r
+\r
+ for (int level = 1; level <= depth; ++level) {\r
+\r
+ int id = (red > node.midRed ? 1 : 0) | (green > node.midGreen ? 2 : 0)\r
+ | (blue > node.midBlue ? 4 : 0);\r
+\r
+ // was this branch visited before ?\r
+ if (node.children[id] == null) {\r
+ int bisect = (1 << (MAX_TREE_DEPTH - level)) >> 1;\r
+ Node n = new Node(node.midRed + ((id & 1) != 0 ? bisect : -bisect), node.midGreen\r
+ + ((id & 2) != 0 ? bisect : -bisect), node.midBlue\r
+ + ((id & 4) != 0 ? bisect : -bisect), node, (byte) id);\r
+ ++nodes;\r
+ node.census |= 1 << id; // register new child\r
+ node.children[id] = n;\r
+\r
+ if (level == depth)\r
+ ++colors;\r
+ }\r
+\r
+ node = node.children[id]; // descent to next level\r
+ node.numberColors += 1 << shift[level];\r
+ }\r
+\r
+ ++node.numberUnique;\r
+ node.totalRed += red;\r
+ node.totalGreen += green;\r
+ node.totalBlue += blue;\r
+ }\r
+\r
+ void pruneLevel(Node node) {\r
+\r
+ if (node.census != 0)\r
+ for (int i = 0; i < node.children.length; ++i)\r
+ if ((node.census & (1 << i)) != 0)\r
+ pruneLevel(node.children[i]);\r
+\r
+ if (node.level == depth)\r
+ pruneChild(node);\r
+ }\r
+\r
+ void reduction(int numberColors) {\r
+\r
+ nextPruningTreshold = 1;\r
+\r
+ while (colors > numberColors) {\r
+ pruningTreshold = nextPruningTreshold;\r
+ nextPruningTreshold = root.numberColors;\r
+ colors = 0;\r
+ reduce(root);\r
+ }\r
+ }\r
+\r
+ void reduce(Node node) {\r
+\r
+ if (node.census != 0)\r
+ for (int i = 0; i < node.children.length; ++i)\r
+ if ((node.census & (1 << i)) != 0)\r
+ reduce(node.children[i]);\r
+\r
+ if (node.numberColors <= pruningTreshold)\r
+ pruneChild(node);\r
+ else {\r
+ if (node.numberUnique > 0)\r
+ ++colors;\r
+ if (node.numberColors < nextPruningTreshold)\r
+ nextPruningTreshold = node.numberColors;\r
+ }\r
+ }\r
+\r
+ void pruneChild(Node node) {\r
+\r
+ Node parent = node.parent;\r
+\r
+ // parent.children[node.id] = null;\r
+ parent.census &= ~(1 << node.id);\r
+ parent.numberUnique += node.numberUnique;\r
+ parent.totalRed += node.totalRed;\r
+ parent.totalGreen += node.totalGreen;\r
+ parent.totalBlue += node.totalBlue;\r
+ --nodes;\r
+ }\r
+\r
+ void assignment(int pixels[], int maxColors) {\r
+\r
+ colormap = new int[maxColors];\r
+ colors = 0;\r
+ colorMap(root);\r
+\r
+ int r, g, b, color;\r
+\r
+ for (int i = 0; i < pixels.length; ++i) {\r
+ color = pixels[i];\r
+ r = (color >> 16) & 0xff;\r
+ g = (color >> 8) & 0xff;\r
+ b = color & 0xff;\r
+ pixels[i] = colormap[rgb2idx(r, g, b)];\r
+ }\r
+ }\r
+\r
+ void colorMap(Node node) {\r
+\r
+ if (node.census != 0)\r
+ for (int i = 0; i < node.children.length; ++i)\r
+ if ((node.census & (1 << i)) != 0)\r
+ colorMap(node.children[i]);\r
+\r
+ if (node.numberUnique != 0) {\r
+ int uh = node.numberUnique >> 1;\r
+ int r = (node.totalRed + uh) / node.numberUnique;\r
+ int g = (node.totalGreen + uh) / node.numberUnique;\r
+ int b = (node.totalBlue + uh) / node.numberUnique;\r
+\r
+ node.colorNumber = colors;\r
+ colormap[colors++] = (r << 16) | (g << 8) | b;\r
+ }\r
+ }\r
+\r
+ int rgb2idx(int red, int green, int blue) {\r
+\r
+ Node node = root;\r
+\r
+ for (;;) {\r
+ byte id = (byte) ((red > node.midRed ? 1 : 0) | (green > node.midGreen ? 2 : 0) | (blue > node.midBlue ? 4\r
+ : 0));\r
+\r
+ if ((node.census & (1 << id)) == 0)\r
+ break;\r
+\r
+ node = node.children[id];\r
+ }\r
+\r
+ cdistance = Integer.MAX_VALUE;\r
+ cred = red;\r
+ cgreen = green;\r
+ cblue = blue;\r
+\r
+ closestColor(node.parent);\r
+\r
+ return colorNumber;\r
+ }\r
+\r
+ void closestColor(Node node) {\r
+ if (node.census != 0)\r
+ for (int i = 0; i < node.children.length; i++)\r
+ if ((node.census & (1 << i)) != 0)\r
+ closestColor(node.children[i]);\r
+\r
+ if (node.numberUnique != 0) {\r
+\r
+ int color = colormap[node.colorNumber];\r
+ int r = (color >> 16) & 0xff;\r
+ int g = (color >> 8) & 0xff;\r
+ int b = color & 0xff;\r
+\r
+ int rD = r - cred + MAX_RGB;\r
+ int gD = g - cgreen + MAX_RGB;\r
+ int bD = b - cblue + MAX_RGB;\r
+\r
+ int distance = SQUARES[rD] + SQUARES[gD] + SQUARES[bD];\r
+\r
+ if (distance < cdistance) {\r
+ cdistance = distance;\r
+ colorNumber = node.colorNumber;\r
+ }\r
+ }\r
+ }\r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.tools;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.string.StringUtil2;\r
+import nl.weeaboo.system.ProcessUtil;\r
+import nl.weeaboo.vnds.BatchProcess;\r
+import nl.weeaboo.vnds.FileOp;\r
+import nl.weeaboo.vnds.ProgressListener;\r
+\r
+public class SoundConverter {\r
+\r
+ public enum ConvertType {\r
+ TYPE_AAC("AAC"), TYPE_ADPCM("ADPCM"), TYPE_MP3("MP3"), TYPE_OGG("OGG");\r
+ \r
+ private String label;\r
+ \r
+ private ConvertType(String label) {\r
+ this.label = label;\r
+ }\r
+ \r
+ public String toString() { return label; }\r
+ \r
+ public static ConvertType fromExt(String string) {\r
+ string = string.toLowerCase();\r
+ if (string.equals("aac")) {\r
+ return TYPE_AAC;\r
+ } else if (string.equals("adpcm")) {\r
+ return TYPE_ADPCM;\r
+ } else if (string.equals("mp3")) {\r
+ return TYPE_MP3;\r
+ } else if (string.equals("ogg")) {\r
+ return TYPE_OGG;\r
+ }\r
+ \r
+ throw new IllegalArgumentException("Unsupported conversion type: " + string);\r
+ }\r
+ }\r
+ \r
+ public static final int AAC_Q_LOW = 35;\r
+ public static final int AAC_Q_MED = 50;\r
+ public static final int AAC_Q_HIGH = 70;\r
+ \r
+ public static final int VORBIS_Q_LOW = 0;\r
+ public static final int VORBIS_Q_MED = 2;\r
+ public static final int VORBIS_Q_HIGH = 4;\r
+ \r
+ private String workingDir = "tools/";\r
+ private int maxThreads = 8;\r
+ private ConvertType mode = ConvertType.TYPE_AAC;\r
+ private int aac_quality = AAC_Q_HIGH;\r
+ private int mp3_minb = 8, mp3_maxb = 128, mp3_avgb = 96;\r
+ private int vorbis_quality = VORBIS_Q_MED;\r
+ private int volume = 100; \r
+ private boolean nameToUpperCase;\r
+ private boolean log;\r
+ \r
+ public SoundConverter() {\r
+ }\r
+ \r
+ // Functions\r
+ public static void main(String args[]) throws IOException {\r
+ SoundConverter ve = new SoundConverter();\r
+ \r
+ //Fate/Stay Night\r
+ //ve.convertFolder("foldername/");\r
+ \r
+ //Narcissu\r
+ //ve.setVolume(800);\r
+ //ve.setConvertNameToUpperCase(true);\r
+ //ve.convertFolder("foldername/", null);\r
+ \r
+ ve.setMode(ConvertType.TYPE_OGG);\r
+ ve.convertFile(new File("z:/temp.mp3"));\r
+ }\r
+ \r
+ public void convertFolder(String folder, final ProgressListener pl) {\r
+ Map<String, File> files = new HashMap<String, File>();\r
+ for (File file : new File(folder).listFiles()) {\r
+ if (!file.isDirectory()) files.put(file.getName(), file);\r
+ }\r
+ \r
+ BatchProcess bp = new BatchProcess();\r
+ bp.setTaskSize(32);\r
+ bp.setThreads(maxThreads);\r
+ bp.setThreadPriority(Thread.MIN_PRIORITY);\r
+ bp.addProgressListener(pl);\r
+ try {\r
+ bp.run(files, new FileOp() {\r
+ public void execute(String relpath, File file) throws IOException {\r
+ convertFile(file);\r
+ }\r
+ });\r
+ } catch (InterruptedException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ protected static void collectFiles(Set<File> files, File file, int depth) {\r
+ if (file.isDirectory()) {\r
+ if (depth == 0 && !file.getName().equals("special")) {\r
+ for (File f : file.listFiles()) {\r
+ collectFiles(files, f, depth + 1);\r
+ }\r
+ }\r
+ } else if (!file.getName().endsWith(".aac")) {\r
+ files.add(file);\r
+ }\r
+ }\r
+ \r
+ protected static File[] createTempFiles(File file, File tempFolder, String... exts)\r
+ throws IOException\r
+ {\r
+ File[] result = new File[exts.length + 1];\r
+ \r
+ String path = file.getAbsolutePath();\r
+\r
+ //Check if entire path consists of ASCII characters\r
+ boolean ascii = true;\r
+ for (int n = 0; n < path.length(); n++) {\r
+ if (path.charAt(n) > 127) {\r
+ ascii = false;\r
+ break;\r
+ }\r
+ }\r
+ \r
+ if (ascii) {\r
+ result[0] = file;\r
+ } else { \r
+ File dst = null;\r
+ String pre = String.format("temp-" + Thread.currentThread().hashCode());\r
+ String post = StringUtil.getExtension(file.getName()).toLowerCase();\r
+ while (dst == null || dst.exists()) {\r
+ dst = new File(tempFolder, pre + "-" + StringUtil2.getRandomString(4) + "." + post);\r
+ }\r
+ FileUtil.copyFile(file, dst);\r
+ result[0] = dst;\r
+ }\r
+ \r
+ for (int n = 0; n < exts.length; n++) {\r
+ String p = StringUtil.replaceExt(result[n].getAbsolutePath(), exts[n]);\r
+ File f = new File(p);\r
+\r
+ int t = 1;\r
+ while (f.exists()) {\r
+ t++;\r
+ f = new File(StringUtil.stripExtension(p) + "-" + t + "." + StringUtil.getExtension(p)); \r
+ }\r
+ result[n+1] = f;\r
+ }\r
+ \r
+ return result;\r
+ }\r
+ \r
+ public void convertFile(File file) throws IOException {\r
+ convertFile(file, mode, null);\r
+ }\r
+ public File convertFile(File srcF, File dstFolder) throws IOException {\r
+ return convertFile(srcF, mode, dstFolder);\r
+ }\r
+ public File convertFile(File srcF, ConvertType mode, File dstFolder) throws IOException {\r
+ if (dstFolder == null) {\r
+ dstFolder = srcF.getParentFile();\r
+ } \r
+ dstFolder.mkdirs();\r
+\r
+ String filters = "";\r
+ if (volume != 100) {\r
+ filters += " -vol " + volume;\r
+ } \r
+ \r
+ File[] temp;\r
+ String[] cmds;\r
+ switch (mode) {\r
+ case TYPE_AAC:\r
+ temp = createTempFiles(srcF, dstFolder, "wav", "aac");\r
+ \r
+ cmds = new String[] {\r
+ String.format("ffmpeg -y -i \"%s\" -vn -ac 1 -ar 22050 %s \"%s\"",\r
+ temp[0], filters, temp[1]),\r
+ String.format("faac -q %d -o \"%s\" \"%s\"",\r
+ aac_quality, temp[2], temp[1])\r
+ };\r
+ break;\r
+ case TYPE_MP3:\r
+ temp = createTempFiles(srcF, dstFolder, "wav", "mp3");\r
+ \r
+ String bitrateFlags = String.format("--abr %d -b %d -B %d",\r
+ mp3_avgb, mp3_minb, mp3_maxb);\r
+ \r
+ cmds = new String[] {\r
+ String.format("ffmpeg -y -i \"%s\" -vn -ac 2 -ar 32000 %s \"%s\"",\r
+ temp[0], filters, temp[1]),\r
+ String.format("lame --resample 32 %s \"%s\" \"%s\"",\r
+ bitrateFlags, temp[1], temp[2])\r
+ };\r
+ break;\r
+ case TYPE_ADPCM:\r
+ temp = createTempFiles(srcF, dstFolder, "wav", "wav.raw");\r
+\r
+ cmds = new String[] {\r
+ String.format("ffmpeg -y -i \"%s\" -vn -acodec adpcm_ima_wav -ac 1 -ar 22050 %s \"%s\"",\r
+ temp[0], filters, temp[1]),\r
+ String.format("ima2raw \"%s\"",\r
+ temp[1])\r
+ };\r
+ break;\r
+ case TYPE_OGG:\r
+ temp = createTempFiles(srcF, dstFolder, "ogg");\r
+ \r
+ cmds = new String[] {\r
+ String.format("ffmpeg -y -i \"%s\" -vn -acodec libvorbis -aq %d %s \"%s\"",\r
+ temp[0], vorbis_quality, filters, temp[1])\r
+ };\r
+ break;\r
+ default:\r
+ throw new IllegalArgumentException("Illegal mode: " + mode);\r
+ }\r
+ \r
+ File dstF = new File(dstFolder, StringUtil.replaceExt(srcF.getName(),\r
+ StringUtil.getExtension(temp[temp.length-1].getName()).toLowerCase()));\r
+ try {\r
+ for (String cmd : cmds) {\r
+ Process p = ProcessUtil.execInDir(cmd, workingDir);\r
+ //System.out.println(ProcessUtil.read(p));\r
+ ProcessUtil.waitFor(p);\r
+ } \r
+ } finally {\r
+ //Rename final result to dst\r
+ if (temp[temp.length-1].exists()) {\r
+ temp[temp.length-1].renameTo(dstF);\r
+ }\r
+ \r
+ //Delete temp files\r
+ for (File file : temp) {\r
+ if (!file.equals(srcF) && !file.equals(dstF)) {\r
+ file.delete();\r
+ }\r
+ }\r
+ }\r
+ \r
+ return dstF;\r
+ }\r
+ \r
+ // Getters\r
+ public boolean isLogging() { return log; }\r
+ public ConvertType getMode() { return mode; }\r
+ public int getAacQuality() { return aac_quality; }\r
+ public int getVorbisQuality() { return vorbis_quality; }\r
+ public int getMp3MinBitrate() { return mp3_minb; }\r
+ public int getMp3MaxBitrate() { return mp3_maxb; }\r
+ public int getMp3AvgBitrate() { return mp3_avgb; }\r
+ public int getVolume() { return volume; }\r
+ public boolean getConvertNameToUpperCase() { return nameToUpperCase; }\r
+ public int getMaxThreads() { return maxThreads; }\r
+ public String getWorkingDir() { return workingDir; }\r
+ \r
+ // Setters\r
+ public void setMode(ConvertType mode) { this.mode = mode; }\r
+ public void setVorbisQuality(int quality) { this.vorbis_quality = quality; }\r
+ public void setAacQuality(int quality) { this.aac_quality = quality; }\r
+ public void setMp3Quality(int min, int max, int avg) {\r
+ mp3_minb = min;\r
+ mp3_maxb = max;\r
+ mp3_avgb = avg;\r
+ }\r
+ public void setVolume(int volume) { this.volume = volume; }\r
+ public void setConvertNameToUpperCase(boolean nameToUpperCase) { this.nameToUpperCase = nameToUpperCase; }\r
+ public void setMaxThreads(int mt) { this.maxThreads = mt; }\r
+ public void setWorkingDir(String dir) { this.workingDir = dir; }\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.tools;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Dimension;\r
+import java.awt.GridLayout;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.io.File;\r
+\r
+import javax.swing.JButton;\r
+import javax.swing.JCheckBox;\r
+import javax.swing.JComboBox;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JOptionPane;\r
+import javax.swing.JPanel;\r
+import javax.swing.JSpinner;\r
+import javax.swing.SpinnerNumberModel;\r
+import javax.swing.border.EmptyBorder;\r
+\r
+import nl.weeaboo.awt.AwtUtil;\r
+import nl.weeaboo.awt.FileBrowseField;\r
+import nl.weeaboo.awt.Sash;\r
+import nl.weeaboo.vnds.ProgressListener;\r
+import nl.weeaboo.vnds.ProgressRunnable;\r
+import nl.weeaboo.vnds.VNDSProgressDialog;\r
+import nl.weeaboo.vnds.tools.SoundConverter.ConvertType;\r
+\r
+/*\r
+ * Changes:\r
+ *\r
+ * 2009/11/08 -- v1.4.2\r
+ * - Code cleanup\r
+ * - Converts input files even if they're already the correct file type\r
+ *\r
+ * 2008/03/03 -- v1.4.1\r
+ * - Added support for changing MP3 quality\r
+ *\r
+ * 2008/10/11 -- v1.4\r
+ * - Added MP3 support\r
+ * - Added a support for changing the number of threads used\r
+ *\r
+ * 2008/09/13 -- v1.3\r
+ * - Bug sometimes caused end of soundfile to become corrupted\r
+ * \r
+ * 2008/07/25 -- v1.2\r
+ * - Logging was broken, now fixed\r
+ * - Filenames with spaces in them weren't handled properly\r
+ * \r
+ * 2008/07/24 -- v1.1\r
+ * - ADPCM Support\r
+ * - Logging function\r
+ * \r
+ * 2008/06/21 -- v1.0\r
+ * - Initial Release\r
+ * \r
+ */\r
+@SuppressWarnings("serial")\r
+public class SoundConverterGUI extends JFrame {\r
+\r
+ private SoundConverter encoder;\r
+ \r
+ private JComboBox modeCombo;\r
+ private JSpinner aacQualitySpinner;\r
+ private JSpinner mp3QualitySpinner;\r
+ private JSpinner volumeSpinner;\r
+ private JCheckBox toUpperCheck;\r
+ private JSpinner threadsSpinner;\r
+ private FileBrowseField browseField;\r
+ private JButton convertButton;\r
+ \r
+ public SoundConverterGUI() {\r
+ encoder = new SoundConverter();\r
+ \r
+ setTitle("VNDS Sound Converter v1.4.2");\r
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);\r
+ \r
+ setLayout(new BorderLayout());\r
+ add(createCenterPanel(), BorderLayout.CENTER);\r
+ \r
+ reset();\r
+ \r
+ setMinimumSize(new Dimension(300, 100));\r
+ pack();\r
+ setLocationRelativeTo(null);\r
+ setVisible(true);\r
+ }\r
+ \r
+ //Functions\r
+ public static void main(String args[]) {\r
+ AwtUtil.setDefaultLAF();\r
+ \r
+ new SoundConverterGUI();\r
+ }\r
+ \r
+ protected JPanel createCenterPanel() {\r
+ JLabel modeLabel = new JLabel("Output Type");\r
+ modeCombo = new JComboBox(new Object[] {ConvertType.TYPE_AAC, ConvertType.TYPE_ADPCM,\r
+ ConvertType.TYPE_MP3});\r
+ modeCombo.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent e) {\r
+ ConvertType mode = (ConvertType)modeCombo.getSelectedItem();\r
+ encoder.setMode(mode);\r
+ aacQualitySpinner.setEnabled(mode == ConvertType.TYPE_AAC);\r
+ mp3QualitySpinner.setEnabled(mode == ConvertType.TYPE_MP3);\r
+ }\r
+ });\r
+ \r
+ JLabel aacQualityLabel = new JLabel("AAC Quality");\r
+ aacQualitySpinner = new JSpinner(new SpinnerNumberModel(70, 5, 995, 5));\r
+ JLabel mp3QualityLabel = new JLabel("MP3 Bitrate");\r
+ mp3QualitySpinner = new JSpinner(new SpinnerNumberModel(96, 8, 128, 16));\r
+ JLabel volumeLabel = new JLabel("Volume (%)");\r
+ volumeSpinner = new JSpinner(new SpinnerNumberModel(100, 10, 990, 10)); \r
+ JLabel toUpperLabel = new JLabel("Filenames to uppercase?");\r
+ toUpperCheck = new JCheckBox();\r
+ JLabel threadsLabel = new JLabel("Threads");\r
+ threadsSpinner = new JSpinner(new SpinnerNumberModel(8, 1, 128, 1));\r
+ \r
+ JPanel panel = new JPanel(new GridLayout(-1, 2, 15, 5));\r
+ panel.add(modeLabel); panel.add(modeCombo);\r
+ panel.add(aacQualityLabel); panel.add(aacQualitySpinner);\r
+ panel.add(mp3QualityLabel); panel.add(mp3QualitySpinner);\r
+ panel.add(volumeLabel); panel.add(volumeSpinner);\r
+ panel.add(toUpperLabel); panel.add(toUpperCheck);\r
+ panel.add(threadsLabel); panel.add(threadsSpinner);\r
+ \r
+ browseField = FileBrowseField.readFolder("Folder", new File(""));\r
+ convertButton = new JButton("Convert");\r
+ convertButton.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent e) {\r
+ convert();\r
+ }\r
+ });\r
+ \r
+ JPanel bottomPanel = new JPanel(new BorderLayout(10, 10));\r
+ bottomPanel.add(new Sash(Sash.HORIZONTAL), BorderLayout.NORTH);\r
+ bottomPanel.add(browseField, BorderLayout.CENTER);\r
+ bottomPanel.add(convertButton, BorderLayout.EAST);\r
+ \r
+ JPanel panel2 = new JPanel(new BorderLayout(0, 10));\r
+ panel2.setBorder(new EmptyBorder(10, 10, 10, 10));\r
+ panel2.add(panel, BorderLayout.NORTH);\r
+ panel2.add(bottomPanel, BorderLayout.SOUTH);\r
+ return panel2;\r
+ }\r
+ \r
+ public void convert() {\r
+ final File folder = browseField.getFile();\r
+ if (!folder.exists() || !folder.isDirectory()) {\r
+ JOptionPane.showMessageDialog(null, "Invalid directory: \"" + folder + "\"",\r
+ "Error", JOptionPane.ERROR_MESSAGE);\r
+ return;\r
+ }\r
+ \r
+ int mp3_avgb = ((Integer)aacQualitySpinner.getValue()) / 16 * 16; //round down to nearest 16\r
+ int mp3_minb = encoder.getMp3MinBitrate();\r
+ int mp3_maxb = ((mp3_avgb+15) / 16) * 16; //round up to nearest 16\r
+ \r
+ mp3_avgb = Math.min(128, mp3_avgb);\r
+ mp3_maxb = Math.min(128, mp3_maxb);\r
+ \r
+ encoder.setAacQuality((Integer)aacQualitySpinner.getValue());\r
+ encoder.setMp3Quality(mp3_minb, mp3_maxb, mp3_avgb);\r
+ encoder.setVolume((Integer)volumeSpinner.getValue());\r
+ encoder.setConvertNameToUpperCase(toUpperCheck.isSelected());\r
+ encoder.setMaxThreads((Integer)threadsSpinner.getValue());\r
+ \r
+ ProgressListener pl = new ProgressListener() {\r
+ public void onFinished(String message) {\r
+ JOptionPane.showMessageDialog(null, String.format(\r
+ "<html>Conversion finished.</html>"),\r
+ "Finished", JOptionPane.PLAIN_MESSAGE);\r
+ }\r
+ public void onProgress(int value, int max, String message) {\r
+ }\r
+ };\r
+ \r
+ ProgressRunnable task = new ProgressRunnable() {\r
+ public void run(ProgressListener pl) {\r
+ encoder.convertFolder(folder.toString(), pl);\r
+ }\r
+ };\r
+ \r
+ VNDSProgressDialog dialog = new VNDSProgressDialog();\r
+ dialog.showDialog(task, pl);\r
+ }\r
+ \r
+ public void reset() {\r
+ aacQualitySpinner.setValue(encoder.getAacQuality());\r
+ mp3QualitySpinner.setValue(encoder.getMp3AvgBitrate());\r
+ volumeSpinner.setValue(encoder.getVolume());\r
+ toUpperCheck.setSelected(encoder.getConvertNameToUpperCase());\r
+ threadsSpinner.setValue(encoder.getMaxThreads());\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.tools;\r
+\r
+import java.awt.Image;\r
+import java.awt.image.BufferedImage;\r
+import java.io.BufferedOutputStream;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileNotFoundException;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.nio.ByteBuffer;\r
+import java.nio.ByteOrder;\r
+import java.nio.channels.FileChannel.MapMode;\r
+import java.util.Comparator;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.TreeSet;\r
+\r
+import javax.imageio.ImageIO;\r
+\r
+import nl.weeaboo.collections.Tuple2;\r
+import nl.weeaboo.common.StringUtil;\r
+import nl.weeaboo.io.FileUtil;\r
+import nl.weeaboo.system.ProcessUtil;\r
+import nl.weeaboo.vnds.BatchProcess;\r
+import nl.weeaboo.vnds.FileOp;\r
+import nl.weeaboo.vnds.ProgressListener;\r
+\r
+public class TextureConverter {\r
+ \r
+ /*\r
+ * Formats:\r
+ * [1] A3I5\r
+ * [2] --\r
+ * [3] --\r
+ * [4] RGB256\r
+ * [5] --\r
+ * [6] A5I3\r
+ * [7] RGBA\r
+ */\r
+\r
+ public enum Mode {\r
+ A3I5(1, "A3I5"),\r
+ RGB256(4, "RGB256"),\r
+ A5I3(6, "A5I3"),\r
+ RGBA(7, "RGBA");\r
+ \r
+ private int num;\r
+ private String label;\r
+ \r
+ private Mode(int i, String l) {\r
+ num = i;\r
+ label = l;\r
+ }\r
+ \r
+ public static Mode fromInt(int i) {\r
+ for (Mode m : Mode.values()) {\r
+ if (m.num == i) return m;\r
+ }\r
+ return null;\r
+ }\r
+ public String toString() {\r
+ return num + ". " + label;\r
+ }\r
+ }\r
+ \r
+ public enum Quant {\r
+ NEUQUANT, OCTREE;\r
+ }\r
+ \r
+ private Mode mode = Mode.RGB256;\r
+ private Quant quant = Quant.OCTREE;\r
+ private boolean dithering = true;\r
+ private int threads = Runtime.getRuntime().availableProcessors();\r
+ \r
+ //Functions\r
+ protected static void printUsage() {\r
+ System.err.printf(\r
+ "Usage: java -jar TextureConverter.jar <mode> <flags> file [out-folder]\n"\r
+ + "\n\tmode:"\r
+ + "\n\t-1 => A3I5"\r
+ + "\n\t-4 => RGB256"\r
+ + "\n\t-6 => A5I3"\r
+ + "\n\t-7 => RGBA"\r
+ + "\n\tflags:"\r
+ + "\n\t-nodither => No dithering"\r
+ + "\n\t-quant <octree|neuquant>"\r
+ + "\n\t-threads <num>"\r
+ + "\n"); \r
+ }\r
+ \r
+ public static void main(String args[]) { \r
+ TextureConverter tc = new TextureConverter();\r
+ String filename = null;\r
+ String folder = null;\r
+ \r
+ try {\r
+ tc.setMode(Mode.fromInt(Integer.parseInt(args[0].substring(1))));\r
+ \r
+ for (int n = 1; n < args.length; n++) {\r
+ if (args[n].startsWith("-nodither")) {\r
+ tc.setDithering(false);\r
+ } else if (args[n].startsWith("-quant")) {\r
+ if (!args[++n].equals("neuquant")) {\r
+ tc.setQuant(Quant.NEUQUANT);\r
+ } else {\r
+ tc.setQuant(Quant.OCTREE);\r
+ }\r
+ } else if (args[n].startsWith("-threads")) {\r
+ tc.setThreads(Integer.parseInt(args[++n]));\r
+ } else if (filename == null) {\r
+ filename = args[n];\r
+ } else if (folder == null) {\r
+ folder = args[n];\r
+ } else {\r
+ throw new IllegalArgumentException("Error parsing arg: " + args[n]);\r
+ }\r
+ }\r
+ } catch (RuntimeException re) {\r
+ printUsage();\r
+ return;\r
+ }\r
+ if (filename == null) {\r
+ printUsage();\r
+ return;\r
+ }\r
+ \r
+ if (folder == null) {\r
+ folder = filename;\r
+ }\r
+ \r
+ try { \r
+ tc.convertFile(filename, folder);\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ } \r
+ }\r
+ \r
+ public void convertFolder(String folder, ProgressListener pl) throws IOException {\r
+ Map<String, File> files = new HashMap<String, File>();\r
+ final File folderF = new File(folder).getCanonicalFile();\r
+ if (folderF.isDirectory()) {\r
+ for (File file : folderF.listFiles()) {\r
+ if (!file.isDirectory()) files.put(file.getName(), file);\r
+ }\r
+ } else {\r
+ files.put(folderF.getName(), folderF);\r
+ }\r
+ \r
+ BatchProcess bp = new BatchProcess();\r
+ bp.setTaskSize(32);\r
+ bp.setThreads(threads);\r
+ bp.setThreadPriority(Thread.MIN_PRIORITY);\r
+ bp.addProgressListener(pl);\r
+ try {\r
+ bp.run(files, new FileOp() {\r
+ public void execute(String relpath, File file) throws IOException {\r
+ convertFile(file.getAbsolutePath(), folderF.getAbsolutePath());\r
+ }\r
+ });\r
+ } catch (InterruptedException e) {\r
+ e.printStackTrace();\r
+ } \r
+ }\r
+ \r
+ public void convertFile(File file) throws IOException {\r
+ convertFile(file.getAbsolutePath(), file.getParent());\r
+ }\r
+ public void convertFile(String src, String dst) throws IOException {\r
+ File srcF = new File(src); \r
+ File dstF = new File(dst);\r
+ \r
+ if (srcF == null || !srcF.exists() || !srcF.isFile() || !srcF.canRead()) {\r
+ throw new FileNotFoundException(srcF.getPath());\r
+ }\r
+ \r
+ if (dstF.exists() && !dstF.isDirectory()) {\r
+ dstF = dstF.getParentFile();\r
+ } else {\r
+ dst = dstF.getAbsolutePath()+File.separator+srcF.getName();\r
+ }\r
+ dstF.mkdirs();\r
+ \r
+ File temp = File.createTempFile("_tc" + Thread.currentThread().hashCode(), ".png", dstF); \r
+ try {\r
+ FileUtil.copyFile(srcF, temp);\r
+ String tempPath = temp.getAbsolutePath();\r
+ \r
+ if (mode == Mode.A3I5) {\r
+ quantize(tempPath, 32);\r
+ convertToPalette(tempPath, 3, 5);\r
+ } else if (mode == Mode.RGB256) {\r
+ quantize(tempPath, 256);\r
+ convertToPalette(tempPath, 0, 8);\r
+ } else if (mode == Mode.A5I3) {\r
+ quantize(tempPath, 8);\r
+ convertToPalette(tempPath, 5, 3);\r
+ } else if (mode == Mode.RGBA) {\r
+ quantize(tempPath, 256);\r
+ convertToRAW(tempPath);\r
+ } else {\r
+ throw new IllegalArgumentException("Invalid mode: " + mode);\r
+ }\r
+ \r
+ String tempBase = StringUtil.stripExtension(tempPath);\r
+ String dstBase = StringUtil.stripExtension(dst);\r
+ \r
+ //Rename to dst names\r
+ \r
+ File dstDTA = new File(dstBase+".dta");\r
+ File dstPAL = new File(dstBase+".pal");\r
+ File tempDTA = new File(tempBase+".dta");\r
+ File tempPAL = new File(tempBase+".pal");\r
+\r
+ dstDTA.delete();\r
+ tempDTA.renameTo(dstDTA);\r
+ tempDTA.delete();\r
+\r
+ dstPAL.delete();\r
+ tempPAL.renameTo(dstPAL);\r
+ tempPAL.delete();\r
+ } finally {\r
+ temp.delete();\r
+ }\r
+ }\r
+ \r
+ protected void quantize(String path, int numColors) throws IOException {\r
+ File file = new File(path).getCanonicalFile();\r
+ if (file == null || !file.exists() || !file.isFile() || !file.canRead()) {\r
+ throw new FileNotFoundException(path);\r
+ }\r
+ path = file.getAbsolutePath();\r
+ \r
+ //Dither to DS colors\r
+ BufferedImage image = ImageIO.read(file);\r
+ int iw = image.getWidth();\r
+ int ih = image.getHeight();\r
+ \r
+ int argb[] = image.getRGB(0, 0, iw, ih, null, 0, iw);\r
+ int alpha[] = new int[argb.length];\r
+ for (int n = 0; n < argb.length; n++) {\r
+ alpha[n] = argb[n] >>> 24;\r
+ argb[n] = 0xFF000000 | (argb[n] & 0xFFFFFF);\r
+ }\r
+ \r
+ if (dithering) {\r
+ floydSteinberg(argb, iw, ih);\r
+ }\r
+ \r
+ //Quantize to the number of colors we want\r
+ if (quant == Quant.NEUQUANT) {\r
+ image.setRGB(0, 0, iw, ih, argb, 0, iw);\r
+ ImageIO.write(image, "png", file);\r
+\r
+ Process p = ProcessUtil.execInDir(String.format(\r
+ "pngnq \"%s\" -n %d", path, numColors),\r
+ ".");\r
+ ProcessUtil.waitFor(p);\r
+ ProcessUtil.kill(p);\r
+ file.delete();\r
+ new File(StringUtil.stripExtension(path)+"-nq8.png").renameTo(file);\r
+\r
+ image = ImageIO.read(file);\r
+ image.getRGB(0, 0, image.getWidth(), image.getHeight(),\r
+ argb, 0, image.getWidth());\r
+ } else if (quant == Quant.OCTREE) {\r
+ Octree tree = new Octree();\r
+ tree.quantize(argb, iw, numColors);\r
+ }\r
+\r
+ //Re-add alpha channel\r
+ for (int n = 0; n < argb.length; n++) {\r
+ argb[n] = (argb[n] & 0xFFFFFF) | (alpha[n]<<24);\r
+ }\r
+ \r
+ image = new BufferedImage(iw, ih, BufferedImage.TYPE_INT_ARGB);\r
+ image.setRGB(0, 0, iw, ih, argb, 0, iw);\r
+ ImageIO.write(image, "png", file);\r
+ \r
+ //FileUtil.copyFile(file, new File(file.getParent()+"/temp2.png"));\r
+ }\r
+ \r
+ protected void convertToPalette(String path, int bitsA, int bitsC) throws IOException {\r
+ File src = new File(path); \r
+ BufferedImage image = ImageIO.read(src);\r
+ int argb[] = image.getRGB(0, 0, image.getWidth(), image.getHeight(),\r
+ null, 0, image.getWidth());\r
+ src.delete();\r
+\r
+ int maxA = (1<<bitsA) - 1;\r
+ int palette[] = createPalette(argb, (1<<bitsC));\r
+ \r
+ File dstDTA = new File(StringUtil.stripExtension(path)+".dta");\r
+ BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(dstDTA));\r
+ for (int c : argb) {\r
+ int alpha = (c>>24) & 0xFF;\r
+ int idx = lookupPalette(palette, A8R8G8B8_to_A1B5G5R5(c));\r
+ if (alpha == 0) idx = 0;\r
+ \r
+ c = ((Math.round(maxA * alpha / 255f) & maxA) << bitsC) | idx; \r
+ bout.write(c&0xFF);\r
+ }\r
+ bout.flush();\r
+ bout.close();\r
+\r
+ File dstPAL = new File(StringUtil.stripExtension(path)+".pal");\r
+ bout = new BufferedOutputStream(new FileOutputStream(dstPAL));\r
+ for (int c : palette) { \r
+ bout.write(c&0xFF);\r
+ bout.write((c>>8)&0xFF);\r
+ }\r
+ bout.flush();\r
+ bout.close();\r
+ }\r
+\r
+ \r
+ protected void convertToRAW(String path) throws IOException {\r
+ File src = new File(path); \r
+ BufferedImage image = ImageIO.read(src);\r
+ int argb[] = image.getRGB(0, 0, image.getWidth(), image.getHeight(),\r
+ null, 0, image.getWidth());\r
+ A8R8G8B8_to_A1B5G5R5(argb);\r
+ src.delete();\r
+ \r
+ File dst = new File(StringUtil.stripExtension(path)+".dta"); \r
+ BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(dst));\r
+ for (int c : argb) {\r
+ bout.write(c&0xFF);\r
+ bout.write((c>>8)&0xFF);\r
+ }\r
+ bout.flush();\r
+ bout.close();\r
+ }\r
+ \r
+ protected int A8R8G8B8_to_A1B5G5R5(int c) {\r
+ int a = ((c>>>24) >= 127 ? (1<<15) : 0);\r
+ int r = Math.round(((c>>16)&0xFF) / 8f);\r
+ r = Math.max(0, Math.min(31, r));\r
+ int g = Math.round(((c>>8)&0xFF) / 8f);\r
+ g = Math.max(0, Math.min(31, g));\r
+ int b = Math.round(((c)&0xFF) / 8f); \r
+ b = Math.max(0, Math.min(31, b));\r
+ return a | (b<<10) | (g<<5) | (r);\r
+ }\r
+ protected void A8R8G8B8_to_A1B5G5R5(int data[]) {\r
+ for (int n = 0; n < data.length; n++) {\r
+ data[n] = A8R8G8B8_to_A1B5G5R5(data[n]);\r
+ }\r
+ }\r
+ \r
+ protected int[] createPalette(int argb[], int maxC) {\r
+ int colorHistogram[] = new int[0x8000]; \r
+ for (int c : argb) {\r
+ colorHistogram[A8R8G8B8_to_A1B5G5R5(c) & 0x7FFF]++;\r
+ }\r
+ \r
+ TreeSet<Tuple2<Integer, Integer>> set = new TreeSet<Tuple2<Integer, Integer>>(\r
+ new Comparator<Tuple2<Integer, Integer>>() {\r
+ public int compare(Tuple2<Integer, Integer> t1, Tuple2<Integer, Integer> t2) {\r
+ int c = t1.y.compareTo(t2.y);\r
+ if (c == 0) c = t1.x.compareTo(t2.x);\r
+ return -c;\r
+ }\r
+ }\r
+ );\r
+ \r
+ for (int n = 0; n < colorHistogram.length; n++) {\r
+ if (colorHistogram[n] > 0) {\r
+ set.add(Tuple2.newTuple(n, colorHistogram[n]));\r
+ }\r
+ }\r
+ \r
+ int result[] = new int[maxC];\r
+ int t = 1;\r
+ for (Tuple2<Integer, Integer> tuple : set) {\r
+ if (t >= result.length) break;\r
+ \r
+ result[t++] = tuple.x;\r
+ }\r
+ \r
+ return result;\r
+ }\r
+ \r
+ protected int lookupPalette(int palette[], int c0) {\r
+ int b0 = (c0>>10) & 31;\r
+ int g0 = (c0>>5) & 31;\r
+ int r0 = (c0) & 31;\r
+ \r
+ int best = 0;\r
+ int bestDist = Integer.MAX_VALUE;\r
+ \r
+ for (int idx = 1; idx < palette.length; idx++) {\r
+ int c1 = palette[idx];\r
+ int b1 = (c1>>10) & 31;\r
+ int g1 = (c1>>5) & 31;\r
+ int r1 = (c1) & 31;\r
+ \r
+ int dist = Math.abs(r1-r0) + Math.abs(g1-g0) + Math.abs(b1-b0);\r
+ if (dist < bestDist) {\r
+ best = idx;\r
+ bestDist = dist;\r
+ }\r
+ }\r
+ \r
+ return best;\r
+ }\r
+ \r
+ /** Does dithering for 5-bit colors (equivalent to 8-bit / 8.0) */\r
+ private static void floydSteinberg(int rgb[], int w, int h) {\r
+ double rgba[][][] = new double[w][h][4];\r
+ int t = 0;\r
+ for (int y = 0; y < h; y++) {\r
+ for (int x = 0; x < w; x++) {\r
+ rgba[x][y][0] = ((rgb[t]>>>16)&0xFF);\r
+ rgba[x][y][1] = ((rgb[t]>>>8 )&0xFF);\r
+ rgba[x][y][2] = ((rgb[t] )&0xFF);\r
+ rgba[x][y][3] = ((rgb[t]>>>24)&0xFF);\r
+ \r
+ t++;\r
+ }\r
+ }\r
+\r
+ for (int x = 0; x < w; x++) {\r
+ for (int y = 0; y < h; y++) {\r
+ for (int c = 0; c < 3; c++) {\r
+ double oldc = rgba[x][y][c];\r
+ double newc = rgba[x][y][c] = Math.round(oldc / 8.0) * 8.0;\r
+ double error = oldc - newc;\r
+\r
+ if (x+1 < w) {\r
+ rgba[x+1][y][c] += (7.0 * error) / 16.0;\r
+ rgba[x+1][y][c] = Math.min(255.0, Math.max(0.0, rgba[x+1][y][c]));\r
+ }\r
+ if (y+1 < h) {\r
+ if (x-1 >= 0) {\r
+ rgba[x-1][y+1][c] += (3.0 * error) / 16.0;\r
+ rgba[x-1][y+1][c] = Math.min(255.0, Math.max(0.0, rgba[x-1][y+1][c]));\r
+ }\r
+ rgba[x][y+1][c] += (5.0 * error) / 16.0;\r
+ rgba[x][y+1][c] = Math.min(255.0, Math.max(0.0, rgba[x][y+1][c]));\r
+ if (x+1 < w) {\r
+ rgba[x+1][y+1][c] += (1.0 * error) / 16.0;\r
+ rgba[x+1][y+1][c] = Math.min(255.0, Math.max(0.0, rgba[x+1][y+1][c]));\r
+ }\r
+ }\r
+ } \r
+ }\r
+ }\r
+ \r
+ t = 0;\r
+ for (int y = 0; y < h; y++) {\r
+ for (int x = 0; x < w; x++) {\r
+ int a = Math.min(255, Math.max(0, (int)Math.round(rgba[x][y][3])));\r
+ int r = Math.min(255, Math.max(0, (int)Math.round(rgba[x][y][0])));\r
+ int g = Math.min(255, Math.max(0, (int)Math.round(rgba[x][y][1])));\r
+ int b = Math.min(255, Math.max(0, (int)Math.round(rgba[x][y][2])));\r
+ rgb[t] = (a<<24)|(r<<16)|(g<<8)|(b);\r
+ t++;\r
+ }\r
+ }\r
+ }\r
+ \r
+ public Image readImage(String origPath) throws IOException {\r
+ File origF = new File(origPath);\r
+ BufferedImage orig = ImageIO.read(origF);\r
+ int w = orig.getWidth();\r
+ int h = orig.getHeight();\r
+ \r
+ String base = StringUtil.stripExtension(origPath);\r
+\r
+ File convDTA = new File(base + ".dta");\r
+ FileInputStream dtaIn = new FileInputStream(convDTA);\r
+ \r
+ try {\r
+ ByteBuffer dtaBuf = dtaIn.getChannel().map(MapMode.READ_ONLY, 0, convDTA.length());\r
+ dtaBuf.order(ByteOrder.LITTLE_ENDIAN);\r
+ \r
+ int bpp = (mode == Mode.RGBA ? 2 : 1);\r
+ int pixels[] = new int[((int)convDTA.length()) / bpp];\r
+ for (int n = 0; n < pixels.length; n++) {\r
+ if (bpp == 2) pixels[n] = dtaBuf.getShort();\r
+ else pixels[n] = dtaBuf.get();\r
+ }\r
+ dtaIn.close();\r
+\r
+ if (mode != Mode.RGBA) {\r
+ File convPAL = new File(base + ".pal");\r
+ FileInputStream palIn = new FileInputStream(convPAL);\r
+ try { \r
+ ByteBuffer palBuf = palIn.getChannel().map(MapMode.READ_ONLY, 0, convPAL.length());\r
+ palBuf.order(ByteOrder.LITTLE_ENDIAN);\r
+ int palette[] = new int[((int)convPAL.length()) / 2];\r
+ for (int n = 0; n < palette.length; n++) {\r
+ palette[n] = palBuf.getShort();\r
+ }\r
+ \r
+ int colorMask = 0xFF;\r
+ if (mode == Mode.A3I5) colorMask = 31;\r
+ else if (mode == Mode.A5I3) colorMask = 7;\r
+ \r
+ for (int n = 0; n < pixels.length; n++) {\r
+ int c = pixels[n];\r
+ \r
+ int alpha = 255;\r
+ if (mode == Mode.A3I5) alpha = (c>>5)*32+16;\r
+ else if (mode == Mode.A5I3) alpha = (c>>3)*8+4;\r
+ else if (mode == Mode.RGB256) alpha = (c == 0 ? 0 : 255);\r
+ \r
+ c = palette[c & colorMask];\r
+ c = (((c&31)*8+4)<<16) | ((((c>>5)&31)*8+4)<<8) | (((c>>10)&31)*8+4);\r
+ pixels[n] = (alpha<<24) | c;\r
+ }\r
+ } finally {\r
+ palIn.close(); \r
+ }\r
+ } else {\r
+ for (int n = 0; n < pixels.length; n++) {\r
+ int c = pixels[n];\r
+ int alpha = (c>>15 != 0 ? 255 : 0);\r
+ c = (((c&31)*8+4)<<16) | ((((c>>5)&31)*8+4)<<8) | (((c>>10)&31)*8+4);\r
+ pixels[n] = (alpha<<24) | c;\r
+ }\r
+ }\r
+ \r
+ BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);\r
+ image.setRGB(0, 0, w, h, pixels, 0, w);\r
+ return image;\r
+ } finally {\r
+ dtaIn.close();\r
+ }\r
+ }\r
+ \r
+ //Getters\r
+ public Mode getMode() { return mode; }\r
+ public Quant getQuant() { return quant; }\r
+ public boolean isDithering() { return dithering; }\r
+ public int getThreads() { return threads; }\r
+ \r
+ //Setters\r
+ public void setMode(Mode mode) { this.mode = mode; }\r
+ public void setQuant(Quant quant) { this.quant = quant; }\r
+ public void setDithering(boolean dithering) { this.dithering = dithering; }\r
+ public void setThreads(int threads) { this.threads = threads; }\r
+ \r
+}\r
--- /dev/null
+package nl.weeaboo.vnds.tools;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Dimension;\r
+import java.awt.GridLayout;\r
+import java.awt.Image;\r
+import java.awt.datatransfer.DataFlavor;\r
+import java.awt.datatransfer.Transferable;\r
+import java.awt.datatransfer.UnsupportedFlavorException;\r
+import java.awt.dnd.DnDConstants;\r
+import java.awt.dnd.DropTarget;\r
+import java.awt.dnd.DropTargetDragEvent;\r
+import java.awt.dnd.DropTargetDropEvent;\r
+import java.awt.dnd.DropTargetEvent;\r
+import java.awt.dnd.DropTargetListener;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.List;\r
+\r
+import javax.swing.ImageIcon;\r
+import javax.swing.JButton;\r
+import javax.swing.JCheckBox;\r
+import javax.swing.JComboBox;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JOptionPane;\r
+import javax.swing.JPanel;\r
+import javax.swing.JSpinner;\r
+import javax.swing.SpinnerNumberModel;\r
+import javax.swing.border.EmptyBorder;\r
+\r
+import nl.weeaboo.awt.AwtUtil;\r
+import nl.weeaboo.awt.FileBrowseField;\r
+import nl.weeaboo.awt.Sash;\r
+import nl.weeaboo.vnds.Log;\r
+import nl.weeaboo.vnds.ProgressListener;\r
+import nl.weeaboo.vnds.ProgressRunnable;\r
+import nl.weeaboo.vnds.VNDSProgressDialog;\r
+import nl.weeaboo.vnds.tools.TextureConverter.Mode;\r
+\r
+/*\r
+ * Changes:\r
+ *\r
+ * 2009/11/?? -- v1.0\r
+ * - Initial release\r
+ * \r
+ */\r
+@SuppressWarnings("serial")\r
+public class TextureConverterGUI extends JFrame {\r
+\r
+ private TextureConverter converter;\r
+ \r
+ private JComboBox modeCombo;\r
+ private JCheckBox ditheringCheck;\r
+ private JSpinner threadsSpinner;\r
+ private FileBrowseField browseField;\r
+ private JButton convertButton;\r
+ \r
+ public TextureConverterGUI() {\r
+ converter = new TextureConverter();\r
+ \r
+ setTitle("NDS Texture Converter v1.0");\r
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);\r
+ \r
+ setLayout(new BorderLayout());\r
+ add(createCenterPanel(), BorderLayout.CENTER);\r
+ \r
+ setMinimumSize(new Dimension(300, 100));\r
+ pack();\r
+ setLocationRelativeTo(null);\r
+ setVisible(true);\r
+ }\r
+ \r
+ //Functions\r
+ public static void main(String args[]) {\r
+ AwtUtil.setDefaultLAF();\r
+ \r
+ if (args.length > 0) {\r
+ TextureConverter.main(args);\r
+ } else {\r
+ new TextureConverterGUI();\r
+ }\r
+ }\r
+ \r
+ protected JPanel createCenterPanel() {\r
+ JLabel modeLabel = new JLabel("Format");\r
+ modeCombo = new JComboBox(TextureConverter.Mode.values());\r
+ modeCombo.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent e) {\r
+ Mode mode = (Mode)modeCombo.getSelectedItem();\r
+ converter.setMode(mode);\r
+ }\r
+ });\r
+ modeCombo.setSelectedItem(converter.getMode());\r
+\r
+ JLabel ditheringLabel = new JLabel("Dithering");\r
+ ditheringCheck = new JCheckBox();\r
+ ditheringCheck.setSelected(converter.isDithering());\r
+\r
+ JLabel threadsLabel = new JLabel("Threads");\r
+ threadsSpinner = new JSpinner(new SpinnerNumberModel(converter.getThreads(), 1, 128, 1));\r
+ \r
+ JPanel panel = new JPanel(new GridLayout(-1, 2, 15, 5));\r
+ panel.add(modeLabel); panel.add(modeCombo);\r
+ panel.add(ditheringLabel); panel.add(ditheringCheck);\r
+ panel.add(threadsLabel); panel.add(threadsSpinner);\r
+ \r
+ browseField = FileBrowseField.readFolder("Folder", new File(""));\r
+ convertButton = new JButton("Convert");\r
+ convertButton.addActionListener(new ActionListener() {\r
+ public void actionPerformed(ActionEvent e) {\r
+ convert();\r
+ }\r
+ });\r
+ \r
+ JPanel bottomPanel = new JPanel(new BorderLayout(10, 10));\r
+ bottomPanel.add(new Sash(Sash.HORIZONTAL), BorderLayout.NORTH);\r
+ bottomPanel.add(browseField, BorderLayout.CENTER);\r
+ bottomPanel.add(convertButton, BorderLayout.EAST);\r
+ \r
+ JPanel panel2 = new JPanel(new BorderLayout(0, 10));\r
+ panel2.setBorder(new EmptyBorder(10, 10, 10, 10));\r
+ panel2.add(panel, BorderLayout.NORTH);\r
+ panel2.add(bottomPanel, BorderLayout.SOUTH);\r
+\r
+ new DropTarget(panel2, new DropTargetListener() {\r
+ public void dragEnter(DropTargetDragEvent dtde) {\r
+ }\r
+ public void dragExit(DropTargetEvent dte) {\r
+ }\r
+ public void dragOver(DropTargetDragEvent dtde) {\r
+ for (DataFlavor df : dtde.getCurrentDataFlavorsAsList()) {\r
+ if (df.isFlavorJavaFileListType()) {\r
+ dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ @SuppressWarnings("unchecked")\r
+ public void drop(DropTargetDropEvent dtde) {\r
+ dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);\r
+ \r
+ Transferable t = dtde.getTransferable(); \r
+ for (DataFlavor df : t.getTransferDataFlavors()) {\r
+ if (df.isFlavorJavaFileListType()) {\r
+ try {\r
+ List<File> list = (List<File>)t.getTransferData(df);\r
+ convert(list);\r
+ } catch (UnsupportedFlavorException e) {\r
+ Log.w("Drag&Drop Exception", e);\r
+ } catch (IOException e) {\r
+ Log.w("Drag&Drop Exception", e);\r
+ } \r
+ } \r
+ }\r
+ }\r
+ public void dropActionChanged(DropTargetDragEvent dtde) {\r
+ }\r
+ });\r
+\r
+ return panel2;\r
+ }\r
+ \r
+ public void convert() {\r
+ convert(null);\r
+ } \r
+ public void convert(final List<File> files) {\r
+ final File folder = browseField.getFile();\r
+ if (files == null) {\r
+ if (!folder.exists() || !folder.isDirectory()) {\r
+ JOptionPane.showMessageDialog(null, "Invalid directory: \"" + folder + "\"",\r
+ "Error", JOptionPane.ERROR_MESSAGE);\r
+ return;\r
+ }\r
+ }\r
+\r
+ converter.setMode((Mode)modeCombo.getSelectedItem());\r
+ converter.setDithering(ditheringCheck.isSelected());\r
+ converter.setThreads((Integer)threadsSpinner.getValue());\r
+ \r
+ ProgressListener pl = new ProgressListener() {\r
+ public void onFinished(String message) {\r
+ JOptionPane.showMessageDialog(null, "<html>Conversion finished.",\r
+ "Finished", JOptionPane.PLAIN_MESSAGE);\r
+ }\r
+ public void onProgress(int value, int max, String message) {\r
+ }\r
+ };\r
+ \r
+ ProgressRunnable task = new ProgressRunnable() {\r
+ public void run(ProgressListener pl) {\r
+ try {\r
+ if (files == null) {\r
+ converter.convertFolder(folder.toString(), pl);\r
+ } else {\r
+ for (File f : files) {\r
+ converter.convertFile(f);\r
+ }\r
+ if (files.size() == 1) {\r
+ Image image = converter.readImage(files.get(0).getAbsolutePath());\r
+ JLabel label = new JLabel(new ImageIcon(image));\r
+ \r
+ JFrame frame = new JFrame();\r
+ frame.getContentPane().add(label);\r
+ frame.pack();\r
+ frame.setLocationRelativeTo(null);\r
+ frame.setVisible(true);\r
+ }\r
+ }\r
+ } catch (IOException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ };\r
+ \r
+ VNDSProgressDialog dialog = new VNDSProgressDialog();\r
+ dialog.showDialog(task, pl);\r
+ }\r
+ \r
+ //Getters\r
+ \r
+ //Setters\r
+ \r
+}\r
--- /dev/null
+title=Fate/Stay Night
+fontsize=16
--- /dev/null
+title=Fate/Stay Night
--- /dev/null
+title=Fate/Stay Night
+fontsize=16
--- /dev/null
+\r
+There are two ways of installing, the basic install contains all the routes and voices.\r
+The advanced installation method allows you to select which routes you want to install.\r
+Installing only one route reduces the install size from 130MB to 75MB~100MB.\r
+\r
+Basic Installation\r
+1. Copy the contents of "base_install" to /vnds/novels/fate on your flashcard.\r
+2. Optional. Overwrite the script.zip and info.txt on the DS with the ones in "patch-ja" to play the game in Japanese.\r
+3. Run with vnds.nds (http://www.digital-haze.net/vnds.php)\r
+\r
+Advanced installation\r
+1. Run "FateInstaller.jar" (you may need to install Java first: http://java.com).\r
+2. Select the routes you want to install, press install and choose a temporary install folder on your pc.\r
+3. Copy the generated "fate" folder to /vnds/novels/ on your flashcard\r
+4. Optional. Overwrite the script.zip and info.txt on the DS with the ones in "patch-ja" to play the game in Japanese.\r
+5. Run with vnds.nds (http://www.digital-haze.net/vnds.php)\r
+\r
+Go to http://www.weeaboo.nl for updates and more information\r
--- /dev/null
+<global>
+ <var name="gセイバークリア" type="int" value="1"></var>
+ <var name="g凛クリア" type="int" value="1"></var>
+</global>
--- /dev/null
+sound ~
+music ~
+setvar ~ ~
+text ~
+bgload special/title2.jpg
+text @Fate/Stay Night DS v1.1
+text @Translation by Mirror Moon
+text @VNDS Port: anonl
+delay 5
+music special/title.mp3
+delay 5
+text !
+choice Watch Prologue|Start Game
+#choice Watch Prologue|Start Game|Mou Ikkai
+music ~
+if selected == 1
+ jump prologue00.scr
+fi
+if selected == 2
+ jump fate01-00.scr
+fi
+if selected == 3
+ jump mouikkai.scr
+fi
--- /dev/null
+music special/mouikkai.mp3
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+delay 6
+bgload special/mouikkai1.png
+delay 6
+bgload special/mouikkai2.png
+delay 6
+bgload special/mouikkai3.png
+delay 6
+bgload special/mouikkai4.png
+music ~
+choice Mou Ikkai!|Back to Title
+if selected == 1
+ jump mouikkai.scr
+fi
+if selected == 2
+ jump main.scr
+fi
--- /dev/null
+text I am the bone of my leek.
+text Leeks are my body, and leeks are my blood.
+text I have wielded over a thousand leeks.
+text Unaware of loss.
+text Nor aware of gain.
+text Withstood pain to use many leeks.
+text Waiting for one's arrival.
+text I have no regrets, this is the only path.
+text My whole life was "Unlimited Leek Works."
+text You took an unimplemented path, returning to title screen...
+jump main.scr
--- /dev/null
+#!/bin/bash
+
+F=$1
+
+if [ "$F" == "build" ]; then
+ rm -rf Out
+
+ cd ./UI && ant && cd ../
+
+ cp -ra Distrib Out
+
+ cp UI/FSN* Out/
+ cp -ra UI/lib Out/lib
+ cp -ra UI/template Out/template
+
+ mkdir tmp && cd tmp
+ wget http://unclemion.com/dev/attachments/download/43/onscrtools-linux-x86_64-20110930.tar.bz2
+ tar -xvpf onscrtools-linux-x86_64-20110930.tar.bz2
+ cd ..
+
+ cp -a tmp/onscrtools-linux-x86_64-20110930/bin/* Out/bin/
+
+ rm -rf tmp
+
+ cd Tools/
+
+ cd ahx2wav
+ chmod +x make.sh
+ ./make.sh ahx2wav
+ mv ahx2wav ../../Out/bin/
+
+ cd ../ima2raw
+ g++ -o ima2raw ima2raw.cpp
+ mv ima2raw ../../Out/bin/
+
+ cd ../tlg2bmp
+ g++ -o tlg2bmp tlg2bmp.cpp
+ mv tlg2bmp ../../Out/bin/
+elif [ "$F" == "clean" ]; then
+ rm -rf Out
+ cd Tools/ahx2wav
+ ./make.sh clean
+ cd ../../UI
+ ant clean
+ cd ..
+fi