]> Chaos Git - vn/FSNConverter-Linux.git/commitdiff
Initial commit 1.2.5-linux-uo
authorchaoskagami <chaos.kagami@gmail.com>
Tue, 19 Aug 2014 12:01:19 +0000 (08:01 -0400)
committerchaoskagami <chaos.kagami@gmail.com>
Tue, 19 Aug 2014 12:01:19 +0000 (08:01 -0400)
99 files changed:
COPYING [new file with mode: 0644]
Distrib/FSNConverter.sh [new file with mode: 0755]
README.txt [new file with mode: 0644]
Tools/ahx2wav/COPYING [new file with mode: 0644]
Tools/ahx2wav/ahx2wav.c [new file with mode: 0644]
Tools/ahx2wav/getopt.c [new file with mode: 0644]
Tools/ahx2wav/make.sh [new file with mode: 0755]
Tools/ima2raw/ima2raw.cpp [new file with mode: 0644]
Tools/tlg2bmp/tlg2bmp.cpp [new file with mode: 0644]
UI/build.xml [new file with mode: 0644]
UI/lib/tcommon.jar [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/InvalidLayeringException.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/KiriKiriConverter.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/MacroHandler.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/MacroParser.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/Packer.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/Sprite.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/XP3Extractor.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/ConversionGUI.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/FateDirectoryFlattener.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/FateExtractor.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/FateInstaller.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/FateMacroHandler.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/FatePacker.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/FatePatcher.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/FateResourceConverter.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/FateScriptConverter.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/FateTextMacroHandler.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/InsertVoice.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/OutputField.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/RealtaNuaSoundExtractor.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/ResourceUsageAnalyzer.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/RouteParser.java [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/res/icon.ico [new file with mode: 0644]
UI/src/nl/weeaboo/krkr/fate/res/icon.png [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/AbstractConversionGUI.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/AbstractPacker.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/AbstractResourceConverter.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/BatchProcess.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/FileExts.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/FileMapper.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/FileOp.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/HashUtil.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/Log.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/Patcher.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/ProgressListener.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/ProgressRunnable.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/ResourcesUsed.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/TargetPlatform.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/VNDSProgressDialog.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/VNImageUtil.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/VNSoundUtil.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/installer/AddCommand.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/installer/Component.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/installer/CreateCommand.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/installer/FileListEntry.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/installer/Installer.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/installer/InstallerPacker.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/installer/PackCommand.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/installer/PatchCommand.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/tools/ImageConverter.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/tools/ImageConverterGUI.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/tools/MaskConverter.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/tools/Octree.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/tools/SoundConverter.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/tools/SoundConverterGUI.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/tools/TextureConverter.java [new file with mode: 0644]
UI/src/nl/weeaboo/vnds/tools/TextureConverterGUI.java [new file with mode: 0644]
UI/template/fate/background/special/blackout.jpg [new file with mode: 0644]
UI/template/fate/background/special/blueout.jpg [new file with mode: 0644]
UI/template/fate/background/special/fate/bone1.jpg [new file with mode: 0644]
UI/template/fate/background/special/fate/bone2.jpg [new file with mode: 0644]
UI/template/fate/background/special/fate/bone3.jpg [new file with mode: 0644]
UI/template/fate/background/special/fate/bone4.jpg [new file with mode: 0644]
UI/template/fate/background/special/fate/bone5.jpg [new file with mode: 0644]
UI/template/fate/background/special/fate/bone6.jpg [new file with mode: 0644]
UI/template/fate/background/special/fate/bone7.jpg [new file with mode: 0644]
UI/template/fate/background/special/fate/bone8.jpg [new file with mode: 0644]
UI/template/fate/background/special/greenout.jpg [new file with mode: 0644]
UI/template/fate/background/special/redout.jpg [new file with mode: 0644]
UI/template/fate/background/special/tigerdojo.jpg [new file with mode: 0644]
UI/template/fate/background/special/title.jpg [new file with mode: 0644]
UI/template/fate/background/special/title2.jpg [new file with mode: 0644]
UI/template/fate/background/special/whiteout.jpg [new file with mode: 0644]
UI/template/fate/default.ttf [new file with mode: 0644]
UI/template/fate/icon-high.png [new file with mode: 0644]
UI/template/fate/icon.png [new file with mode: 0644]
UI/template/fate/info-ch.txt [new file with mode: 0644]
UI/template/fate/info-en.txt [new file with mode: 0644]
UI/template/fate/info-ja.txt [new file with mode: 0644]
UI/template/fate/instructions.txt [new file with mode: 0644]
UI/template/fate/save/GLOBAL.SAV [new file with mode: 0644]
UI/template/fate/script/main.scr [new file with mode: 0644]
UI/template/fate/script/mouikkai.scr [new file with mode: 0644]
UI/template/fate/script/ulw.scr [new file with mode: 0644]
UI/template/fate/sound/special/title.mp3 [new file with mode: 0644]
UI/template/fate/thumbnail-high.jpg [new file with mode: 0644]
UI/template/fate/thumbnail.png [new file with mode: 0644]
build.sh [new file with mode: 0755]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..1dce76e
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,343 @@
+                   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
diff --git a/Distrib/FSNConverter.sh b/Distrib/FSNConverter.sh
new file mode 100755 (executable)
index 0000000..1106aeb
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/bash
+export PATH=$PATH:`pwd`/bin
+java -jar FSNConverter.jar
diff --git a/README.txt b/README.txt
new file mode 100644 (file)
index 0000000..c9622a5
--- /dev/null
@@ -0,0 +1,49 @@
+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 )
diff --git a/Tools/ahx2wav/COPYING b/Tools/ahx2wav/COPYING
new file mode 100644 (file)
index 0000000..b6f92f3
--- /dev/null
@@ -0,0 +1,340 @@
+                   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.
diff --git a/Tools/ahx2wav/ahx2wav.c b/Tools/ahx2wav/ahx2wav.c
new file mode 100644 (file)
index 0000000..b5229b8
--- /dev/null
@@ -0,0 +1,609 @@
+/*
+    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;
+}
diff --git a/Tools/ahx2wav/getopt.c b/Tools/ahx2wav/getopt.c
new file mode 100644 (file)
index 0000000..82e991d
--- /dev/null
@@ -0,0 +1,65 @@
+/*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
diff --git a/Tools/ahx2wav/make.sh b/Tools/ahx2wav/make.sh
new file mode 100755 (executable)
index 0000000..fc19318
--- /dev/null
@@ -0,0 +1,11 @@
+#!/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
diff --git a/Tools/ima2raw/ima2raw.cpp b/Tools/ima2raw/ima2raw.cpp
new file mode 100644 (file)
index 0000000..515685b
--- /dev/null
@@ -0,0 +1,165 @@
+#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
diff --git a/Tools/tlg2bmp/tlg2bmp.cpp b/Tools/tlg2bmp/tlg2bmp.cpp
new file mode 100644 (file)
index 0000000..2a0ffc1
--- /dev/null
@@ -0,0 +1,247 @@
+#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
diff --git a/UI/build.xml b/UI/build.xml
new file mode 100644 (file)
index 0000000..a7be753
--- /dev/null
@@ -0,0 +1,289 @@
+\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
diff --git a/UI/lib/tcommon.jar b/UI/lib/tcommon.jar
new file mode 100644 (file)
index 0000000..057fc56
Binary files /dev/null and b/UI/lib/tcommon.jar differ
diff --git a/UI/src/nl/weeaboo/krkr/InvalidLayeringException.java b/UI/src/nl/weeaboo/krkr/InvalidLayeringException.java
new file mode 100644 (file)
index 0000000..77ba31a
--- /dev/null
@@ -0,0 +1,12 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/KiriKiriConverter.java b/UI/src/nl/weeaboo/krkr/KiriKiriConverter.java
new file mode 100644 (file)
index 0000000..54d7f00
--- /dev/null
@@ -0,0 +1,365 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/MacroHandler.java b/UI/src/nl/weeaboo/krkr/MacroHandler.java
new file mode 100644 (file)
index 0000000..2eaa8cd
--- /dev/null
@@ -0,0 +1,60 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/MacroParser.java b/UI/src/nl/weeaboo/krkr/MacroParser.java
new file mode 100644 (file)
index 0000000..0dd157b
--- /dev/null
@@ -0,0 +1,361 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/Packer.java b/UI/src/nl/weeaboo/krkr/Packer.java
new file mode 100644 (file)
index 0000000..6340431
--- /dev/null
@@ -0,0 +1,200 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/Sprite.java b/UI/src/nl/weeaboo/krkr/Sprite.java
new file mode 100644 (file)
index 0000000..20d8a2b
--- /dev/null
@@ -0,0 +1,25 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/XP3Extractor.java b/UI/src/nl/weeaboo/krkr/XP3Extractor.java
new file mode 100644 (file)
index 0000000..2fe1701
--- /dev/null
@@ -0,0 +1,343 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/ConversionGUI.java b/UI/src/nl/weeaboo/krkr/fate/ConversionGUI.java
new file mode 100644 (file)
index 0000000..892f092
--- /dev/null
@@ -0,0 +1,118 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/FateDirectoryFlattener.java b/UI/src/nl/weeaboo/krkr/fate/FateDirectoryFlattener.java
new file mode 100644 (file)
index 0000000..8fbadc8
--- /dev/null
@@ -0,0 +1,58 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/FateExtractor.java b/UI/src/nl/weeaboo/krkr/fate/FateExtractor.java
new file mode 100644 (file)
index 0000000..57b0e4d
--- /dev/null
@@ -0,0 +1,210 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/FateInstaller.java b/UI/src/nl/weeaboo/krkr/fate/FateInstaller.java
new file mode 100644 (file)
index 0000000..c08b6ef
--- /dev/null
@@ -0,0 +1,177 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/FateMacroHandler.java b/UI/src/nl/weeaboo/krkr/fate/FateMacroHandler.java
new file mode 100644 (file)
index 0000000..8430ed5
--- /dev/null
@@ -0,0 +1,377 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/FatePacker.java b/UI/src/nl/weeaboo/krkr/fate/FatePacker.java
new file mode 100644 (file)
index 0000000..413e1bf
--- /dev/null
@@ -0,0 +1,150 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/FatePatcher.java b/UI/src/nl/weeaboo/krkr/fate/FatePatcher.java
new file mode 100644 (file)
index 0000000..a402f8e
--- /dev/null
@@ -0,0 +1,238 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/FateResourceConverter.java b/UI/src/nl/weeaboo/krkr/fate/FateResourceConverter.java
new file mode 100644 (file)
index 0000000..978b111
--- /dev/null
@@ -0,0 +1,243 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/FateScriptConverter.java b/UI/src/nl/weeaboo/krkr/fate/FateScriptConverter.java
new file mode 100644 (file)
index 0000000..65eef9e
--- /dev/null
@@ -0,0 +1,185 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/FateTextMacroHandler.java b/UI/src/nl/weeaboo/krkr/fate/FateTextMacroHandler.java
new file mode 100644 (file)
index 0000000..5c913da
--- /dev/null
@@ -0,0 +1,87 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/InsertVoice.java b/UI/src/nl/weeaboo/krkr/fate/InsertVoice.java
new file mode 100644 (file)
index 0000000..0ea2f6e
--- /dev/null
@@ -0,0 +1,136 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/OutputField.java b/UI/src/nl/weeaboo/krkr/fate/OutputField.java
new file mode 100644 (file)
index 0000000..e31eb9b
--- /dev/null
@@ -0,0 +1,72 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/RealtaNuaSoundExtractor.java b/UI/src/nl/weeaboo/krkr/fate/RealtaNuaSoundExtractor.java
new file mode 100644 (file)
index 0000000..c68e937
--- /dev/null
@@ -0,0 +1,147 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/ResourceUsageAnalyzer.java b/UI/src/nl/weeaboo/krkr/fate/ResourceUsageAnalyzer.java
new file mode 100644 (file)
index 0000000..f8df5ef
--- /dev/null
@@ -0,0 +1,483 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/RouteParser.java b/UI/src/nl/weeaboo/krkr/fate/RouteParser.java
new file mode 100644 (file)
index 0000000..807937f
--- /dev/null
@@ -0,0 +1,265 @@
+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
diff --git a/UI/src/nl/weeaboo/krkr/fate/res/icon.ico b/UI/src/nl/weeaboo/krkr/fate/res/icon.ico
new file mode 100644 (file)
index 0000000..c5da089
Binary files /dev/null and b/UI/src/nl/weeaboo/krkr/fate/res/icon.ico differ
diff --git a/UI/src/nl/weeaboo/krkr/fate/res/icon.png b/UI/src/nl/weeaboo/krkr/fate/res/icon.png
new file mode 100644 (file)
index 0000000..316ac2d
Binary files /dev/null and b/UI/src/nl/weeaboo/krkr/fate/res/icon.png differ
diff --git a/UI/src/nl/weeaboo/vnds/AbstractConversionGUI.java b/UI/src/nl/weeaboo/vnds/AbstractConversionGUI.java
new file mode 100644 (file)
index 0000000..b53260b
--- /dev/null
@@ -0,0 +1,513 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/AbstractPacker.java b/UI/src/nl/weeaboo/vnds/AbstractPacker.java
new file mode 100644 (file)
index 0000000..bc763ee
--- /dev/null
@@ -0,0 +1,242 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/AbstractResourceConverter.java b/UI/src/nl/weeaboo/vnds/AbstractResourceConverter.java
new file mode 100644 (file)
index 0000000..6da47ab
--- /dev/null
@@ -0,0 +1,224 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/BatchProcess.java b/UI/src/nl/weeaboo/vnds/BatchProcess.java
new file mode 100644 (file)
index 0000000..0334e67
--- /dev/null
@@ -0,0 +1,169 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/FileExts.java b/UI/src/nl/weeaboo/vnds/FileExts.java
new file mode 100644 (file)
index 0000000..b2a61f1
--- /dev/null
@@ -0,0 +1,47 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/FileMapper.java b/UI/src/nl/weeaboo/vnds/FileMapper.java
new file mode 100644 (file)
index 0000000..00560c7
--- /dev/null
@@ -0,0 +1,134 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/FileOp.java b/UI/src/nl/weeaboo/vnds/FileOp.java
new file mode 100644 (file)
index 0000000..362df68
--- /dev/null
@@ -0,0 +1,10 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/HashUtil.java b/UI/src/nl/weeaboo/vnds/HashUtil.java
new file mode 100644 (file)
index 0000000..642ce2a
--- /dev/null
@@ -0,0 +1,63 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/Log.java b/UI/src/nl/weeaboo/vnds/Log.java
new file mode 100644 (file)
index 0000000..719dadc
--- /dev/null
@@ -0,0 +1,85 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/Patcher.java b/UI/src/nl/weeaboo/vnds/Patcher.java
new file mode 100644 (file)
index 0000000..d9fa4db
--- /dev/null
@@ -0,0 +1,21 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/ProgressListener.java b/UI/src/nl/weeaboo/vnds/ProgressListener.java
new file mode 100644 (file)
index 0000000..b6fd5c4
--- /dev/null
@@ -0,0 +1,18 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/ProgressRunnable.java b/UI/src/nl/weeaboo/vnds/ProgressRunnable.java
new file mode 100644 (file)
index 0000000..738e71b
--- /dev/null
@@ -0,0 +1,12 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/ResourcesUsed.java b/UI/src/nl/weeaboo/vnds/ResourcesUsed.java
new file mode 100644 (file)
index 0000000..082c683
--- /dev/null
@@ -0,0 +1,106 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/TargetPlatform.java b/UI/src/nl/weeaboo/vnds/TargetPlatform.java
new file mode 100644 (file)
index 0000000..416ce30
--- /dev/null
@@ -0,0 +1,29 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/VNDSProgressDialog.java b/UI/src/nl/weeaboo/vnds/VNDSProgressDialog.java
new file mode 100644 (file)
index 0000000..e46184e
--- /dev/null
@@ -0,0 +1,90 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/VNImageUtil.java b/UI/src/nl/weeaboo/vnds/VNImageUtil.java
new file mode 100644 (file)
index 0000000..8f03269
--- /dev/null
@@ -0,0 +1,124 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/VNSoundUtil.java b/UI/src/nl/weeaboo/vnds/VNSoundUtil.java
new file mode 100644 (file)
index 0000000..ce99617
--- /dev/null
@@ -0,0 +1,158 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/installer/AddCommand.java b/UI/src/nl/weeaboo/vnds/installer/AddCommand.java
new file mode 100644 (file)
index 0000000..5c70e3d
--- /dev/null
@@ -0,0 +1,42 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/installer/Component.java b/UI/src/nl/weeaboo/vnds/installer/Component.java
new file mode 100644 (file)
index 0000000..7906a18
--- /dev/null
@@ -0,0 +1,86 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/installer/CreateCommand.java b/UI/src/nl/weeaboo/vnds/installer/CreateCommand.java
new file mode 100644 (file)
index 0000000..9a8aaff
--- /dev/null
@@ -0,0 +1,47 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/installer/FileListEntry.java b/UI/src/nl/weeaboo/vnds/installer/FileListEntry.java
new file mode 100644 (file)
index 0000000..e6d3919
--- /dev/null
@@ -0,0 +1,68 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/installer/Installer.java b/UI/src/nl/weeaboo/vnds/installer/Installer.java
new file mode 100644 (file)
index 0000000..69529ea
--- /dev/null
@@ -0,0 +1,182 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/installer/InstallerPacker.java b/UI/src/nl/weeaboo/vnds/installer/InstallerPacker.java
new file mode 100644 (file)
index 0000000..718b245
--- /dev/null
@@ -0,0 +1,228 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/installer/PackCommand.java b/UI/src/nl/weeaboo/vnds/installer/PackCommand.java
new file mode 100644 (file)
index 0000000..8ab62f2
--- /dev/null
@@ -0,0 +1,21 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/installer/PatchCommand.java b/UI/src/nl/weeaboo/vnds/installer/PatchCommand.java
new file mode 100644 (file)
index 0000000..57cf7ab
--- /dev/null
@@ -0,0 +1,73 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/tools/ImageConverter.java b/UI/src/nl/weeaboo/vnds/tools/ImageConverter.java
new file mode 100644 (file)
index 0000000..46b0d1b
--- /dev/null
@@ -0,0 +1,543 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/tools/ImageConverterGUI.java b/UI/src/nl/weeaboo/vnds/tools/ImageConverterGUI.java
new file mode 100644 (file)
index 0000000..c7fe43d
--- /dev/null
@@ -0,0 +1,328 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/tools/MaskConverter.java b/UI/src/nl/weeaboo/vnds/tools/MaskConverter.java
new file mode 100644 (file)
index 0000000..8139c09
--- /dev/null
@@ -0,0 +1,76 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/tools/Octree.java b/UI/src/nl/weeaboo/vnds/tools/Octree.java
new file mode 100644 (file)
index 0000000..78da7e6
--- /dev/null
@@ -0,0 +1,293 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/tools/SoundConverter.java b/UI/src/nl/weeaboo/vnds/tools/SoundConverter.java
new file mode 100644 (file)
index 0000000..2197b64
--- /dev/null
@@ -0,0 +1,278 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/tools/SoundConverterGUI.java b/UI/src/nl/weeaboo/vnds/tools/SoundConverterGUI.java
new file mode 100644 (file)
index 0000000..ee29108
--- /dev/null
@@ -0,0 +1,201 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/tools/TextureConverter.java b/UI/src/nl/weeaboo/vnds/tools/TextureConverter.java
new file mode 100644 (file)
index 0000000..74eea1f
--- /dev/null
@@ -0,0 +1,545 @@
+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
diff --git a/UI/src/nl/weeaboo/vnds/tools/TextureConverterGUI.java b/UI/src/nl/weeaboo/vnds/tools/TextureConverterGUI.java
new file mode 100644 (file)
index 0000000..c0f15ea
--- /dev/null
@@ -0,0 +1,226 @@
+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
diff --git a/UI/template/fate/background/special/blackout.jpg b/UI/template/fate/background/special/blackout.jpg
new file mode 100644 (file)
index 0000000..2845f6a
Binary files /dev/null and b/UI/template/fate/background/special/blackout.jpg differ
diff --git a/UI/template/fate/background/special/blueout.jpg b/UI/template/fate/background/special/blueout.jpg
new file mode 100644 (file)
index 0000000..0a17330
Binary files /dev/null and b/UI/template/fate/background/special/blueout.jpg differ
diff --git a/UI/template/fate/background/special/fate/bone1.jpg b/UI/template/fate/background/special/fate/bone1.jpg
new file mode 100644 (file)
index 0000000..d7e7c46
Binary files /dev/null and b/UI/template/fate/background/special/fate/bone1.jpg differ
diff --git a/UI/template/fate/background/special/fate/bone2.jpg b/UI/template/fate/background/special/fate/bone2.jpg
new file mode 100644 (file)
index 0000000..8b1394f
Binary files /dev/null and b/UI/template/fate/background/special/fate/bone2.jpg differ
diff --git a/UI/template/fate/background/special/fate/bone3.jpg b/UI/template/fate/background/special/fate/bone3.jpg
new file mode 100644 (file)
index 0000000..09ceee2
Binary files /dev/null and b/UI/template/fate/background/special/fate/bone3.jpg differ
diff --git a/UI/template/fate/background/special/fate/bone4.jpg b/UI/template/fate/background/special/fate/bone4.jpg
new file mode 100644 (file)
index 0000000..9b3b1ca
Binary files /dev/null and b/UI/template/fate/background/special/fate/bone4.jpg differ
diff --git a/UI/template/fate/background/special/fate/bone5.jpg b/UI/template/fate/background/special/fate/bone5.jpg
new file mode 100644 (file)
index 0000000..6454b1c
Binary files /dev/null and b/UI/template/fate/background/special/fate/bone5.jpg differ
diff --git a/UI/template/fate/background/special/fate/bone6.jpg b/UI/template/fate/background/special/fate/bone6.jpg
new file mode 100644 (file)
index 0000000..d4cc503
Binary files /dev/null and b/UI/template/fate/background/special/fate/bone6.jpg differ
diff --git a/UI/template/fate/background/special/fate/bone7.jpg b/UI/template/fate/background/special/fate/bone7.jpg
new file mode 100644 (file)
index 0000000..ff4442c
Binary files /dev/null and b/UI/template/fate/background/special/fate/bone7.jpg differ
diff --git a/UI/template/fate/background/special/fate/bone8.jpg b/UI/template/fate/background/special/fate/bone8.jpg
new file mode 100644 (file)
index 0000000..bc3f506
Binary files /dev/null and b/UI/template/fate/background/special/fate/bone8.jpg differ
diff --git a/UI/template/fate/background/special/greenout.jpg b/UI/template/fate/background/special/greenout.jpg
new file mode 100644 (file)
index 0000000..df7f111
Binary files /dev/null and b/UI/template/fate/background/special/greenout.jpg differ
diff --git a/UI/template/fate/background/special/redout.jpg b/UI/template/fate/background/special/redout.jpg
new file mode 100644 (file)
index 0000000..4e2fbe2
Binary files /dev/null and b/UI/template/fate/background/special/redout.jpg differ
diff --git a/UI/template/fate/background/special/tigerdojo.jpg b/UI/template/fate/background/special/tigerdojo.jpg
new file mode 100644 (file)
index 0000000..2ddf490
Binary files /dev/null and b/UI/template/fate/background/special/tigerdojo.jpg differ
diff --git a/UI/template/fate/background/special/title.jpg b/UI/template/fate/background/special/title.jpg
new file mode 100644 (file)
index 0000000..48dc2c5
Binary files /dev/null and b/UI/template/fate/background/special/title.jpg differ
diff --git a/UI/template/fate/background/special/title2.jpg b/UI/template/fate/background/special/title2.jpg
new file mode 100644 (file)
index 0000000..94fbcc8
Binary files /dev/null and b/UI/template/fate/background/special/title2.jpg differ
diff --git a/UI/template/fate/background/special/whiteout.jpg b/UI/template/fate/background/special/whiteout.jpg
new file mode 100644 (file)
index 0000000..72513b7
Binary files /dev/null and b/UI/template/fate/background/special/whiteout.jpg differ
diff --git a/UI/template/fate/default.ttf b/UI/template/fate/default.ttf
new file mode 100644 (file)
index 0000000..e47fc14
Binary files /dev/null and b/UI/template/fate/default.ttf differ
diff --git a/UI/template/fate/icon-high.png b/UI/template/fate/icon-high.png
new file mode 100644 (file)
index 0000000..316ac2d
Binary files /dev/null and b/UI/template/fate/icon-high.png differ
diff --git a/UI/template/fate/icon.png b/UI/template/fate/icon.png
new file mode 100644 (file)
index 0000000..f27666c
Binary files /dev/null and b/UI/template/fate/icon.png differ
diff --git a/UI/template/fate/info-ch.txt b/UI/template/fate/info-ch.txt
new file mode 100644 (file)
index 0000000..9dee79c
--- /dev/null
@@ -0,0 +1,2 @@
+title=Fate/Stay Night
+fontsize=16
diff --git a/UI/template/fate/info-en.txt b/UI/template/fate/info-en.txt
new file mode 100644 (file)
index 0000000..6ed96bd
--- /dev/null
@@ -0,0 +1 @@
+title=Fate/Stay Night
diff --git a/UI/template/fate/info-ja.txt b/UI/template/fate/info-ja.txt
new file mode 100644 (file)
index 0000000..9dee79c
--- /dev/null
@@ -0,0 +1,2 @@
+title=Fate/Stay Night
+fontsize=16
diff --git a/UI/template/fate/instructions.txt b/UI/template/fate/instructions.txt
new file mode 100644 (file)
index 0000000..a816764
--- /dev/null
@@ -0,0 +1,18 @@
+\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
diff --git a/UI/template/fate/save/GLOBAL.SAV b/UI/template/fate/save/GLOBAL.SAV
new file mode 100644 (file)
index 0000000..5d69d8d
--- /dev/null
@@ -0,0 +1,4 @@
+<global>
+  <var name="gセイバークリア" type="int" value="1"></var>
+  <var name="g凛クリア" type="int" value="1"></var>
+</global>
diff --git a/UI/template/fate/script/main.scr b/UI/template/fate/script/main.scr
new file mode 100644 (file)
index 0000000..a902799
--- /dev/null
@@ -0,0 +1,24 @@
+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
diff --git a/UI/template/fate/script/mouikkai.scr b/UI/template/fate/script/mouikkai.scr
new file mode 100644 (file)
index 0000000..7ba1949
--- /dev/null
@@ -0,0 +1,313 @@
+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
diff --git a/UI/template/fate/script/ulw.scr b/UI/template/fate/script/ulw.scr
new file mode 100644 (file)
index 0000000..d40400a
--- /dev/null
@@ -0,0 +1,11 @@
+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
diff --git a/UI/template/fate/sound/special/title.mp3 b/UI/template/fate/sound/special/title.mp3
new file mode 100644 (file)
index 0000000..7e45d38
Binary files /dev/null and b/UI/template/fate/sound/special/title.mp3 differ
diff --git a/UI/template/fate/thumbnail-high.jpg b/UI/template/fate/thumbnail-high.jpg
new file mode 100644 (file)
index 0000000..19b3d57
Binary files /dev/null and b/UI/template/fate/thumbnail-high.jpg differ
diff --git a/UI/template/fate/thumbnail.png b/UI/template/fate/thumbnail.png
new file mode 100644 (file)
index 0000000..c52e045
Binary files /dev/null and b/UI/template/fate/thumbnail.png differ
diff --git a/build.sh b/build.sh
new file mode 100755 (executable)
index 0000000..4bf0e9f
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,46 @@
+#!/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