- initial commit of QTinNS project
authorAkiko <akiko@linux-addicted.net>
Wed, 2 Oct 2013 15:13:16 +0000 (17:13 +0200)
committerAkiko <akiko@linux-addicted.net>
Wed, 2 Oct 2013 15:13:16 +0000 (17:13 +0200)
- added basic verision of the QTinNS editor
- added zlib compression tools
- added not fully functioning vfs_tool (an older command line version of the GUI editor)
- added a prototype for dat file parsing (just for testing if it can be done that way)
- added full At based version of a class handling Neocron VFS/PAK files
- added cmake module for adding Irrlicht to the project
- added cleanup script for cleaning up of in-source build (out of source builds are not working yet)

! French language is missing (sorry, I had to learn Latin in my youth)
! patches and corrections will be accepted, but this repo will stay read only for now

30 files changed:
CMakeLists.txt [new file with mode: 0644]
COPYING [new file with mode: 0644]
README [new file with mode: 0644]
cmake_distclean.sh [new file with mode: 0755]
cmake_modules/FindIrrlicht.cmake [new file with mode: 0644]
common/NCTypes.hxx [new file with mode: 0644]
editor/CMakeLists.txt [new file with mode: 0644]
editor/Main.cxx [new file with mode: 0644]
editor/MainWindow.cxx [new file with mode: 0644]
editor/MainWindow.hxx [new file with mode: 0644]
editor/MainWindow.ui [new file with mode: 0644]
editor/Resources.qrc [new file with mode: 0644]
editor/i18n_english.ts [new file with mode: 0644]
editor/i18n_french.ts [new file with mode: 0644]
editor/i18n_german.ts [new file with mode: 0644]
filesystem/CMakeLists.txt [new file with mode: 0644]
filesystem/FileIdentifier.cxx [new file with mode: 0644]
filesystem/FileIdentifier.hxx [new file with mode: 0644]
filesystem/NCFile.cxx [new file with mode: 0644]
filesystem/NCFile.hxx [new file with mode: 0644]
filesystem/NCFilesystem.cxx [new file with mode: 0644]
filesystem/NCFilesystem.hxx [new file with mode: 0644]
tests/CMakeLists.txt [new file with mode: 0644]
tests/test_file.cxx [new file with mode: 0644]
tests/test_filesystem.cxx [new file with mode: 0644]
tools/CMakeLists.txt [new file with mode: 0644]
tools/dat_viewer.cxx [new file with mode: 0644]
tools/vfs_tool.cxx [new file with mode: 0644]
tools/zcompress.cxx [new file with mode: 0644]
tools/zdecompress.cxx [new file with mode: 0644]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..772944d
--- /dev/null
@@ -0,0 +1,30 @@
+cmake_minimum_required (VERSION 2.8.8)
+project                (QTinns)
+set                    (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake_modules/")
+
+# needed packages
+find_package           (Qt5Core)
+find_package           (Qt5Widgets)
+find_package           (Qt5Network)
+find_package           (Qt5LinguistTools)
+find_package           (ZLIB REQUIRED)
+find_package           (Irrlicht REQUIRED)
+
+# includes and outputs
+set                    (CMAKE_INCLUDE_CURRENT_DIR on)
+include_directories    (${PROJECT_SOURCE_DIR})
+set                    (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
+set                    (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
+set                    (CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
+
+# compiler settings
+set                    (CMAKE_CXX_FLAGS "-std=c++11 -W -Wall -Wextra -Os")
+set                    (CMAKE_C_FLAGS "-std=c++11 -W -Wall -Wextra -Os")
+
+#add_subdirectory       (analyzer)
+add_subdirectory       (editor)
+add_subdirectory       (filesystem)
+#add_subdirectory       (renderer)
+#add_subdirectory       (server)
+add_subdirectory       (tests)
+add_subdirectory       (tools)
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..d159169
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                    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 Lesser 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.
+
+                    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.)
+
+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.
+
+  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.
+
+  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
+
+            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 Lesser General
+Public License instead of this License.
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..dc891ef
--- /dev/null
+++ b/README
@@ -0,0 +1,7 @@
+This directory contains QTinNS, a Qt based reimplementation of TinNS
+and corresponding tools, a Neocron server emulation.
+
+QTinNS is free software. See the files whose names start with COPYING
+for copying permission.
+
+Copyright (C) 2013 Akiko <akiko@linux-addicted.net>.
diff --git a/cmake_distclean.sh b/cmake_distclean.sh
new file mode 100755 (executable)
index 0000000..e67620c
--- /dev/null
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+rm -rf CMakeCache.txt
+rm -rf CMakeFiles/
+rm -rf Makefile
+rm -rf cmake_install.cmake
+rm -rf *.qrc.depends
+rm -rf moc_*.*
+
+rm -rf analyzer/CMakeCache.txt
+rm -rf analyzer/CMakeFiles/
+rm -rf analyzer/Makefile
+rm -rf analyzer/cmake_install.cmake
+rm -rf analyzer/*.qrc.depends
+rm -rf analyzer/moc_*.*
+
+rm -rf editor/CMakeCache.txt
+rm -rf editor/CMakeFiles/
+rm -rf editor/Makefile
+rm -rf editor/cmake_install.cmake
+rm -rf editor/*.qrc.depends
+rm -rf editor/moc_*.*
+
+rm -rf filesystem/CMakeCache.txt
+rm -rf filesystem/CMakeFiles/
+rm -rf filesystem/Makefile
+rm -rf filesystem/cmake_install.cmake
+rm -rf filesystem/*.qrc.depends
+rm -rf filesystem/moc_*.*
+
+rm -rf renderer/CMakeCache.txt
+rm -rf renderer/CMakeFiles/
+rm -rf renderer/Makefile
+rm -rf renderer/cmake_install.cmake
+rm -rf renderer/*.qrc.depends
+rm -rf renderer/moc_*.*
+
+rm -rf server/CMakeCache.txt
+rm -rf server/CMakeFiles/
+rm -rf server/Makefile
+rm -rf server/cmake_install.cmake
+rm -rf server/*.qrc.depends
+rm -rf server/moc_*.*
+
+rm -rf tests/CMakeCache.txt
+rm -rf tests/CMakeFiles/
+rm -rf tests/Makefile
+rm -rf tests/cmake_install.cmake
+rm -rf tests/*.qrc.depends
+rm -rf tests/moc_*.*
+
+rm -rf tools/CMakeCache.txt
+rm -rf tools/CMakeFiles/
+rm -rf tools/Makefile
+rm -rf tools/cmake_install.cmake
+rm -rf tools/*.qrc.depends
+rm -rf tools/moc_*.*
+
+# code counter
+LOG="/tmp/log.$$"
+LINES=0
+C=0
+find| grep "\.c$" >$LOG
+find| grep "\.cpp$" >>$LOG
+find| grep "\.cxx$" >>$LOG
+find| grep "\.h$" >>$LOG
+find| grep "\.hpp$" >>$LOG
+find| grep "\.hxx$" >>$LOG
+find| grep "\.sh$" >>$LOG
+find| grep "\.S$" >>$LOG
+find| grep "\.ts$" >>$LOG
+find| grep "\.ui$" >>$LOG
+find| grep "\.qrc$" >>$LOG
+find| grep "\.cmake$" >>$LOG
+while read L
+do
+       C=$((C+1))
+       W=$(cat $L|wc -l)
+       S=$(cat $L|wc -c)
+       SIZE=$((SIZE+S))
+       LINES=$((LINES+W))
+done <$LOG
+rm $LOG
+echo
+echo "--- REPOSITORY SUMMARY ---"
+echo "source files:  $C"
+echo "lines of code: $LINES"
+echo "size of code:  $SIZE bytes"
+echo
diff --git a/cmake_modules/FindIrrlicht.cmake b/cmake_modules/FindIrrlicht.cmake
new file mode 100644 (file)
index 0000000..d1cafb8
--- /dev/null
@@ -0,0 +1,42 @@
+set (IRRLICHT_FOUND TRUE)
+
+find_path (
+    IRRLICHT_INCLUDE_DIRS
+    NAMES
+        irrlicht.h
+    HINTS
+        ENV
+        IRRLICHT_ROOT
+    PATHS
+        /usr
+        /usr/local
+    PATH_SUFFIXES
+        include
+        irrlicht
+)
+
+find_library (
+    IRRLICHT_LIBRARY
+        Irrlicht
+    HINTS
+        ENV
+        IRRLICHT_ROOT
+    PATHS
+        /usr/lib
+        /usr/local/lib
+)
+
+if (IRRLICHT_INCLUDE_DIRS AND EXISTS "${IRRLICHT_INCLUDE_DIRS}/IrrCompileConfig.h")
+    file (STRINGS "${IRRLICHT_INCLUDE_DIRS}/IrrCompileConfig.h"
+          verstr REGEX "^#define[ \t]+IRRLICHT_SDK_VERSION[ \t]+\".+\""
+    )
+    string (REGEX REPLACE "^#define[ \t]+IRRLICHT_SDK_VERSION[ \t]+\"([^\"]+)\".*" "\\1"
+            IRRLICHT_VERSION_STRING "${verstr}"
+    )
+    unset (verstr)
+endif ()
+
+include (FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS (Irrlicht REQUIRED_VARS IRRLICHT_LIBRARY IRRLICHT_INCLUDE_DIRS VERSION_VAR IRRLICHT_VERSION_STRING)
+
+mark_as_advanced (IRRLICHT_INCLUDE_DIRS IRRLICHT_LIBRARIES)
diff --git a/common/NCTypes.hxx b/common/NCTypes.hxx
new file mode 100644 (file)
index 0000000..8250525
--- /dev/null
@@ -0,0 +1,85 @@
+#pragma once
+
+#include <limits>
+#include <QObject>
+
+namespace NC
+{
+    namespace Meta
+    {
+        const quint32 Error = std::numeric_limits<quint32>::max();
+
+        enum Type : quint64 {
+            T00 = 0x0,
+            T01 = 0x1,
+            T02 = 0x2,
+            T03 = 0x4,
+            T04 = 0x8,
+            T05 = 0x10,
+            T06 = 0x20,
+            T07 = 0x40,
+            T08 = 0x80,
+            T09 = 0x100,
+            T10 = 0x200,
+            T11 = 0x400,
+            T12 = 0x800,
+            T13 = 0x1000,
+            T14 = 0x2000,
+            T15 = 0x4000,
+            T16 = 0x8000,
+            T17 = 0x10000,
+            T18 = 0x20000,
+            T19 = 0x40000,
+            T20 = 0x80000,
+            T21 = 0x100000,
+            T22 = 0x200000,
+            T23 = 0x400000,
+            T24 = 0x800000,
+            T25 = 0x1000000,
+            T26 = 0x2000000,
+            T27 = 0x4000000,
+            T28 = 0x8000000,
+            T29 = 0x10000000,
+            T30 = 0x20000000,
+            T31 = 0x40000000,
+            T32 = 0x80000000,
+            T33 = 0x100000000,
+            T34 = 0x200000000,
+            T35 = 0x400000000,
+            T36 = 0x800000000,
+            T37 = 0x1000000000,
+            T38 = 0x2000000000,
+            T39 = 0x4000000000,
+            T40 = 0x8000000000
+        };
+    }
+
+    namespace FileData
+    {
+        enum Type : quint8 {
+            None = Meta::Type::T00,
+            HSize = Meta::Type::T01,
+            Position = Meta::Type::T02,
+            CSize = Meta::Type::T03,
+            OSize = Meta::Type::T04,
+            NameLength = Meta::Type::T05,
+            Name = Meta::Type::T06,
+            CData = Meta::Type::T07,
+            OData = Meta::Type::T08,
+            All = HSize + Position + CSize + OSize + NameLength + Name + CData + OData
+        };
+    }
+
+    namespace Identify
+    {
+        enum Type : quint32 {
+            Error = Meta::Error,
+            Plain = Error - 1,
+            ZLIB = Error - 2,
+            VFS = 0x3D458CDE,
+            PAK1 = 0x883DF70A,
+            PAK2 = 0x93847584,
+            PAK3 = 0xABFDEFBD
+        };
+    }
+}
diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt
new file mode 100644 (file)
index 0000000..cbe634d
--- /dev/null
@@ -0,0 +1,17 @@
+# Qt sources
+set                   (E_SOURCES Main.cxx MainWindow.cxx)
+set                   (E_HEADERS MainWindow.hxx)
+set                   (E_UI_FILES MainWindow.ui)
+set                   (E_RES_FILES Resources.qrc)
+set                   (E_TS_FILES i18n_english.ts i18n_french.ts i18n_german.ts)
+
+# wrappers
+QT5_ADD_TRANSLATION   (E_TS_SOURCES ${E_TS_FILES})
+QT5_WRAP_CPP          (E_MOC_SOURCES ${E_HEADERS})
+QT5_WRAP_UI           (E_UI_SOURCES ${E_UI_FILES})
+QT5_ADD_RESOURCES     (E_RES_SOURCES ${E_RES_FILES})
+
+# apps
+add_executable        (QTinNS_Editor ${E_SOURCES} ${E_MOC_SOURCES} ${E_UI_SOURCES} ${E_RES_SOURCES} ${E_TS_SOURCES})
+qt5_use_modules       (QTinNS_Editor Core Widgets)
+target_link_libraries (QTinNS_Editor Filesystem)
diff --git a/editor/Main.cxx b/editor/Main.cxx
new file mode 100644 (file)
index 0000000..e37fe84
--- /dev/null
@@ -0,0 +1,12 @@
+#include <QApplication>
+#include "MainWindow.hxx"
+
+int main(int argc, char *argv[])
+{
+    QApplication app(argc, argv);
+    MainWindow w;
+
+    w.show();
+
+    return app.exec();
+}
diff --git a/editor/MainWindow.cxx b/editor/MainWindow.cxx
new file mode 100644 (file)
index 0000000..0f29ce9
--- /dev/null
@@ -0,0 +1,753 @@
+#include <QApplication>
+#include <QCryptographicHash>
+#include <QDebug>
+#include <QEvent>
+#include <QFileDialog>
+#include <QProxyStyle>
+#include <QSettings>
+#include <QStyleFactory>
+#include <QTranslator>
+#include "MainWindow.hxx"
+#include "ui_MainWindow.h"
+#include "filesystem/NCFilesystem.hxx"
+
+// --- internal typedefs ---
+
+using QTW = QTableWidget;
+using QTWI = QTableWidgetItem;
+using QCH = QCryptographicHash;
+using ColType = NC::FileData::Type;
+using FileType = NC::Identify::Type;
+
+// --- internal constants ---
+
+const QColor Green(180, 255, 180);
+const QColor Grey(180, 180, 180);
+const QColor Red(255, 180, 180);
+const QString TableHeader("header size;position;compressed size;original size;name length;name;SHA-1");
+
+// --- constructors and deconstructors ---
+
+MainWindow::MainWindow(QWidget *parent)
+: QMainWindow(parent), _ui(new Ui::MainWindow), _translator(new QTranslator(this)),
+  _settings(new QSettings("LinuxAddicted", "QTinNS Editor", this)), _vfs(new NCFilesystem)
+{
+    _ui->setupUi(this);
+    _ui->wid_table->setColumnCount(7);
+    _ui->wid_table->setHorizontalHeaderLabels(TableHeader.split(";"));
+    _ui->wid_table->setSelectionBehavior(QAbstractItemView::SelectRows);
+
+    setLanguage(Language::English);
+    setupActions();
+
+    if (_settings)
+    {
+        move(_settings->value("mw_position", QPoint(40, 40)).toPoint());
+        resize(_settings->value("mw_size", QSize(800, 600)).toSize());
+        setMinimumSize(_settings->value("mw_minsize", QSize(640, 480)).toSize());
+    }
+    else
+    {
+        move(40, 40);
+        resize(800, 600);
+        setMinimumSize(640, 480);
+    }
+
+    slotStatusUpdate();
+    setMode(Mode::Fresh);
+}
+
+MainWindow::~MainWindow()
+{
+    if (_settings)
+    {
+        _settings->setValue("mw_position", pos());
+        _settings->setValue("mw_size", size());
+        _settings->setValue("mw_minsize", minimumSize());
+        _settings->sync();
+    }
+
+    delete _vfs;
+    delete _settings;
+    delete _translator;
+    delete _ui;
+}
+
+// --- slots ---
+
+void MainWindow::slotAppNew()
+{
+    _vfs->clear();
+    setMode(Mode::Fresh);
+}
+
+void MainWindow::slotAppOpen()
+{
+    QString filename = QFileDialog::getOpenFileName(this, tr("DIA-OPEN-VFS"), "./", tr("FLT-FILE-VFS"));
+
+    if (_vfs->load(filename) != _vfs->Error)
+    {
+        _filename = filename;
+        slotStatusUpdate();
+        setMode(Mode::Edited);
+    }
+}
+
+void MainWindow::slotAppSave()
+{
+    if (_filename.isEmpty())
+        slotAppSaveAs();
+    else
+        _vfs->save(_filename);
+}
+
+void MainWindow::slotAppSaveAs()
+{
+    QString filename = QFileDialog::getSaveFileName(this, tr("DIA-SAVE-VFS"), "./unnamed.pak", tr("FLT-FILE-VFS"));
+
+    if (filename.size())
+    {
+        _filename = filename;
+        _vfs->save(_filename);
+    }
+}
+
+void MainWindow::slotAppQuit()
+{
+    close();
+}
+
+void MainWindow::slotFileAdd()
+{
+    QString filename = QFileDialog::getOpenFileName(this, tr("DIA-OPEN-FILE"), "./", tr("FLT-FILE-ANY"));
+
+    if (filename.size())
+    {
+        _vfs->fileAdd(filename);
+        slotStatusUpdate();
+    }
+}
+
+void MainWindow::slotFileReplace()
+{
+    QString filename = QFileDialog::getOpenFileName(this, tr("DIA-OPEN-FILE"), "./", tr("FLT-FILE-ANY"));
+
+    if (filename.size())
+    {
+        _vfs->fileReplace(filename, _ui->wid_table->currentRow());
+        slotStatusUpdate();
+    }
+}
+
+void MainWindow::slotFileRemove()
+{
+    _vfs->fileRemove(_ui->wid_table->currentRow());
+    slotStatusUpdate();
+    setMode(Mode::Edited);
+}
+
+void MainWindow::slotFileSavePlain()
+{
+    QString filename = QFileDialog::getSaveFileName(this, tr("DIA-SAVE-PLAIN"), "./file", tr("FLT-FILE-PLAIN"));
+
+    if (filename.size())
+        _vfs->fileSave(filename, _ui->wid_table->currentRow());
+}
+
+void MainWindow::slotFileSavePAK()
+{
+    QString filename = QFileDialog::getSaveFileName(this, tr("DIA-SAVE-PAK"), "./file", tr("FLT-FILE-PAK"));
+
+    if (filename.size())
+        _vfs->fileSave(filename, _ui->wid_table->currentRow(), FileType::PAK1);
+}
+
+void MainWindow::slotFileSaveZLIB()
+{
+    QString filename = QFileDialog::getSaveFileName(this, tr("DIA-SAVE-ZLIB"), "./file", tr("FLT-FILE-ZLIB"));
+
+    if (filename.size())
+        _vfs->fileSave(filename, _ui->wid_table->currentRow(), FileType::ZLIB);
+}
+
+void MainWindow::slotFileConvWin()
+{
+    QTW *table = _ui->wid_table;
+    NCFile entry = _vfs->fileGet(table->currentRow());
+
+    entry.setName(entry.name().replace("/", "\\"));
+    _vfs->fileReplace(entry, table->currentRow());
+    slotTableSelected();
+}
+
+void MainWindow::slotFileConvLin()
+{
+    QTW *table = _ui->wid_table;
+    NCFile entry = _vfs->fileGet(table->currentRow());
+
+    entry.setName(entry.name().replace("\\", "/"));
+    _vfs->fileReplace(entry, table->currentRow());
+    slotTableSelected();
+}
+
+void MainWindow::slotLangEnglish()
+{
+    setLanguage(Language::English);
+}
+
+void MainWindow::slotLangFrench()
+{
+    setLanguage(Language::French);
+}
+
+void MainWindow::slotLangGerman()
+{
+    setLanguage(Language::German);
+}
+
+void MainWindow::slotStyleDefault()
+{
+    QApplication::setStyle(new QProxyStyle);
+}
+
+void MainWindow::slotStyleFusion()
+{
+    QApplication::setStyle(QStyleFactory::create("Fusion"));
+}
+
+void MainWindow::slotStyleWindows()
+{
+    QApplication::setStyle(QStyleFactory::create("Windows"));
+}
+
+void MainWindow::slotStyleGTK()
+{
+    QApplication::setStyle(QStyleFactory::create("GTK+"));
+}
+
+void MainWindow::slotAboutApp()
+{
+}
+
+void MainWindow::slotAboutQt()
+{
+    qApp->aboutQt();
+}
+
+void MainWindow::slotStatusUpdate()
+{
+    QString msg1 = tr("MSG-UPD-FILE") + ": '" + _filename + "' " + tr("MSG-UPD-WITH") + " "
+                   + QString::number(_vfs->count()) + " " + tr("MSG-UPD-ENTRIES");
+    QString msg2 = tr("FRM-MAINWINDOW") + " - " + msg1;
+
+    statusBar()->showMessage(msg1);
+    setWindowTitle(msg2);
+}
+
+void MainWindow::slotTableSelected()
+{
+    QTW *table = _ui->wid_table;
+    NCFile entry = _vfs->fileGet(table->currentRow());
+
+    if (entry.name().count("/"))
+        _ui->act_file_conv_win->setEnabled(true);
+    else
+        _ui->act_file_conv_win->setEnabled(false);
+
+    if (entry.name().count("\\"))
+        _ui->act_file_conv_lin->setEnabled(true);
+    else
+        _ui->act_file_conv_lin->setEnabled(false);
+
+    setMode(Mode::Selected);
+}
+
+void MainWindow::slotTableChange(qint32 row)
+{
+    QTW *table = _ui->wid_table;
+    NCFile entry = _vfs->fileGet(row);
+
+    entry.setName(table->item(row, 5)->text());
+    entry.setNameLength(entry.name().size() + 1);
+    entry.setHSize(entry.headerSize() + entry.nameLength());
+
+    _vfs->fileReplace(entry, row);
+    slotStatusUpdate();
+}
+
+void MainWindow::slotTableStatus(quint32 from_row, quint32 to_row, ColType cols)
+{
+    QTW *table = _ui->wid_table;
+    QTWI *it0 = 0;
+    QTWI *it1 = 0;
+    QTWI *it2 = 0;
+    QTWI *it3 = 0;
+    QTWI *it4 = 0;
+    QTWI *it5 = 0;
+    QTWI *it6 = 0;
+
+    // if that happens, something is very very odd
+    if (to_row != _vfs->count())
+        qDebug() << "it happend! oh my god, it happend!!";
+
+    // if these do not fit, we have a full refresh and have to build every single
+    // table item
+    if (to_row != static_cast<quint32>(table->rowCount()))
+    {
+        table->setRowCount(to_row);
+        for (quint32 row = 0; row < to_row; ++row)
+        {
+            NCFile entry = _vfs->fileGet(row);
+
+            it0 = new QTWI(QString::number(entry.hSize()));
+            it1 = new QTWI(QString::number(entry.position()));
+            it2 = new QTWI(QString::number(entry.cSize()));
+            it3 = new QTWI(QString::number(entry.oSize()));
+            it4 = new QTWI(QString::number(entry.nameLength()));
+            it5 = new QTWI(entry.name());
+            it6 = new QTWI(QString(QCH::hash(entry.cData(), QCH::Sha1).toHex().toUpper()));
+
+            it0->setBackground(Grey);
+            it0->setFlags(it0->flags() ^ Qt::ItemIsEditable);
+            it0->setTextAlignment(Qt::AlignRight);
+
+            it1->setBackground(Grey);
+            it1->setFlags(it1->flags() ^ Qt::ItemIsEditable);
+            it1->setTextAlignment(Qt::AlignRight);
+
+            it2->setBackground(Grey);
+            it2->setFlags(it2->flags() ^ Qt::ItemIsEditable);
+            it2->setTextAlignment(Qt::AlignRight);
+
+            it3->setBackground(Grey);
+            it3->setFlags(it3->flags() ^ Qt::ItemIsEditable);
+            it3->setTextAlignment(Qt::AlignRight);
+
+            it4->setBackground(Grey);
+            it4->setFlags(it4->flags() ^ Qt::ItemIsEditable);
+            it4->setTextAlignment(Qt::AlignRight);
+
+            it5->setBackground(Green);
+            it5->setTextAlignment(Qt::AlignLeft);
+
+            it6->setBackground(Grey);
+            it6->setFlags(it6->flags() ^ Qt::ItemIsEditable);
+            it6->setTextAlignment(Qt::AlignLeft);
+
+            switch (cols)
+            {
+                case ColType::HSize:
+                    it0->setBackground(Red);
+                    break;
+
+                case ColType::Position:
+                    it1->setBackground(Red);
+                    break;
+
+                case ColType::CSize:
+                    it2->setBackground(Red);
+                    break;
+
+                case ColType::OSize:
+                    it3->setBackground(Red);
+                    break;
+
+                case ColType::NameLength:
+                    it4->setBackground(Red);
+                    break;
+
+                case ColType::Name:
+                    it5->setBackground(Red);
+                    break;
+
+                case ColType::CData:
+                case ColType::OData:
+                    break;
+
+                case ColType::All:
+                    it0->setBackground(Red);
+                    it1->setBackground(Red);
+                    it2->setBackground(Red);
+                    it3->setBackground(Red);
+                    it4->setBackground(Red);
+                    it5->setBackground(Red);
+                    it6->setBackground(Red);
+                    break;
+
+                case ColType::None:
+                default:
+                    it0->setBackground(Grey);
+                    it1->setBackground(Grey);
+                    it2->setBackground(Grey);
+                    it3->setBackground(Grey);
+                    it4->setBackground(Grey);
+                    it5->setBackground(Green);
+                    it6->setBackground(Grey);
+            }
+
+            disconnect(table, SIGNAL(cellChanged(qint32, qint32)),
+                       this, SLOT(slotTableChange(qint32)));
+
+            table->setItem(row, 0, it0);
+            table->setItem(row, 1, it1);
+            table->setItem(row, 2, it2);
+            table->setItem(row, 3, it3);
+            table->setItem(row, 4, it4);
+            table->setItem(row, 5, it5);
+            table->setItem(row, 6, it6);
+
+            connect(table, SIGNAL(cellChanged(qint32, qint32)),
+                    this, SLOT(slotTableChange(qint32)));
+        }
+    }
+    else
+    {
+        disconnect(table, SIGNAL(cellChanged(qint32, qint32)),
+                   this, SLOT(slotTableChange(qint32)));
+
+        for (quint32 row = 0; row < to_row; ++row)
+        {
+            NCFile entry = _vfs->fileGet(row);
+
+            table->item(row, 1)->setText(QString::number(entry.position()));
+        }
+
+        for (quint32 row = from_row; row < to_row; ++row)
+        {
+            switch (cols)
+            {
+                case ColType::HSize:
+                    table->item(row, 0)->setBackground(Red);
+                    break;
+
+                case ColType::Position:
+                    table->item(row, 1)->setBackground(Red);
+                    break;
+
+                case ColType::CSize:
+                    table->item(row, 2)->setBackground(Red);
+                    break;
+
+                case ColType::OSize:
+                    table->item(row, 3)->setBackground(Red);
+                    break;
+
+                case ColType::NameLength:
+                    table->item(row, 4)->setBackground(Red);
+                    break;
+
+                case ColType::Name:
+                    table->item(row, 5)->setBackground(Red);
+                    break;
+
+                case ColType::CData:
+                case ColType::OData:
+                    break;
+
+                case ColType::All:
+                    table->item(row, 0)->setBackground(Red);
+                    table->item(row, 1)->setBackground(Red);
+                    table->item(row, 2)->setBackground(Red);
+                    table->item(row, 3)->setBackground(Red);
+                    table->item(row, 4)->setBackground(Red);
+                    table->item(row, 5)->setBackground(Red);
+                    table->item(row, 6)->setBackground(Red);
+                    break;
+
+                case ColType::None:
+                default:
+                    table->item(row, 0)->setBackground(Grey);
+                    table->item(row, 1)->setBackground(Grey);
+                    table->item(row, 2)->setBackground(Grey);
+                    table->item(row, 3)->setBackground(Grey);
+                    table->item(row, 4)->setBackground(Grey);
+                    table->item(row, 5)->setBackground(Green);
+                    table->item(row, 6)->setBackground(Grey);
+            }
+        }
+
+        connect(table, SIGNAL(cellChanged(qint32, qint32)),
+                this, SLOT(slotTableChange(qint32)));
+    }
+
+    table->resizeColumnsToContents();
+}
+
+void MainWindow::slotTableAdd(NCFile& entry, quint32 row)
+{
+    QTW *table = _ui->wid_table;
+    QTWI *it0 = new QTWI(QString::number(entry.hSize()));
+    QTWI *it1 = new QTWI(QString::number(entry.position()));
+    QTWI *it2 = new QTWI(QString::number(entry.cSize()));
+    QTWI *it3 = new QTWI(QString::number(entry.oSize()));
+    QTWI *it4 = new QTWI(QString::number(entry.nameLength()));
+    QTWI *it5 = new QTWI(entry.name());
+    QTWI *it6 = new QTWI(QString(QCH::hash(entry.cData(), QCH::Sha1).toHex().toUpper()));
+
+    it0->setBackground(Red);
+    it0->setFlags(it0->flags() ^ Qt::ItemIsEditable);
+    it0->setTextAlignment(Qt::AlignRight);
+
+    it1->setBackground(Red);
+    it1->setFlags(it1->flags() ^ Qt::ItemIsEditable);
+    it1->setTextAlignment(Qt::AlignRight);
+
+    it2->setBackground(Red);
+    it2->setFlags(it2->flags() ^ Qt::ItemIsEditable);
+    it2->setTextAlignment(Qt::AlignRight);
+
+    it3->setBackground(Red);
+    it3->setFlags(it3->flags() ^ Qt::ItemIsEditable);
+    it3->setTextAlignment(Qt::AlignRight);
+
+    it4->setBackground(Red);
+    it4->setFlags(it4->flags() ^ Qt::ItemIsEditable);
+    it4->setTextAlignment(Qt::AlignRight);
+
+    it5->setBackground(Red);
+    it5->setTextAlignment(Qt::AlignLeft);
+
+    it6->setBackground(Red);
+    it6->setFlags(it6->flags() ^ Qt::ItemIsEditable);
+    it6->setTextAlignment(Qt::AlignLeft);
+
+    disconnect(table, SIGNAL(cellChanged(qint32, qint32)),
+               this, SLOT(slotTableChange(qint32)));
+
+    table->insertRow(row);
+    table->setItem(row, 0, it0);
+    table->setItem(row, 1, it1);
+    table->setItem(row, 2, it2);
+    table->setItem(row, 3, it3);
+    table->setItem(row, 4, it4);
+    table->setItem(row, 5, it5);
+    table->setItem(row, 6, it6);
+
+    connect(table, SIGNAL(cellChanged(qint32, qint32)),
+            this, SLOT(slotTableChange(qint32)));
+}
+
+void MainWindow::slotTableReplace(NCFile& entry, quint32 row)
+{
+    QTW *table = _ui->wid_table;
+
+    disconnect(table, SIGNAL(cellChanged(qint32, qint32)),
+               this, SLOT(slotTableChange(qint32)));
+
+    table->item(row, 0)->setText(QString::number(entry.hSize()));
+    table->item(row, 0)->setBackground(Red);
+
+    table->item(row, 1)->setText(QString::number(entry.position()));
+    table->item(row, 1)->setBackground(Red);
+
+    table->item(row, 2)->setText(QString::number(entry.cSize()));
+    table->item(row, 2)->setBackground(Red);
+
+    table->item(row, 3)->setText(QString::number(entry.oSize()));
+    table->item(row, 3)->setBackground(Red);
+
+    table->item(row, 4)->setText(QString::number(entry.nameLength()));
+    table->item(row, 4)->setBackground(Red);
+
+    table->item(row, 5)->setText(entry.name());
+    table->item(row, 5)->setBackground(Red);
+
+    table->item(row, 6)->setText(QString(QCH::hash(entry.cData(), QCH::Sha1).toHex().toUpper()));
+    table->item(row, 6)->setBackground(Red);
+
+    connect(table, SIGNAL(cellChanged(qint32, qint32)),
+            this, SLOT(slotTableChange(qint32)));
+}
+
+void MainWindow::slotTableRemove(quint32 row)
+{
+    _ui->wid_table->removeRow(row);
+}
+
+// --- protected methods ---
+
+void MainWindow::changeEvent(QEvent *event)
+{
+    if (event->type() == QEvent::LanguageChange)
+        _ui->retranslateUi(this);
+    else
+        QMainWindow::changeEvent(event);
+}
+
+void MainWindow::setLanguage(const Language::Type lang)
+{
+    QTranslator *translator = new QTranslator(this);
+
+    if (translator)
+    {
+        switch (lang)
+        {
+            case Language::French:
+                translator->load(":/Language/French");
+                break;
+            case Language::German:
+                translator->load(":/Language/German");
+                break;
+            case Language::English:
+            default:
+                translator->load(":/Language/English");
+        }
+
+        if (!translator->isEmpty())
+        {
+            qApp->removeTranslator(_translator);
+            delete _translator;
+            _translator = translator;
+            qApp->installTranslator(_translator);
+
+            _ui->act_lang_english->setEnabled(true);
+            _ui->act_lang_french->setEnabled(true);
+            _ui->act_lang_german->setEnabled(true);
+
+            switch (lang)
+            {
+                case Language::French:
+                    _ui->act_lang_french->setEnabled(false);
+                    break;
+                case Language::German:
+                    _ui->act_lang_german->setEnabled(false);
+                    break;
+                case Language::English:
+                default:
+                    _ui->act_lang_english->setEnabled(false);
+            }
+        }
+        else
+            delete translator;
+    }
+}
+
+void MainWindow::setMode(const Mode::Type mode)
+{
+    switch (mode)
+    {
+        case Mode::Fresh:
+            _filename = "";
+            _ui->act_app_open->setEnabled(true);
+            _ui->act_app_save->setEnabled(false);
+            _ui->act_app_saveas->setEnabled(false);
+            _ui->act_file_replace->setEnabled(false);
+            _ui->act_file_remove->setEnabled(false);
+            _ui->act_file_save_plain->setEnabled(false);
+            _ui->act_file_save_pak->setEnabled(false);
+            _ui->act_file_save_zlib->setEnabled(false);
+            _ui->act_file_conv_win->setEnabled(false);
+            _ui->act_file_conv_lin->setEnabled(false);
+            break;
+        case Mode::Edited:
+            _ui->act_app_open->setEnabled(false);
+            _ui->act_app_save->setEnabled(true);
+            _ui->act_app_saveas->setEnabled(true);
+            _ui->act_file_replace->setEnabled(false);
+            _ui->act_file_remove->setEnabled(false);
+            _ui->act_file_save_plain->setEnabled(false);
+            _ui->act_file_save_pak->setEnabled(false);
+            _ui->act_file_save_zlib->setEnabled(false);
+            break;
+        case Mode::Selected:
+            _ui->act_app_open->setEnabled(false);
+            _ui->act_app_save->setEnabled(true);
+            _ui->act_app_saveas->setEnabled(true);
+            _ui->act_file_replace->setEnabled(true);
+            _ui->act_file_remove->setEnabled(true);
+            _ui->act_file_save_plain->setEnabled(true);
+            _ui->act_file_save_pak->setEnabled(true);
+            _ui->act_file_save_zlib->setEnabled(true);
+            break;
+    }
+}
+
+void MainWindow::setupActions()
+{
+    QTW *table = _ui->wid_table;
+    QAction *ctx_sep1 = new QAction(this);
+    QAction *ctx_sep2 = new QAction(this);
+    QAction *ctx_sep3 = new QAction(this);
+
+    // all the nice connections we need for the GUI
+    connect(_ui->act_app_new, SIGNAL(triggered()),
+            this, SLOT(slotAppNew()));
+    connect(_ui->act_app_open, SIGNAL(triggered()),
+            this, SLOT(slotAppOpen()));
+    connect(_ui->act_app_save, SIGNAL(triggered()),
+            this, SLOT(slotAppSave()));
+    connect(_ui->act_app_saveas, SIGNAL(triggered()),
+            this, SLOT(slotAppSaveAs()));
+    connect(_ui->act_app_quit, SIGNAL(triggered()),
+            this, SLOT(slotAppQuit()));
+
+    connect(_ui->act_file_add, SIGNAL(triggered()),
+            this, SLOT(slotFileAdd()));
+    connect(_ui->act_file_replace, SIGNAL(triggered()),
+            this, SLOT(slotFileReplace()));
+    connect(_ui->act_file_remove, SIGNAL(triggered()),
+            this, SLOT(slotFileRemove()));
+    connect(_ui->act_file_save_plain, SIGNAL(triggered()),
+            this, SLOT(slotFileSavePlain()));
+    connect(_ui->act_file_save_pak, SIGNAL(triggered()),
+            this, SLOT(slotFileSavePAK()));
+    connect(_ui->act_file_save_zlib, SIGNAL(triggered()),
+            this, SLOT(slotFileSaveZLIB()));
+    connect(_ui->act_file_conv_win, SIGNAL(triggered()),
+            this, SLOT(slotFileConvWin()));
+    connect(_ui->act_file_conv_lin, SIGNAL(triggered()),
+            this, SLOT(slotFileConvLin()));
+
+    connect(_ui->act_lang_english, SIGNAL(triggered()),
+            this, SLOT(slotLangEnglish()));
+    connect(_ui->act_lang_french, SIGNAL(triggered()),
+            this, SLOT(slotLangFrench()));
+    connect(_ui->act_lang_german, SIGNAL(triggered()),
+            this, SLOT(slotLangGerman()));
+
+    connect(_ui->act_style_default, SIGNAL(triggered()),
+            this, SLOT(slotStyleDefault()));
+    connect(_ui->act_style_fusion, SIGNAL(triggered()),
+            this, SLOT(slotStyleFusion()));
+    connect(_ui->act_style_windows, SIGNAL(triggered()),
+            this, SLOT(slotStyleWindows()));
+    connect(_ui->act_style_gtk, SIGNAL(triggered()),
+            this, SLOT(slotStyleGTK()));
+
+    connect(_ui->act_about_app, SIGNAL(triggered()),
+            this, SLOT(slotAboutApp()));
+    connect(_ui->act_about_qt, SIGNAL(triggered()),
+            this, SLOT(slotAboutQt()));
+
+    // table update connections
+    connect(table, SIGNAL(cellPressed(qint32, qint32)),
+            this, SLOT(slotTableSelected()));
+    connect(table, SIGNAL(cellChanged(qint32, qint32)),
+            this, SLOT(slotTableChange(qint32)));
+
+    connect(_vfs, SIGNAL(sigFileStatus(quint32, quint32, ColType)),
+            this, SLOT(slotTableStatus(quint32, quint32, ColType)));
+    connect(_vfs, SIGNAL(sigFileAdd(NCFile&, quint32)),
+            this, SLOT(slotTableAdd(NCFile&, quint32)));
+    connect(_vfs, SIGNAL(sigFileReplace(NCFile&, quint32)),
+            this, SLOT(slotTableReplace(NCFile&, quint32)));
+    connect(_vfs, SIGNAL(sigFileRemove(quint32)),
+            this, SLOT(slotTableRemove(quint32)));
+
+    // context menus for the QTableWidget
+    ctx_sep1->setSeparator(true);
+    ctx_sep2->setSeparator(true);
+    ctx_sep3->setSeparator(true);
+
+    table->addAction(_ui->act_file_add);
+    table->addAction(ctx_sep1);
+    table->addAction(_ui->act_file_replace);
+    table->addAction(_ui->act_file_remove);
+    table->addAction(ctx_sep2);
+    table->addAction(_ui->act_file_save_plain);
+    table->addAction(_ui->act_file_save_pak);
+    table->addAction(_ui->act_file_save_zlib);
+    table->addAction(ctx_sep3);
+    table->addAction(_ui->act_file_conv_win);
+    table->addAction(_ui->act_file_conv_lin);
+}
diff --git a/editor/MainWindow.hxx b/editor/MainWindow.hxx
new file mode 100644 (file)
index 0000000..e598ff3
--- /dev/null
@@ -0,0 +1,95 @@
+#pragma once
+
+#include <QMainWindow>
+#include "common/NCTypes.hxx"
+
+namespace Ui
+{
+    class MainWindow;
+}
+class QSettings;
+class QTranslator;
+class NCFile;
+class NCFilesystem;
+
+namespace Language
+{
+    enum Type : quint8 {
+        English,
+        French,
+        German
+    };
+}
+
+namespace Mode
+{
+    enum Type : quint8 {
+        Fresh,
+        Edited,
+        Selected
+    };
+}
+
+class MainWindow : public QMainWindow {
+    Q_OBJECT
+public:
+    // internal typedefs
+    using ColType = NC::FileData::Type;
+
+    // constructors and deconstructors
+    explicit MainWindow(QWidget *parent = 0);
+    virtual ~MainWindow();
+
+public slots:
+    // public slots
+    void slotAppNew();
+    void slotAppOpen();
+    void slotAppSave();
+    void slotAppSaveAs();
+    void slotAppQuit();
+
+    void slotFileAdd();
+    void slotFileReplace();
+    void slotFileRemove();
+    void slotFileSavePlain();
+    void slotFileSavePAK();
+    void slotFileSaveZLIB();
+    void slotFileConvWin();
+    void slotFileConvLin();
+
+    void slotLangEnglish();
+    void slotLangFrench();
+    void slotLangGerman();
+
+    void slotStyleDefault();
+    void slotStyleFusion();
+    void slotStyleWindows();
+    void slotStyleGTK();
+
+    void slotAboutApp();
+    void slotAboutQt();
+
+    void slotStatusUpdate();
+
+    void slotTableSelected();
+    void slotTableChange(qint32 row);
+    void slotTableStatus(quint32 from_row, quint32 to_row, ColType cols);
+    void slotTableAdd(NCFile& entry, quint32 row);
+    void slotTableReplace(NCFile& entry, quint32 row);
+    void slotTableRemove(quint32 row);
+
+protected:
+    // protected stuff
+    virtual void changeEvent(QEvent *event);
+    void setLanguage(const Language::Type lang = Language::English);
+    void setMode(const Mode::Type mode = Mode::Fresh);
+    void setupActions();
+
+private:
+    Ui::MainWindow *_ui;
+    QTranslator *_translator;
+    QSettings *_settings;
+    NCFilesystem *_vfs;
+    QVector<QPair<qint32, qint32>> _marks;
+    QString _filename;
+};
diff --git a/editor/MainWindow.ui b/editor/MainWindow.ui
new file mode 100644 (file)
index 0000000..5b36234
--- /dev/null
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>FRM-MAINWINDOW</string>
+  </property>
+  <property name="locale">
+   <locale language="English" country="UnitedStates"/>
+  </property>
+  <widget class="QWidget" name="wid_center">
+   <layout class="QGridLayout" name="gridLayout">
+    <property name="leftMargin">
+     <number>0</number>
+    </property>
+    <property name="topMargin">
+     <number>0</number>
+    </property>
+    <property name="rightMargin">
+     <number>0</number>
+    </property>
+    <property name="bottomMargin">
+     <number>0</number>
+    </property>
+    <item row="0" column="0">
+     <widget class="QTableWidget" name="wid_table">
+      <property name="contextMenuPolicy">
+       <enum>Qt::ActionsContextMenu</enum>
+      </property>
+      <property name="selectionMode">
+       <enum>QAbstractItemView::SingleSelection</enum>
+      </property>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>800</width>
+     <height>25</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="mm_app">
+    <property name="title">
+     <string>MM-APP</string>
+    </property>
+    <addaction name="act_app_new"/>
+    <addaction name="separator"/>
+    <addaction name="act_app_open"/>
+    <addaction name="act_app_save"/>
+    <addaction name="act_app_saveas"/>
+    <addaction name="separator"/>
+    <addaction name="act_app_quit"/>
+   </widget>
+   <widget class="QMenu" name="mm_about">
+    <property name="title">
+     <string>MM-ABOUT</string>
+    </property>
+    <addaction name="act_about_app"/>
+    <addaction name="act_about_qt"/>
+   </widget>
+   <widget class="QMenu" name="mm_conf">
+    <property name="title">
+     <string>MM-CONF</string>
+    </property>
+    <widget class="QMenu" name="sm_lang">
+     <property name="title">
+      <string>SM-LANG</string>
+     </property>
+     <addaction name="act_lang_english"/>
+     <addaction name="act_lang_french"/>
+     <addaction name="act_lang_german"/>
+    </widget>
+    <widget class="QMenu" name="sm_style">
+     <property name="title">
+      <string>SM-STYLE</string>
+     </property>
+     <addaction name="act_style_default"/>
+     <addaction name="separator"/>
+     <addaction name="act_style_fusion"/>
+     <addaction name="act_style_windows"/>
+     <addaction name="act_style_gtk"/>
+    </widget>
+    <addaction name="sm_lang"/>
+    <addaction name="sm_style"/>
+   </widget>
+   <widget class="QMenu" name="mm_file">
+    <property name="title">
+     <string>MM-FILE</string>
+    </property>
+    <addaction name="act_file_add"/>
+    <addaction name="separator"/>
+    <addaction name="act_file_replace"/>
+    <addaction name="act_file_remove"/>
+    <addaction name="separator"/>
+    <addaction name="act_file_save_plain"/>
+    <addaction name="act_file_save_pak"/>
+    <addaction name="act_file_save_zlib"/>
+    <addaction name="separator"/>
+    <addaction name="act_file_conv_win"/>
+    <addaction name="act_file_conv_lin"/>
+   </widget>
+   <addaction name="mm_app"/>
+   <addaction name="mm_file"/>
+   <addaction name="mm_conf"/>
+   <addaction name="mm_about"/>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <action name="act_app_quit">
+   <property name="text">
+    <string>ME-APP-QUIT</string>
+   </property>
+  </action>
+  <action name="act_app_new">
+   <property name="text">
+    <string>ME-APP-NEW</string>
+   </property>
+  </action>
+  <action name="act_app_open">
+   <property name="text">
+    <string>ME-APP-OPEN</string>
+   </property>
+  </action>
+  <action name="act_app_save">
+   <property name="text">
+    <string>ME-APP-SAVE</string>
+   </property>
+  </action>
+  <action name="act_app_saveas">
+   <property name="text">
+    <string>ME-APP-SAVEAS</string>
+   </property>
+  </action>
+  <action name="act_about_app">
+   <property name="text">
+    <string>ME-ABOUT-APP</string>
+   </property>
+  </action>
+  <action name="act_about_qt">
+   <property name="text">
+    <string>ME-ABOUT-QT</string>
+   </property>
+  </action>
+  <action name="act_lang_english">
+   <property name="text">
+    <string>ME-LANG-ENGLISH</string>
+   </property>
+  </action>
+  <action name="act_lang_french">
+   <property name="text">
+    <string>ME-LANG-FRENCH</string>
+   </property>
+  </action>
+  <action name="act_lang_german">
+   <property name="text">
+    <string>ME-LANG-GERMAN</string>
+   </property>
+  </action>
+  <action name="act_style_default">
+   <property name="text">
+    <string>ME-STYLE-DEFAULT</string>
+   </property>
+  </action>
+  <action name="act_style_windows">
+   <property name="text">
+    <string>ME-STYLE-WINDOWS</string>
+   </property>
+  </action>
+  <action name="act_style_gtk">
+   <property name="text">
+    <string>ME-STYLE-GTK</string>
+   </property>
+  </action>
+  <action name="act_style_fusion">
+   <property name="text">
+    <string>ME-STYLE-FUSION</string>
+   </property>
+  </action>
+  <action name="act_file_add">
+   <property name="text">
+    <string>ME-FILE-ADD</string>
+   </property>
+  </action>
+  <action name="act_file_replace">
+   <property name="text">
+    <string>ME-FILE-REPLACE</string>
+   </property>
+  </action>
+  <action name="act_file_remove">
+   <property name="text">
+    <string>ME-FILE-REMOVE</string>
+   </property>
+  </action>
+  <action name="act_file_save_plain">
+   <property name="text">
+    <string>ME-FILE-SAVE-PLAIN</string>
+   </property>
+  </action>
+  <action name="act_file_save_pak">
+   <property name="text">
+    <string>ME-FILE-SAVE-PAK</string>
+   </property>
+  </action>
+  <action name="act_file_save_zlib">
+   <property name="text">
+    <string>ME-FILE-SAVE-ZLIB</string>
+   </property>
+  </action>
+  <action name="act_file_conv_win">
+   <property name="text">
+    <string>ME-FILE-CONV-WIN</string>
+   </property>
+  </action>
+  <action name="act_file_conv_lin">
+   <property name="text">
+    <string>ME-FILE-CONV-LIN</string>
+   </property>
+  </action>
+ </widget>
+ <resources>
+  <include location="Resources.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/editor/Resources.qrc b/editor/Resources.qrc
new file mode 100644 (file)
index 0000000..41680fc
--- /dev/null
@@ -0,0 +1,7 @@
+<RCC>
+  <qresource prefix="Language">
+    <file alias="English">i18n_english.qm</file>
+    <file alias="French">i18n_french.qm</file>
+    <file alias="German">i18n_german.qm</file>
+  </qresource>
+</RCC>
diff --git a/editor/i18n_english.ts b/editor/i18n_english.ts
new file mode 100644 (file)
index 0000000..a2e3ebd
--- /dev/null
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="en_US">
+<context>
+    <name>MainWindow</name>
+    <message>
+        <location filename="MainWindow.ui" line="14"/>
+        <location filename="MainWindow.cxx" line="221"/>
+        <source>FRM-MAINWINDOW</source>
+        <translation>QTinNS Editor</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="56"/>
+        <source>MM-APP</source>
+        <translation>Filesystem</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="68"/>
+        <source>MM-ABOUT</source>
+        <translation>About</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="75"/>
+        <source>MM-CONF</source>
+        <translation>Configuration</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="79"/>
+        <source>SM-LANG</source>
+        <translation>Language</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="87"/>
+        <source>SM-STYLE</source>
+        <translation>GUI Style</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="100"/>
+        <source>MM-FILE</source>
+        <translation>File</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="122"/>
+        <source>ME-APP-QUIT</source>
+        <translation>Quit</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="127"/>
+        <source>ME-APP-NEW</source>
+        <translation>New virtual filesystem</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="132"/>
+        <source>ME-APP-OPEN</source>
+        <translation>Open virtual filesystem ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="137"/>
+        <source>ME-APP-SAVE</source>
+        <translation>Save virtual filesystem</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="142"/>
+        <source>ME-APP-SAVEAS</source>
+        <translation>Save virtual filesystem as ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="147"/>
+        <source>ME-ABOUT-APP</source>
+        <translation>About QTinNS Editor ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="152"/>
+        <source>ME-ABOUT-QT</source>
+        <translation>About Qt framework ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="157"/>
+        <source>ME-LANG-ENGLISH</source>
+        <translation>English</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="162"/>
+        <source>ME-LANG-FRENCH</source>
+        <translation>French</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="167"/>
+        <source>ME-LANG-GERMAN</source>
+        <translation>German</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="172"/>
+        <source>ME-STYLE-DEFAULT</source>
+        <translation>System Default</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="177"/>
+        <source>ME-STYLE-WINDOWS</source>
+        <oldsource>ME-STYLE-CDE</oldsource>
+        <translation>Windows</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="182"/>
+        <source>ME-STYLE-GTK</source>
+        <oldsource>ME-STYLE-CLEANLOOKS</oldsource>
+        <translation>GTK+</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="187"/>
+        <source>ME-STYLE-FUSION</source>
+        <oldsource>ME-STYLE-PLASTIQUE</oldsource>
+        <translation>Fusion</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="192"/>
+        <source>ME-FILE-ADD</source>
+        <translation>Add file(s) ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="197"/>
+        <source>ME-FILE-REPLACE</source>
+        <translation>Replace file ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="202"/>
+        <source>ME-FILE-REMOVE</source>
+        <translation>Remove file ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="207"/>
+        <source>ME-FILE-SAVE-PLAIN</source>
+        <translation>Save plain file as ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="212"/>
+        <source>ME-FILE-SAVE-PAK</source>
+        <translation>Save PAK file as ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="217"/>
+        <source>ME-FILE-SAVE-ZLIB</source>
+        <translation>Save ZLIB file as ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="222"/>
+        <source>ME-FILE-CONV-WIN</source>
+        <translation>Convert UNIX path to Windows path</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="227"/>
+        <source>ME-FILE-CONV-LIN</source>
+        <translation>Convert Windows path to UNIX path</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="122"/>
+        <location filename="MainWindow.cxx" line="133"/>
+        <source>DIA-OPEN-FILE</source>
+        <translation>Open files ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="86"/>
+        <location filename="MainWindow.cxx" line="106"/>
+        <source>FLT-FILE-VFS</source>
+        <oldsource>DIA-SAVE-FILE</oldsource>
+        <translation>Neocron VFS file (*.vfs *.pak)</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="86"/>
+        <source>DIA-OPEN-VFS</source>
+        <translation>Open virtual filesystem ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="106"/>
+        <source>DIA-SAVE-VFS</source>
+        <translation>Save virtual filesystem ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="122"/>
+        <location filename="MainWindow.cxx" line="133"/>
+        <source>FLT-FILE-ANY</source>
+        <translation>File (*.*)</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="151"/>
+        <source>DIA-SAVE-PLAIN</source>
+        <translation>Save file ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="151"/>
+        <source>FLT-FILE-PLAIN</source>
+        <translation>File (*.*)</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="159"/>
+        <source>DIA-SAVE-PAK</source>
+        <translation>Save PAK file ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="159"/>
+        <source>FLT-FILE-PAK</source>
+        <translation>Neocron PAK file (*.pak)</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="167"/>
+        <source>DIA-SAVE-ZLIB</source>
+        <translation>Save ZLIB file ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="167"/>
+        <source>FLT-FILE-ZLIB</source>
+        <translation>ZLIB file (*.z)</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="219"/>
+        <source>MSG-UPD-FILE</source>
+        <translation>Virtual filesystem</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="219"/>
+        <source>MSG-UPD-WITH</source>
+        <translation>with</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="220"/>
+        <source>MSG-UPD-ENTRIES</source>
+        <translation>entries</translation>
+    </message>
+</context>
+</TS>
diff --git a/editor/i18n_french.ts b/editor/i18n_french.ts
new file mode 100644 (file)
index 0000000..2d3c6ee
--- /dev/null
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="fr_FR">
+<context>
+    <name>MainWindow</name>
+    <message>
+        <location filename="MainWindow.ui" line="14"/>
+        <location filename="MainWindow.cxx" line="221"/>
+        <source>FRM-MAINWINDOW</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="56"/>
+        <source>MM-APP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="68"/>
+        <source>MM-ABOUT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="75"/>
+        <source>MM-CONF</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="79"/>
+        <source>SM-LANG</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="87"/>
+        <source>SM-STYLE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="100"/>
+        <source>MM-FILE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="122"/>
+        <source>ME-APP-QUIT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="127"/>
+        <source>ME-APP-NEW</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="132"/>
+        <source>ME-APP-OPEN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="137"/>
+        <source>ME-APP-SAVE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="142"/>
+        <source>ME-APP-SAVEAS</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="147"/>
+        <source>ME-ABOUT-APP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="152"/>
+        <source>ME-ABOUT-QT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="157"/>
+        <source>ME-LANG-ENGLISH</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="162"/>
+        <source>ME-LANG-FRENCH</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="167"/>
+        <source>ME-LANG-GERMAN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="172"/>
+        <source>ME-STYLE-DEFAULT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="177"/>
+        <source>ME-STYLE-WINDOWS</source>
+        <oldsource>ME-STYLE-CDE</oldsource>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="182"/>
+        <source>ME-STYLE-GTK</source>
+        <oldsource>ME-STYLE-CLEANLOOKS</oldsource>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="187"/>
+        <source>ME-STYLE-FUSION</source>
+        <oldsource>ME-STYLE-PLASTIQUE</oldsource>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="192"/>
+        <source>ME-FILE-ADD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="197"/>
+        <source>ME-FILE-REPLACE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="202"/>
+        <source>ME-FILE-REMOVE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="207"/>
+        <source>ME-FILE-SAVE-PLAIN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="212"/>
+        <source>ME-FILE-SAVE-PAK</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="217"/>
+        <source>ME-FILE-SAVE-ZLIB</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="222"/>
+        <source>ME-FILE-CONV-WIN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="227"/>
+        <source>ME-FILE-CONV-LIN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="122"/>
+        <location filename="MainWindow.cxx" line="133"/>
+        <source>DIA-OPEN-FILE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="86"/>
+        <location filename="MainWindow.cxx" line="106"/>
+        <source>FLT-FILE-VFS</source>
+        <oldsource>DIA-SAVE-FILE</oldsource>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="86"/>
+        <source>DIA-OPEN-VFS</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="106"/>
+        <source>DIA-SAVE-VFS</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="122"/>
+        <location filename="MainWindow.cxx" line="133"/>
+        <source>FLT-FILE-ANY</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="151"/>
+        <source>DIA-SAVE-PLAIN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="151"/>
+        <source>FLT-FILE-PLAIN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="159"/>
+        <source>DIA-SAVE-PAK</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="159"/>
+        <source>FLT-FILE-PAK</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="167"/>
+        <source>DIA-SAVE-ZLIB</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="167"/>
+        <source>FLT-FILE-ZLIB</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="219"/>
+        <source>MSG-UPD-FILE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="219"/>
+        <source>MSG-UPD-WITH</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="220"/>
+        <source>MSG-UPD-ENTRIES</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+</TS>
diff --git a/editor/i18n_german.ts b/editor/i18n_german.ts
new file mode 100644 (file)
index 0000000..b62454c
--- /dev/null
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="de_DE">
+<context>
+    <name>MainWindow</name>
+    <message>
+        <location filename="MainWindow.ui" line="14"/>
+        <location filename="MainWindow.cxx" line="221"/>
+        <source>FRM-MAINWINDOW</source>
+        <translation>QTinNS Editor</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="56"/>
+        <source>MM-APP</source>
+        <translation>Dateisystem</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="68"/>
+        <source>MM-ABOUT</source>
+        <translation>Über</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="75"/>
+        <source>MM-CONF</source>
+        <translation>Konfiguration</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="79"/>
+        <source>SM-LANG</source>
+        <translation>Sprache</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="87"/>
+        <source>SM-STYLE</source>
+        <translation>GUI Stil</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="100"/>
+        <source>MM-FILE</source>
+        <translation>Datei</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="122"/>
+        <source>ME-APP-QUIT</source>
+        <translation>Beenden</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="127"/>
+        <source>ME-APP-NEW</source>
+        <translation>Neues virtuelles Dateisystem</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="132"/>
+        <source>ME-APP-OPEN</source>
+        <translation>Virtuelles Dateisystem Ã¶ffnen ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="137"/>
+        <source>ME-APP-SAVE</source>
+        <translation>Virtuelles Dateisystem speichern</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="142"/>
+        <source>ME-APP-SAVEAS</source>
+        <translation>Virtuelles Dateisystem speichern als ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="147"/>
+        <source>ME-ABOUT-APP</source>
+        <translation>Über QTinNS Editor ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="152"/>
+        <source>ME-ABOUT-QT</source>
+        <translation>Über Qt Framework ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="157"/>
+        <source>ME-LANG-ENGLISH</source>
+        <translation>Englisch</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="162"/>
+        <source>ME-LANG-FRENCH</source>
+        <translation>Französisch</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="167"/>
+        <source>ME-LANG-GERMAN</source>
+        <translation>Deutsch</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="172"/>
+        <source>ME-STYLE-DEFAULT</source>
+        <translation>System Standard</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="177"/>
+        <source>ME-STYLE-WINDOWS</source>
+        <oldsource>ME-STYLE-CDE</oldsource>
+        <translation>Windows</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="182"/>
+        <source>ME-STYLE-GTK</source>
+        <oldsource>ME-STYLE-CLEANLOOKS</oldsource>
+        <translation>GTK+</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="187"/>
+        <source>ME-STYLE-FUSION</source>
+        <oldsource>ME-STYLE-PLASTIQUE</oldsource>
+        <translation>Fusion</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="192"/>
+        <source>ME-FILE-ADD</source>
+        <translation>Füge Datei(en) hinzu ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="197"/>
+        <source>ME-FILE-REPLACE</source>
+        <translation>Ersetze Datei ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="202"/>
+        <source>ME-FILE-REMOVE</source>
+        <translation>Entferne Datei ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="207"/>
+        <source>ME-FILE-SAVE-PLAIN</source>
+        <translation>Speichere normale Datei als ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="212"/>
+        <source>ME-FILE-SAVE-PAK</source>
+        <translation>Speichere PAK Datei als ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="217"/>
+        <source>ME-FILE-SAVE-ZLIB</source>
+        <translation>Speichere ZLIB Datei als ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="222"/>
+        <source>ME-FILE-CONV-WIN</source>
+        <translation>Konvertiere UNIX Pfad nach Windows</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.ui" line="227"/>
+        <source>ME-FILE-CONV-LIN</source>
+        <translation>Konvertiere Windows Pfad nach UNIX</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="122"/>
+        <location filename="MainWindow.cxx" line="133"/>
+        <source>DIA-OPEN-FILE</source>
+        <translation>Datei Ã¶ffnen ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="86"/>
+        <location filename="MainWindow.cxx" line="106"/>
+        <source>FLT-FILE-VFS</source>
+        <oldsource>DIA-SAVE-FILE</oldsource>
+        <translation>Neocron VFS Datei (*.vfs *.pak)</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="86"/>
+        <source>DIA-OPEN-VFS</source>
+        <translation>Virtuelles Dateisystem Ã¶ffnen ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="106"/>
+        <source>DIA-SAVE-VFS</source>
+        <translation>Virtuelles Dateisystem speichern ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="122"/>
+        <location filename="MainWindow.cxx" line="133"/>
+        <source>FLT-FILE-ANY</source>
+        <translation>Datei (*.*)</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="151"/>
+        <source>DIA-SAVE-PLAIN</source>
+        <translation>Datei speichern ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="151"/>
+        <source>FLT-FILE-PLAIN</source>
+        <translation>Datei (*.*)</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="159"/>
+        <source>DIA-SAVE-PAK</source>
+        <translation>PAK Datei speichern ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="159"/>
+        <source>FLT-FILE-PAK</source>
+        <translation>Neocron PAK Datei (*.pak)</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="167"/>
+        <source>DIA-SAVE-ZLIB</source>
+        <translation>ZLIB Datei speichern ...</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="167"/>
+        <source>FLT-FILE-ZLIB</source>
+        <translation>ZLIB Datei (*.z)</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="219"/>
+        <source>MSG-UPD-FILE</source>
+        <translation>Virtuelles Dateisystem</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="219"/>
+        <source>MSG-UPD-WITH</source>
+        <translation>mit</translation>
+    </message>
+    <message>
+        <location filename="MainWindow.cxx" line="220"/>
+        <source>MSG-UPD-ENTRIES</source>
+        <translation>Einträgen</translation>
+    </message>
+</context>
+</TS>
diff --git a/filesystem/CMakeLists.txt b/filesystem/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a6798f4
--- /dev/null
@@ -0,0 +1,17 @@
+# Qt sources
+set                   (NCF_SOURCES FileIdentifier.cxx NCFile.cxx NCFilesystem.cxx)
+set                   (NCF_HEADERS FileIdentifier.hxx NCFile.hxx NCFilesystem.hxx)
+set                   (NCF_UI_FILES )
+set                   (NCF_RES_FILES )
+set                   (E_TS_FILES )
+
+# wrappers
+QT5_ADD_TRANSLATION   (NCF_TS_SOURCES ${NCF_TS_FILES})
+QT5_WRAP_CPP          (NCF_MOC_SOURCES ${NCF_HEADERS})
+QT5_WRAP_UI           (NCF_UI_SOURCES ${NCF_UI_FILES})
+QT5_ADD_RESOURCES     (NCF_RES_SOURCES ${NCF_RES_FILES})
+
+# libs
+add_library           (Filesystem ${NCF_SOURCES} ${NCF_MOC_SOURCES} ${NCF_UI_SOURCES} ${NCF_RES_SOURCES}
+                       ${NCF_TS_SOURCES})
+qt5_use_modules       (Filesystem Core)
diff --git a/filesystem/FileIdentifier.cxx b/filesystem/FileIdentifier.cxx
new file mode 100644 (file)
index 0000000..b0146f7
--- /dev/null
@@ -0,0 +1,97 @@
+#include <QFile>
+#include "filesystem/FileIdentifier.hxx"
+
+// --- internal typedefs ---
+
+using IdentifyType = NC::Identify::Type;
+
+// --- constructors and deconstructors ---
+
+FileIdentifier::FileIdentifier(QObject *parent)
+: QObject(parent)
+{
+}
+
+FileIdentifier::~FileIdentifier()
+{
+}
+
+// --- public methodes ---
+
+IdentifyType FileIdentifier::identify(const QString& filename) const
+{
+    QFile ifile(filename);
+    quint32 data = 0;
+
+    if (!ifile.exists())
+        return IdentifyType::Error;
+
+    if (ifile.open(QIODevice::ReadOnly))
+    {
+        ifile.read(reinterpret_cast<char *>(&data), sizeof (data));
+        ifile.close();
+    }
+    else
+        return IdentifyType::Error;
+
+    return identifyData(data);
+}
+
+IdentifyType FileIdentifier::identify(const QByteArray& stream) const
+{
+    if (stream.size() < static_cast<const qint32>(sizeof (quint32)))
+        return IdentifyType::Error;
+
+    QByteArray data = stream.left(sizeof (quint32));
+
+    return identifyData(data.toUInt());
+}
+
+QString FileIdentifier::identifyName(const IdentifyType& type) const
+{
+    switch (type)
+    {
+        case Error:
+            return "file error";
+        case Plain:
+            return "common file";
+        case ZLIB:
+            return "ZLIB compressed file";
+        case VFS:
+            return "Neocron VFS file";
+        case PAK1:
+            return "Neocron PAK file";
+        case PAK2:
+            return "Neocron PAK file (2nd header)";
+        case PAK3:
+            return "Neocron PAK file (3rd header)";
+        default:
+            return "unknown file format";
+    }
+}
+
+QString FileIdentifier::identifyName(const QString& filename) const
+{
+    return identifyName(identify(filename));
+}
+
+QString FileIdentifier::identifyName(const QByteArray& stream) const
+{
+    return identifyName(identify(stream));
+}
+
+// --- protected methods ---
+
+IdentifyType FileIdentifier::identifyData(const quint32& data) const
+{
+    if (data == IdentifyType::VFS)
+        return IdentifyType::VFS;
+
+    if (data == IdentifyType::PAK1)
+        return IdentifyType::PAK1;
+
+    if (reinterpret_cast<const char *>(&data)[0] == 'x')
+        return IdentifyType::ZLIB;
+
+    return IdentifyType::Plain;
+}
diff --git a/filesystem/FileIdentifier.hxx b/filesystem/FileIdentifier.hxx
new file mode 100644 (file)
index 0000000..52ec6f9
--- /dev/null
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <QObject>
+#include "common/NCTypes.hxx"
+
+class FileIdentifier : public QObject {
+    Q_OBJECT
+public:
+    // internal typedefs
+    using IdentifyType = NC::Identify::Type;
+
+    // internal constants
+    enum : quint32 {
+        Error = NC::Meta::Error,
+        VFS = NC::Identify::VFS,
+        PAK1 = NC::Identify::PAK1,
+        PAK2 = NC::Identify::PAK2,
+        PAK3 = NC::Identify::PAK3,
+        Plain = NC::Identify::Plain,
+        ZLIB = NC::Identify::ZLIB
+    };
+
+    // constructors and deconstructors
+    FileIdentifier(QObject *parent = 0);
+    FileIdentifier(const FileIdentifier& rhs) = delete;
+    FileIdentifier(const FileIdentifier&& rhs) = delete;
+    virtual ~FileIdentifier();
+
+    // operators
+    FileIdentifier& operator=(const FileIdentifier& rhs) = delete;
+    FileIdentifier& operator=(const FileIdentifier&& rhs) = delete;
+
+    // public methodes
+    IdentifyType identify(const QString& filename) const;
+    IdentifyType identify(const QByteArray& stream) const;
+
+    QString identifyName(const IdentifyType& type) const;
+    QString identifyName(const QString& filename) const;
+    QString identifyName(const QByteArray& stream) const;
+
+protected:
+    // protected methods
+    IdentifyType identifyData(const quint32& data) const;
+};
diff --git a/filesystem/NCFile.cxx b/filesystem/NCFile.cxx
new file mode 100644 (file)
index 0000000..ad0d369
--- /dev/null
@@ -0,0 +1,197 @@
+#include <QDataStream>
+#include "filesystem/NCFile.hxx"
+
+/*
+ * qCompress()/qUncompress() does not behave like the real zlib compress2/uncompress functions,
+ * the Qt functions add a quint32 at the beginning of the QByteArray holding the original size
+ */
+
+// --- internal constants and stuff ---
+
+const quint8 All = NC::FileData::HSize | NC::FileData::Position | NC::FileData::CSize | NC::FileData::OSize
+                 | NC::FileData::NameLength | NC::FileData::Name | NC::FileData::CData | NC::FileData::OData;
+
+// --- constructors and deconstructors ---
+
+NCFile::NCFile(QObject *parent,
+               const quint32& hsize, const quint32& pos, const quint32& csize, const quint32& osize,
+               const quint32& nlen, const QString& name, const QByteArray& cdata)
+: QObject(parent), _hsize(hsize), _position(pos), _csize(csize), _osize(osize), _name_length(nlen), _name(name),
+  _cdata(cdata)
+{
+    emit sigFileData(this, All);
+}
+
+NCFile::NCFile(const NCFile& rhs)
+: QObject(rhs.parent()), _hsize(rhs._hsize), _position(rhs._position), _csize(rhs._csize), _osize(rhs._osize),
+  _name_length(rhs._name_length), _name(rhs._name), _cdata(rhs._cdata)
+{
+    emit sigFileData(this, All);
+}
+
+NCFile::NCFile(const NCFile&& rhs)
+: QObject(std::move(rhs.parent())), _hsize(std::move(rhs._hsize)), _position(std::move(rhs._position)),
+  _csize(std::move(rhs._csize)), _osize(std::move(rhs._osize)), _name_length(std::move(rhs._name_length)),
+  _name(std::move(rhs._name)), _cdata(std::move(rhs._cdata))
+{
+    emit sigFileData(this, All);
+}
+
+NCFile::~NCFile()
+{
+}
+
+// --- operators ---
+
+NCFile& NCFile::operator=(const NCFile& rhs)
+{
+    if (this != &rhs)
+    {
+        _hsize = rhs._hsize;
+        _position = rhs._position;
+        _csize = rhs._csize;
+        _osize = rhs._osize;
+        _name_length = rhs._name_length;
+        _name = rhs._name;
+        _cdata = rhs._cdata;
+    }
+
+    return *this;
+}
+
+NCFile& NCFile::operator=(const NCFile&& rhs)
+{
+    if (this != &rhs)
+    {
+        _hsize = std::move(rhs._hsize);
+        _position = std::move(rhs._position);
+        _csize = std::move(rhs._csize);
+        _osize = std::move(rhs._osize);
+        _name_length = std::move(rhs._name_length);
+        _name = std::move(rhs._name);
+        _cdata = std::move(rhs._cdata);
+    }
+
+    return *this;
+}
+
+bool NCFile::operator==(const NCFile& rhs) const
+{
+    return _hsize == rhs._hsize
+           && _position == rhs._position
+           && _csize == rhs._csize
+           && _osize == rhs._osize
+           && _name_length == rhs._name_length
+           && _name == rhs._name
+           && _cdata == rhs._cdata;
+}
+
+bool NCFile::operator!=(const NCFile& rhs) const
+{
+    return !(*this == rhs);
+}
+
+// --- setters and getters ---
+
+void NCFile::setHSize(const quint32& size)
+{
+    _hsize = size;
+}
+
+quint32 NCFile::hSize() const
+{
+    return _hsize;
+}
+
+void NCFile::setPosition(const quint32& pos)
+{
+    _position = pos;
+}
+
+quint32 NCFile::position() const
+{
+    return _position;
+}
+
+void NCFile::setCSize(const quint32& size)
+{
+    _csize = size;
+}
+
+quint32 NCFile::cSize() const
+{
+    return _csize;
+}
+
+void NCFile::setOSize(const quint32& size)
+{
+    _osize = size;
+}
+
+quint32 NCFile::oSize() const
+{
+    return _osize;
+}
+
+void NCFile::setNameLength(const quint32& len)
+{
+    _name_length = len;
+}
+
+quint32 NCFile::nameLength() const
+{
+    return _name_length;
+}
+
+void NCFile::setName(const QString& name)
+{
+    _name = name;
+}
+
+QString NCFile::name() const
+{
+    return _name;
+}
+
+void NCFile::setCData(const QByteArray& data)
+{
+    _cdata = data;
+}
+
+QByteArray NCFile::cData() const
+{
+    return _cdata;
+}
+
+void NCFile::setOData(const QByteArray& data)
+{
+    _cdata = qCompress(data, 9).mid(sizeof (quint32));
+}
+
+QByteArray NCFile::oData() const
+{
+    QByteArray size;
+    QDataStream stream(&size, QIODevice::WriteOnly);
+
+    stream << _csize;
+
+    return qUncompress(size + _cdata);
+}
+
+// --- status methods ---
+
+quint32 NCFile::size() const
+{
+    return HSize + _name_length + _cdata.size();
+}
+
+quint32 NCFile::headerSize() const
+{
+    return HSize;
+}
+
+bool NCFile::valid() const
+{
+    return !(_hsize == Error || _position == Error || _csize == Error || _osize == Error || _name_length == Error
+           || _name.isEmpty() || _cdata.isEmpty());
+}
diff --git a/filesystem/NCFile.hxx b/filesystem/NCFile.hxx
new file mode 100644 (file)
index 0000000..e0756d6
--- /dev/null
@@ -0,0 +1,74 @@
+#pragma once
+
+#include <QObject>
+#include "common/NCTypes.hxx"
+
+class NCFile : public QObject {
+    Q_OBJECT
+signals:
+    void sigFileData(NCFile *file, quint8 type);
+
+public:
+    // internal typedefs
+    enum : quint32 {
+        Error = NC::Meta::Error,
+        HSize = sizeof (quint32) * 5
+    };
+
+    // constructors and deconstructors
+    //explicit NCFile();
+    NCFile(QObject *parent = 0,
+           const quint32& hsize = Error, const quint32& pos = Error, const quint32& csize = Error,
+           const quint32& osize = Error, const quint32& nlen = Error, const QString& name = "",
+           const QByteArray& cdata = "");
+    NCFile(const NCFile& rhs);
+    NCFile(const NCFile&& rhs);
+    virtual ~NCFile();
+
+    // operators
+    NCFile& operator=(const NCFile& rhs);
+    NCFile& operator=(const NCFile&& rhs);
+
+    bool operator==(const NCFile& rhs) const;
+    bool operator!=(const NCFile& rhs) const;
+
+    // setters and getters
+    void setHSize(const quint32& size = 0);
+    quint32 hSize() const;
+
+    void setPosition(const quint32& pos = 0);
+    quint32 position() const;
+
+    void setCSize(const quint32& size = 0);
+    quint32 cSize() const;
+
+    void setOSize(const quint32& size = 0);
+    quint32 oSize() const;
+
+    void setNameLength(const quint32& len = 0);
+    quint32 nameLength() const;
+
+    void setName(const QString& name = "");
+    QString name() const;
+
+    void setCData(const QByteArray& data = "");
+    QByteArray cData() const;
+
+    void setOData(const QByteArray& data = "");
+    QByteArray oData() const;
+
+    // status stuff
+    quint32 size() const;
+    quint32 headerSize() const;
+    bool valid() const;
+
+private:
+    // data fields
+    quint32 _hsize;
+    quint32 _position;
+    quint32 _csize;
+    quint32 _osize;
+    quint32 _name_length;
+    QString _name;
+    QByteArray _cdata;
+};
diff --git a/filesystem/NCFilesystem.cxx b/filesystem/NCFilesystem.cxx
new file mode 100644 (file)
index 0000000..3f08a61
--- /dev/null
@@ -0,0 +1,740 @@
+#include <QFile>
+#include "filesystem/NCFilesystem.hxx"
+#include "filesystem/FileIdentifier.hxx"
+
+// --- internal typedefs ---
+
+using IdentifyType = NC::Identify::Type;
+using ColType = NC::FileData::Type;
+
+// --- internal objects ---
+
+FileIdentifier id;
+
+// --- constructors and deconstructors ---
+
+NCFilesystem::NCFilesystem(QObject *parent)
+: QObject(parent)
+{
+    emit sigFileStatus(0, _files.count(), ColType::None);
+}
+
+NCFilesystem::NCFilesystem(const NCFilesystem& rhs)
+: QObject(rhs.parent()), _files(rhs._files)
+{
+    emit sigFileStatus(0, _files.count(), ColType::None);
+}
+
+NCFilesystem::NCFilesystem(const NCFilesystem&& rhs)
+: QObject(std::move(rhs.parent())), _files(std::move(rhs._files))
+{
+    emit sigFileStatus(0, _files.count(), ColType::None);
+}
+
+NCFilesystem::~NCFilesystem()
+{
+    _files.clear();
+    emit sigFileStatus(0, _files.count(), ColType::None);
+}
+
+// --- operators ---
+
+NCFilesystem& NCFilesystem::operator=(const NCFilesystem& rhs)
+{
+    if (this != &rhs)
+    {
+        _files = rhs._files;
+        emit sigFileStatus(0, _files.count(), ColType::All);
+    }
+
+    return *this;
+}
+
+NCFilesystem& NCFilesystem::operator=(const NCFilesystem&& rhs)
+{
+    if (this != &rhs)
+    {
+        _files = std::move(rhs._files);
+        emit sigFileStatus(0, _files.count(), ColType::All);
+    }
+
+    return *this;
+}
+
+bool NCFilesystem::operator==(const NCFilesystem& rhs) const
+{
+    return _files == rhs._files;
+}
+
+bool NCFilesystem::operator!=(const NCFilesystem& rhs) const
+{
+    return _files != rhs._files;
+}
+
+// --- basic and status methodes ---
+
+quint32 NCFilesystem::count() const
+{
+    return _files.count();
+}
+
+quint32 NCFilesystem::size() const
+{
+    quint32 result = HSize;
+
+    for (auto& f : _files)
+        result += f.size();
+
+    return result;
+}
+
+bool NCFilesystem::valid() const
+{
+    auto it = _files.begin();
+    bool result = true;
+
+    while (result && it != _files.end())
+    {
+        result = result && (*it).valid();
+        ++it;
+    }
+
+    return result;
+}
+
+void NCFilesystem::clear()
+{
+    _files.clear();
+    emit sigFileStatus(0, _files.count(), ColType::None);
+}
+
+QString NCFilesystem::info() const
+{
+    QString result;
+
+    for (qint32 i = 0; i < _files.count(); ++i)
+    {
+        result.append("idx:").append(QString::number(i));
+        result.append(" hs:").append(QString::number(_files[i].hSize()));
+        result.append(" pos:").append(QString::number(_files[i].position()));
+        result.append(" os:").append(QString::number(_files[i].oSize()));
+        result.append(" cs:").append(QString::number(_files[i].cSize()));
+        result.append(" ln:").append(QString::number(_files[i].nameLength()));
+        result.append(" name:'").append(_files[i].name()).append("'").append("\n");
+    }
+
+    return result;
+}
+
+IdentifyType NCFilesystem::fileIdentify(const QString& filename) const
+{
+    return id.identify(filename);
+}
+
+QString NCFilesystem::fileIdentifyName(const IdentifyType& type) const
+{
+    return id.identifyName(type);
+}
+
+QString NCFilesystem::fileIdentifyName(const QString& filename) const
+{
+    return id.identifyName(fileIdentify(filename));
+}
+
+quint32 NCFilesystem::load(const QString& filename)
+{
+    if (fileIdentify(filename) == id.VFS)
+        return vfsLoader(filename, false);
+
+    return Error;
+}
+
+quint32 NCFilesystem::save(const QString& filename) const
+{
+    return vfsSaver(filename);
+}
+
+quint32 NCFilesystem::fileLoad(const QString& filename)
+{
+    switch (fileIdentify(filename))
+    {
+        case id.VFS:
+            return vfsLoader(filename, false);
+        case id.PAK1:
+            return pakLoader(filename, false);
+        case id.Plain:
+            return plainLoader(filename, false);
+        case id.ZLIB:
+            return zlibLoader(filename, false);
+        default:
+            break;
+    }
+
+    return Error;
+}
+
+quint32 NCFilesystem::fileAdd(const QString& filename)
+{
+    switch (fileIdentify(filename))
+    {
+        case id.VFS:
+            return vfsLoader(filename, true);
+        case id.PAK1:
+            return pakLoader(filename, true);
+        case id.Plain:
+            return plainLoader(filename, true);
+        case id.ZLIB:
+            return zlibLoader(filename, true);
+        default:
+            break;
+    }
+
+    return Error;
+}
+
+quint32 NCFilesystem::fileAdd(const NCFile& entry)
+{
+    _files.push_back(entry);
+    recalc(_files);
+
+    emit sigFileAdd(_files.back(), _files.count() - 1);
+    emit sigFileStatus(0, _files.count(), ColType::Position);
+
+    return _files.size();
+}
+
+quint32 NCFilesystem::fileSave(const QString& filename, const QString& name) const
+{
+    return plainSaver(fileGet(name), filename);
+}
+
+quint32 NCFilesystem::fileSave(const QString& filename, const quint32& index) const
+{
+    return plainSaver(fileGet(index), filename);
+}
+
+quint32 NCFilesystem::fileSave(const QString& filename, const QString& name, const IdentifyType& type) const
+{
+    switch (type)
+    {
+        case id.PAK1:
+            return pakSaver(fileGet(name), filename);
+        case id.Plain:
+            return plainSaver(fileGet(name), filename);
+        case id.ZLIB:
+            return zlibSaver(fileGet(name), filename);
+        default:
+            break;
+    }
+
+    return Error;
+}
+
+quint32 NCFilesystem::fileSave(const QString& filename, const quint32& index, const IdentifyType& type) const
+{
+    switch (type)
+    {
+        case id.PAK1:
+            return pakSaver(fileGet(index), filename);
+        case id.Plain:
+            return plainSaver(fileGet(index), filename);
+        case id.ZLIB:
+            return zlibSaver(fileGet(index), filename);
+        default:
+            break;
+    }
+
+    return Error;
+}
+
+NCFile NCFilesystem::fileGet(const QString& name) const
+{
+    NCFile result;
+
+    for (auto& f : _files)
+    {
+        if (name == f.name())
+        {
+            result = f;
+            break;
+        }
+    }
+
+    return result;
+}
+
+NCFile NCFilesystem::fileGet(const quint32& index) const
+{
+    NCFile result;
+
+    if (index < static_cast<quint32>(_files.count()))
+        result = _files[index];
+
+    return result;
+}
+
+NCFile NCFilesystem::fileReplace(const NCFile& entry, const QString& name)
+{
+    NCFile result;
+
+    if (!entry.valid())
+        return result;
+
+    for (auto& f : _files)
+    {
+        if (name == f.name())
+        {
+            result = f;
+            f = entry;
+
+            emit sigFileReplace(f, _files.indexOf(f));
+            emit sigFileStatus(0, _files.count(), ColType::Position);
+
+            break;
+        }
+    }
+    recalc(_files);
+
+    return result;
+}
+
+NCFile NCFilesystem::fileReplace(const NCFile& entry, const quint32& index)
+{
+    NCFile result;
+
+    if (!entry.valid())
+        return result;
+
+    if (index >= static_cast<quint32>(_files.count()))
+        return result;
+
+    result = _files[index];
+    _files[index] = entry;
+    recalc(_files);
+
+    emit sigFileReplace(_files[index], index);
+    emit sigFileStatus(0, _files.count(), ColType::Position);
+
+    return result;
+}
+
+NCFile NCFilesystem::fileReplace(const QString& filename, const QString& name)
+{
+    NCFile result;
+
+    if (filename.isEmpty())
+        return result;
+
+    for (auto& f : _files)
+    {
+        if (name == f.name())
+        {
+            NCFilesystem temp;
+
+            temp.fileLoad(filename);
+            if (temp.count())
+            {
+                const quint32 index = _files.indexOf(f);
+                const quint32 added = temp.count();
+
+                result = f;
+                _files.remove(index);
+                while (temp.count())
+                {
+                    _files.insert(index, temp.fileGet(temp.count() - 1));
+                    temp.fileRemove(temp.count() - 1);
+                }
+                recalc(_files);
+
+                emit sigFileReplace(_files[index], index);
+                for (quint32 i = index; i < (index + added); ++i)
+                    emit sigFileAdd(_files[i], i);
+                emit sigFileStatus(0, _files.count(), ColType::Position);
+            }
+            break;
+        }
+    }
+
+    return result;
+}
+
+NCFile NCFilesystem::fileReplace(const QString& filename, const quint32& index)
+{
+    NCFile result;
+    NCFilesystem temp;
+
+    if (filename.isEmpty())
+        return result;
+
+    if (index >= static_cast<quint32>(_files.count()))
+        return result;
+
+    temp.fileLoad(filename);
+    if (temp.count())
+    {
+        const quint32 added = temp.count();
+
+        result = _files[index];
+        _files.remove(index);
+        while (temp.count())
+        {
+            _files.insert(index, temp.fileGet(temp.count() - 1));
+            temp.fileRemove(temp.count() - 1);
+        }
+        recalc(_files);
+
+        emit sigFileReplace(_files[index], index);
+        for (quint32 i = (index + 1); i < (index + added); ++i)
+            emit sigFileAdd(_files[i], i);
+        emit sigFileStatus(0, _files.count(), ColType::Position);
+    }
+
+    return result;
+}
+
+NCFile NCFilesystem::fileRemove(const QString& name)
+{
+    NCFile result;
+
+    if (name.isEmpty())
+        return result;
+
+    for (auto it = _files.begin(); it != _files.end(); ++it)
+    {
+        if (name == (*it).name())
+        {
+            emit sigFileRemove(_files.indexOf(it));
+            emit sigFileStatus(0, _files.count(), ColType::Position);
+
+            result = *it;
+            _files.erase(it);
+            break;
+        }
+    }
+    recalc(_files);
+
+    return result;
+}
+
+NCFile NCFilesystem::fileRemove(const quint32& index)
+{
+    NCFile result;
+
+    if (index >= static_cast<quint32>(_files.count()))
+        return result;
+
+    result = _files[index];
+    _files.erase(_files.begin() + index);
+    recalc(_files);
+
+    emit sigFileRemove(index);
+    emit sigFileStatus(0, _files.count(), ColType::Position);
+
+    return result;
+}
+
+// --- internal methodes ---
+
+void NCFilesystem::recalc(QVector<NCFile>& files)
+{
+    quint32 base_offset = 0;
+    quint32 csize_offset = 0;
+
+    for (auto& f : files)
+        base_offset += f.headerSize() + f.nameLength();
+
+    for (auto& f : files)
+    {
+        f.setPosition(base_offset + csize_offset);
+        csize_offset += f.cSize();
+    }
+}
+
+quint32 NCFilesystem::vfsLoader(const QString& filename, const bool add)
+{
+    QFile ifile(filename);
+
+    if (ifile.open(QIODevice::ReadOnly))
+    {
+        QVector<NCFile> files;
+        const quint32 old_size = _files.count();
+        quint32 data = 0;
+        quint32 count = 0;
+        QByteArray stream;
+
+        ifile.seek(sizeof (id.VFS));
+        ifile.read(reinterpret_cast<char *>(&count), sizeof (count));
+
+        for (quint32 i = 0; i < count; ++i)
+        {
+            NCFile entry;
+            quint32 pos = 0;
+
+            // header + name
+            ifile.read(reinterpret_cast<char *>(&data), sizeof (data));
+            entry.setHSize(data);
+            ifile.read(reinterpret_cast<char *>(&data), sizeof (data));
+            entry.setPosition(data);
+            ifile.read(reinterpret_cast<char *>(&data), sizeof (data));
+            entry.setCSize(data);
+            ifile.read(reinterpret_cast<char *>(&data), sizeof (data));
+            entry.setOSize(data);
+            ifile.read(reinterpret_cast<char *>(&data), sizeof (data));
+            entry.setNameLength(data);
+            stream.resize(data);
+            ifile.read(stream.data(), data);
+            stream.truncate(data);
+            entry.setName(stream);
+            // compressed data
+            pos = ifile.pos();
+            ifile.seek(entry.position());
+            stream.resize(entry.cSize());
+            ifile.read(stream.data(), entry.cSize());
+            entry.setCData(stream);
+            ifile.seek(pos);
+
+            files.push_back(entry);
+        }
+        ifile.close();
+
+        if (add)
+        {
+            _files += files;
+            recalc(_files);
+
+            emit sigFileStatus(0, _files.count(), ColType::Position);
+            emit sigFileStatus(old_size, _files.count(), ColType::All);
+        }
+        else
+        {
+            recalc(files);
+            _files = files;
+
+            emit sigFileStatus(0, _files.count(), ColType::None);
+        }
+
+        return _files.count();
+    }
+
+    return Error;
+}
+
+quint32 NCFilesystem::pakLoader(const QString& filename, const bool add)
+{
+    const quint32 IDSize = sizeof (id.PAK1) + sizeof (id.PAK2) + sizeof (id.PAK3);
+    QFile ifile(filename);
+
+    if (ifile.open(QIODevice::ReadOnly))
+    {
+        quint32 data = 0;
+        QByteArray stream;
+        NCFile entry;
+
+        // header data
+        entry.setPosition(0);
+        entry.setCSize(ifile.size() - IDSize);
+        ifile.seek(IDSize);
+        ifile.read(reinterpret_cast<char *>(&data), sizeof (data));
+        entry.setOSize(data);
+        entry.setNameLength(filename.size() + 1);
+        entry.setName(filename);
+        entry.setHSize(entry.headerSize() + entry.nameLength());
+        // compressed data
+        stream.resize(entry.cSize());
+        ifile.read(stream.data(), entry.cSize());
+        entry.setCData(stream);
+        ifile.close();
+
+        if (!add)
+            _files.clear();
+
+        _files.push_back(entry);
+        recalc(_files);
+
+        emit sigFileAdd(_files.back(), _files.count() - 1);
+        emit sigFileStatus(0, _files.count(), ColType::Position);
+
+        return _files.count();
+    }
+
+    return Error;
+}
+
+quint32 NCFilesystem::plainLoader(const QString& filename, const bool add)
+{
+    QFile ifile(filename);
+
+    if (ifile.open(QIODevice::ReadOnly))
+    {
+        QByteArray stream;
+        NCFile entry;
+
+        stream.resize(ifile.size());
+        ifile.read(stream.data(), ifile.size());
+        entry.setOData(stream); // odata compresses stream to cdata
+        entry.setPosition(0);
+        entry.setCSize(entry.cData().size());
+        entry.setOSize(ifile.size());
+        entry.setNameLength(filename.size() + 1);
+        entry.setName(filename);
+        entry.setHSize(entry.headerSize() + entry.nameLength());
+        ifile.close();
+
+        if (!add)
+            _files.clear();
+
+        _files.push_back(entry);
+        recalc(_files);
+
+        emit sigFileAdd(_files.back(), _files.count() - 1);
+        emit sigFileStatus(0, _files.count(), ColType::Position);
+
+        return _files.count();
+    }
+
+    return Error;
+}
+
+quint32 NCFilesystem::zlibLoader(const QString& filename, const bool add)
+{
+    QFile ifile(filename);
+
+    if (ifile.open(QIODevice::ReadOnly))
+    {
+        QByteArray stream;
+        NCFile entry;
+
+        stream.resize(ifile.size());
+        ifile.read(stream.data(), ifile.size());
+        entry.setCData(stream);
+        entry.setPosition(0);
+        entry.setCSize(ifile.size());
+        entry.setOSize(entry.oData().size());
+        entry.setNameLength(filename.size() + 1);
+        entry.setName(filename);
+        entry.setHSize(entry.headerSize() + entry.nameLength());
+        ifile.close();
+
+        if (!add)
+            _files.clear();
+
+        _files.push_back(entry);
+        recalc(_files);
+
+        emit sigFileAdd(_files.back(), _files.count() - 1);
+        emit sigFileStatus(0, _files.count(), ColType::Position);
+
+        return _files.count();
+    }
+
+    return Error;
+}
+
+quint32 NCFilesystem::vfsSaver(const QString& filename) const
+{
+    QFile ofile(filename);
+
+    if (!valid())
+        return Error;
+
+    if (ofile.open(QIODevice::WriteOnly))
+    {
+        quint32 data = 0;
+        const quint32 count = _files.count();
+
+        data = id.VFS;
+        ofile.write(reinterpret_cast<const char *>(&data), sizeof (data));
+        ofile.write(reinterpret_cast<const char *>(&count), sizeof (count));
+
+        for (auto& f : _files)
+        {
+            data = f.hSize();
+            ofile.write(reinterpret_cast<const char *>(&data), sizeof (data));
+            data = f.position();
+            ofile.write(reinterpret_cast<const char *>(&data), sizeof (data));
+            data = f.cSize();
+            ofile.write(reinterpret_cast<const char *>(&data), sizeof (data));
+            data = f.oSize();
+            ofile.write(reinterpret_cast<const char *>(&data), sizeof (data));
+            data = f.nameLength();
+            ofile.write(reinterpret_cast<const char *>(&data), sizeof (data));
+            ofile.write(f.name().toStdString().c_str(), f.nameLength());
+        }
+
+        for (auto& f : _files)
+            ofile.write(f.cData());
+
+        ofile.close();
+
+        emit sigFileStatus(0, _files.count() - 1, ColType::None);
+
+        return _files.count();
+    }
+
+    return Error;
+}
+
+quint32 NCFilesystem::pakSaver(const NCFile& entry, const QString& filename) const
+{
+    QFile ofile(filename);
+
+    if (!entry.valid())
+        return Error;
+
+    if (ofile.open(QIODevice::WriteOnly))
+    {
+        quint32 data = 0;
+
+        data = id.PAK1;
+        ofile.write(reinterpret_cast<const char *>(&data), sizeof (data));
+        data = id.PAK2;
+        ofile.write(reinterpret_cast<const char *>(&data), sizeof (data));
+        data = id.PAK3;
+        ofile.write(reinterpret_cast<const char *>(&data), sizeof (data));
+        data = entry.oSize();
+        ofile.write(reinterpret_cast<const char *>(&data), sizeof (data));
+        ofile.write(entry.cData());
+
+        ofile.close();
+
+        return 1;
+    }
+
+    return Error;
+}
+
+quint32 NCFilesystem::plainSaver(const NCFile& entry, const QString& filename) const
+{
+    QFile ofile(filename);
+
+    if (!entry.valid())
+        return Error;
+
+    if (ofile.open(QIODevice::WriteOnly))
+    {
+        ofile.write(entry.oData());
+        ofile.close();
+
+        return 1;
+    }
+
+    return Error;
+}
+
+quint32 NCFilesystem::zlibSaver(const NCFile& entry, const QString& filename) const
+{
+    QFile ofile(filename);
+
+    if (!entry.valid())
+        return Error;
+
+    if (ofile.open(QIODevice::WriteOnly))
+    {
+        ofile.write(entry.cData());
+        ofile.close();
+
+        return 1;
+    }
+
+    return Error;
+}
diff --git a/filesystem/NCFilesystem.hxx b/filesystem/NCFilesystem.hxx
new file mode 100644 (file)
index 0000000..1b4f1b5
--- /dev/null
@@ -0,0 +1,95 @@
+#pragma once
+
+/*
+ * the QVector is NOT holding a pointer, this is C++11, we have rvalue references
+ */
+
+#include <QObject>
+#include <QVector>
+#include "common/NCTypes.hxx"
+#include "filesystem/NCFile.hxx"
+
+class NCFilesystem : public QObject {
+    Q_OBJECT
+
+    // interna typedefs
+    using ColType = NC::FileData::Type;
+    using IdentifyType = NC::Identify::Type;
+signals:
+    // signals to report every vfs change
+    void sigFileStatus(quint32 from_row, quint32 to_row, ColType col) const;
+    void sigFileAdd(NCFile& file, quint32 row) const;
+    void sigFileReplace(NCFile& file, quint32 row) const;
+    void sigFileRemove(quint32 row) const;
+
+public:
+    // internal constants
+    enum : quint32 {
+        Error = NC::Meta::Error,
+        HSize = sizeof (quint32) * 2
+    };
+
+    // constructors and deconstructors
+    NCFilesystem(QObject *parent = 0);
+    NCFilesystem(const NCFilesystem& rhs);
+    NCFilesystem(const NCFilesystem&& rhs);
+    virtual ~NCFilesystem();
+
+    // operators
+    NCFilesystem& operator=(const NCFilesystem& rhs);
+    NCFilesystem& operator=(const NCFilesystem&& rhs);
+
+    bool operator==(const NCFilesystem& rhs) const;
+    bool operator!=(const NCFilesystem& rhs) const;
+
+    // basic and status methodes
+    quint32 count() const;
+    quint32 size() const;
+    bool valid() const;
+    void clear();
+    QString info() const;
+
+    IdentifyType fileIdentify(const QString& filename) const;
+    QString fileIdentifyName(const IdentifyType& type) const;
+    QString fileIdentifyName(const QString& filename) const;
+
+    // setters and getters
+    quint32 load(const QString& filename);
+    quint32 save(const QString& filename) const;
+
+    quint32 fileLoad(const QString& filename);
+    quint32 fileAdd(const QString& filename);
+    quint32 fileAdd(const NCFile& entry);
+
+    quint32 fileSave(const QString& filename, const QString& name) const;
+    quint32 fileSave(const QString& filename, const quint32& index) const;
+    quint32 fileSave(const QString& filename, const QString& name, const IdentifyType& type) const;
+    quint32 fileSave(const QString& filename, const quint32& index, const IdentifyType& type) const;
+
+    NCFile fileGet(const QString& name) const;
+    NCFile fileGet(const quint32& index) const;
+
+    NCFile fileReplace(const NCFile& entry, const QString& name);
+    NCFile fileReplace(const NCFile& entry, const quint32& index);
+    NCFile fileReplace(const QString& filename, const QString& name);
+    NCFile fileReplace(const QString& filename, const quint32& index);
+
+    NCFile fileRemove(const QString& name);
+    NCFile fileRemove(const quint32& index);
+
+protected:
+    void recalc(QVector<NCFile>& files);
+
+    quint32 vfsLoader(const QString& filename, const bool add = false);
+    quint32 pakLoader(const QString& filename, const bool add = false);
+    quint32 plainLoader(const QString& filename, const bool add = false);
+    quint32 zlibLoader(const QString& filename, const bool add = false);
+
+    quint32 vfsSaver(const QString& filename) const;
+    quint32 pakSaver(const NCFile& entry, const QString& filename) const;
+    quint32 plainSaver(const NCFile& entry, const QString& filename) const;
+    quint32 zlibSaver(const NCFile& entry, const QString& filename) const;
+
+private:
+    QVector<NCFile> _files;
+};
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ff52526
--- /dev/null
@@ -0,0 +1,8 @@
+# apps
+add_executable        (test_file test_file.cxx)
+qt5_use_modules       (test_file Core)
+target_link_libraries (test_file Filesystem)
+
+add_executable        (test_filesystem test_filesystem.cxx)
+qt5_use_modules       (test_filesystem Core)
+target_link_libraries (test_filesystem Filesystem)
diff --git a/tests/test_file.cxx b/tests/test_file.cxx
new file mode 100644 (file)
index 0000000..4e640c1
--- /dev/null
@@ -0,0 +1,46 @@
+#include <QFile>
+#include <QTextStream>
+#include "filesystem/NCFile.hxx"
+
+QTextStream out(stdout);
+QTextStream err(stderr);
+
+int main(int argc, char **argv)
+{
+    QFile ifile;
+    NCFile entry;
+    QString filename = argv[0];
+    QByteArray stream;
+
+    if (argc == 2)
+        filename = argv[1];
+
+    ifile.setFileName(filename);
+
+    if (!ifile.exists())
+    {
+        err << "ERROR: unable to open file '" << filename << "'" << endl;
+        return 1;
+    }
+
+    if (ifile.open(QIODevice::ReadOnly))
+    {
+        stream.resize(ifile.size());
+        ifile.read(stream.data(), ifile.size());
+        ifile.close();
+    }
+
+    entry.setPosition(17);
+    entry.setOSize(stream.size());
+    entry.setName(filename);
+    entry.setNameLength(filename.size() + 1);
+    entry.setOData(stream);
+    entry.setCSize(entry.cData().size());
+    entry.setHSize(entry.headerSize() + entry.nameLength());
+
+    out << "file: i:" << entry.hSize() << " p:" << entry.position() << " c:" << entry.cSize() << " :o"
+        << entry.oSize() << " l:" << entry.nameLength() << " n:'" << entry.name() << "' -> " << entry.size()
+        << " bytes" << endl;
+
+    return 0;
+}
diff --git a/tests/test_filesystem.cxx b/tests/test_filesystem.cxx
new file mode 100644 (file)
index 0000000..81b660c
--- /dev/null
@@ -0,0 +1,20 @@
+#include <QTextStream>
+#include "filesystem/NCFilesystem.hxx"
+
+QTextStream out(stdout);
+
+int main(int argc, char **argv)
+{
+    QString filename = argv[0];
+    NCFilesystem vfs;
+
+    if (argc == 2)
+        filename = argv[1];
+
+    out << "loading of '" << filename << "' as VFS was" << (vfs.load(filename) == NC::Meta::Error ? " not " : " ")
+        << "successful" << endl;
+
+    out << "files:" << endl << vfs.info() << endl;
+
+    return 0;
+}
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e9d8be9
--- /dev/null
@@ -0,0 +1,13 @@
+# apps
+add_executable        (vfs_tool vfs_tool.cxx)
+qt5_use_modules       (vfs_tool Core)
+target_link_libraries (vfs_tool Filesystem)
+
+add_executable        (dat_viewer dat_viewer.cxx)
+qt5_use_modules       (dat_viewer Core)
+
+add_executable        (zcompress zcompress.cxx)
+qt5_use_modules       (zcompress Core)
+
+add_executable        (zdecompress zdecompress.cxx)
+qt5_use_modules       (zdecompress Core)
diff --git a/tools/dat_viewer.cxx b/tools/dat_viewer.cxx
new file mode 100644 (file)
index 0000000..b92c425
--- /dev/null
@@ -0,0 +1,106 @@
+#include <QFile>
+#include <QTextStream>
+
+QTextStream out(stdout);
+QTextStream err(stderr);
+
+struct Furniture {
+    float coord[3];
+    float rot[3];
+    float scale;
+    quint32 unk1;
+    quint16 mid;
+    quint32 unk2[2];
+    quint16 unk3;
+    quint16 wid;
+    quint16 unk4;
+    qint32 oid;
+    float lower[3];
+    float upper[3];
+} __attribute__((packed));
+
+int main(int argc, char **argv)
+{
+    QFile ifile;
+
+    if (argc == 2)
+    {
+        ifile.setFileName(argv[1]);
+        if (ifile.open(QIODevice::ReadOnly))
+        {
+            QByteArray head;
+
+            while (!ifile.atEnd())
+            {
+                QByteArray head(5 * 4, '\0');
+                quint32 counter = 0;
+                quint32 type = 0;
+                quint32 size = 0;
+
+                ifile.read(reinterpret_cast<char *>(head.data()), head.size());
+                ifile.read(reinterpret_cast<char *>(&type), sizeof (type));
+                ifile.read(reinterpret_cast<char *>(&size), sizeof (size));
+
+                out << "head: 0x" << head.toHex().toUpper() << endl
+                    << "type: " << type << endl
+                    << "size: " << size << endl;
+
+                while (size)
+                {
+                    QByteArray id(2 * 4, '\0');
+                    quint32 objtype = 0;
+                    quint32 objsize = 0;
+
+                    ifile.read(reinterpret_cast<char *>(id.data()), id.size());
+                    ifile.read(reinterpret_cast<char *>(&objtype), sizeof (objtype));
+                    ifile.read(reinterpret_cast<char *>(&objsize), sizeof (objsize));
+
+                    size -= (16 + objsize);
+
+                    out << "---------------------------------------------------" << endl
+                        << "index:    " << counter << endl
+                        << "id:       0x" << id.toHex().toUpper() << endl
+                        << "objtype:  " << objtype << endl
+                        << "objsize:  " << objsize << endl
+                        << "leftsize: " << size << endl;
+
+                    switch (objtype)
+                    {
+                        case 1000003:
+                        {
+                            Furniture f;
+
+                            ifile.read(reinterpret_cast<char *>(&f), sizeof (f));
+
+                            out << "furniture!" << endl
+                                << "pos:      " << f.coord[0] << ", " << f.coord[1] << ", " << f.coord[2] << endl
+                                << "rot:      " << f.rot[0] << ", " << f.rot[1] << ", " << f.rot[2] << endl
+                                << "scale:    " << f.scale << endl
+                                << "unk1:     " << f.unk1 << endl
+                                << "model id: " << f.mid << endl
+                                << "unk2:     " << f.unk2[0] << ", " << f.unk2[1] << endl
+                                << "unk3:     " << f.unk3 << endl
+                                << "world id: " << f.wid << endl
+                                << "unk4:     " << f.unk4 << endl
+                                << "obj id:   " << f.oid << endl
+                                << "low box:  " << f.lower[0] << ", " << f.lower[1] << ", " << f.lower[2] << endl
+                                << "up box:   " << f.upper[0] << ", " << f.upper[1] << ", " << f.upper[2] << endl;
+
+                            break;
+                        }
+                        default:
+                            ifile.seek(ifile.pos() + objsize);
+                    }
+                    ++counter;
+                }
+            }
+            ifile.close();
+        }
+
+        return 0;
+    }
+
+    out << "usage: " << argv[0] << " <dat file>" << endl;
+
+    return 0;
+}
diff --git a/tools/vfs_tool.cxx b/tools/vfs_tool.cxx
new file mode 100644 (file)
index 0000000..673447b
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * commands:
+ *  - quit/exit : die :D
+ *  - ls/list : vfs.info();
+ *  - cls/clear : vfs.clear()
+ *  - size : vfs.size()
+ *  - count : vfs.count()
+ *  --------------------------------------------------------------------------
+ *  - load <file> : vfs.fileLoad()
+ *  - add <file> : vfs.fileAdd()
+ *  - remove/delete/rem/del <file/index> : vfs.fileRemove()
+ *  - save <filename> : vfs.save()
+ *  --------------------------------------------------------------------------
+ *  - replace/rep <file/index> <filename> : vfs.fileReplace()
+ *  - save <file/index> <filename> : vfs.fileSave()
+ */
+
+#include <QStringList>
+#include <QTextStream>
+#include "filesystem/NCFilesystem.hxx"
+
+QTextStream in(stdin);
+QTextStream out(stdout);
+QTextStream err(stderr);
+
+int main()
+{
+    NCFilesystem vfs;
+    QString input;
+    QStringList list;
+
+    while (true)
+    {
+        out << "(" << vfs.count() << "|" << vfs.size() << ") > " << flush;
+        input = in.readLine();
+        list = input.split(" ");
+
+        switch (list.count())
+        {
+            case 1:
+            {
+                if (list[0].compare("quit", Qt::CaseInsensitive) == 0 || list[0].compare("exit", Qt::CaseInsensitive) == 0)
+                    return 0;
+
+                if (list[0].compare("clear", Qt::CaseInsensitive) == 0 || list[0].compare("cls", Qt::CaseInsensitive) == 0)
+                    vfs.clear();
+
+                if (list[0].compare("list", Qt::CaseInsensitive) == 0 || list[0].compare("ls", Qt::CaseInsensitive) == 0)
+                    out << "list: " << endl << vfs.info() << endl;
+
+                if (list[0].compare("size", Qt::CaseInsensitive) == 0)
+                    out << "size: " << vfs.size() << " bytes" << endl;
+
+                if (list[0].compare("count", Qt::CaseInsensitive) == 0)
+                    out << "files: " << vfs.count() << endl;
+
+                break;
+            }
+            case 2:
+            {
+                if (list[0].compare("load", Qt::CaseInsensitive) == 0)
+                    out << "loading of '" << list[1] << "' " << (vfs.fileLoad(list[1]) != vfs.Error ? "successful" : "failed")
+                        << endl;
+
+                if (list[0].compare("add", Qt::CaseInsensitive) == 0)
+                    out << "adding of '" << list[1] << "' " << (vfs.fileAdd(list[1]) != vfs.Error ? "successful" : "failed")
+                        << endl;
+
+                if (list[0].compare("remove", Qt::CaseInsensitive) == 0 || list[0].compare("delete", Qt::CaseInsensitive) == 0
+                    || list[0].compare("rem", Qt::CaseInsensitive) == 0 || list[0].compare("del", Qt::CaseInsensitive) == 0)
+                {
+                    bool ok = true;
+                    quint32 index = list[1].toUInt(&ok);
+
+                    if (ok)
+                        out << "removing of file at index " << index << " "
+                            << (vfs.fileRemove(index).valid() ? "successful" : "failed") << endl;
+                    else
+                        out << "removing of file '" << list[1] << "' "
+                            << (vfs.fileRemove(list[1]).valid() ? "successful" : "failed") << endl;
+                }
+
+                if (list[0].compare("save", Qt::CaseInsensitive) == 0)
+                    out << "saving vfs as '" << list[1] << "' " << (vfs.save(list[1]) != vfs.Error ? "successful" : "failed")
+                        << endl;
+
+                break;
+            }
+            case 3:
+            {
+                /*if (list[0].compare("replace", Qt::CaseInsensitive) == 0 || list[0].compare("rep", Qt::CaseInsensitive) == 0)
+                {
+                    bool ok = true;
+                    quint32 index = list[1].toUInt(&ok);
+
+                    if (ok)
+                        out << "replacing file at index " << index << " with file '" << list[2] << "' "
+                            << (vfs.fileReplace(list[2], index).valid() ? "successful" : "failed") << endl;
+                    else
+                        out << "replacing file '" << list[1] << " with file " << list[2] << "' "
+                            << (vfs.fileReplace(list[2], list[1]).valid() ? "successful" : "failed") << endl;
+                }*/
+
+                if (list[0].compare("save", Qt::CaseInsensitive) == 0)
+                {
+                    bool ok = true;
+                    quint32 index = list[1].toUInt(&ok);
+
+                    if (ok)
+                        out << "saving file at index " << index << " as file '" << list[2] << "' "
+                            << (vfs.fileSave(list[2], index) != vfs.Error ? "successful" : "failed") << endl;
+                    else
+                        out << "saving file '" << list[1] << "' as file '" << list[2] << "' "
+                            << (vfs.fileSave(list[2], list[1]) != vfs.Error ? "successful" : "failed") << endl;
+                }
+
+                break;
+            }
+            case 4:
+                break;
+            default:
+                err << "error: unknown command '" << input << "'" << endl;
+        }
+    }
+
+    return 0;
+}
diff --git a/tools/zcompress.cxx b/tools/zcompress.cxx
new file mode 100644 (file)
index 0000000..2b7ed2a
--- /dev/null
@@ -0,0 +1,59 @@
+#include <QFile>
+#include <QString>
+#include <QTextStream>
+
+QTextStream out(stdout);
+QTextStream err(stderr);
+
+int main(int argc, char **argv)
+{
+    if (argc == 2)
+    {
+        QFile file(argv[1]);
+
+        if (!file.exists())
+        {
+            err << "ERROR: unable to open file '" << file.fileName() << "'" << endl;
+            return 1;
+        }
+
+        if (file.open(QIODevice::ReadOnly))
+        {
+            QByteArray stream(file.size(), '\0');
+
+            file.read(stream.data(), file.size());
+            file.close();
+
+            if (stream[0] == 'x')
+            {
+                err << "ERROR: file '" << file.fileName() << "' is zlib compressed already" << endl;
+                return 1;
+            }
+
+            stream = qCompress(stream, 9).mid(sizeof (quint32));
+
+            file.setFileName(file.fileName() + ".z");
+            if (file.open(QIODevice::WriteOnly))
+            {
+                file.write(stream);
+                file.close();
+
+                return 0;
+            }
+            else
+            {
+                err << "ERROR: file '" << file.fileName() << "' is not writeable" << endl;
+                return 1;
+            }
+        }
+        else
+        {
+            err << "ERROR: file '" << file.fileName() << "' is not readable" << endl;
+            return 1;
+        }
+    }
+
+    out << "usage: " << argv[0] << " <file to compress>" << endl;
+
+    return 0;
+}
diff --git a/tools/zdecompress.cxx b/tools/zdecompress.cxx
new file mode 100644 (file)
index 0000000..cbf8246
--- /dev/null
@@ -0,0 +1,62 @@
+#include <QFile>
+#include <QString>
+#include <QTextStream>
+
+QTextStream out(stdout);
+QTextStream err(stderr);
+
+int main(int argc, char **argv)
+{
+    if (argc == 2)
+    {
+        QFile file(argv[1]);
+
+        if (!file.exists())
+        {
+            err << "ERROR: unable to open file '" << file.fileName() << "'" << endl;
+            return 1;
+        }
+
+        if (file.open(QIODevice::ReadOnly))
+        {
+            // black maigc ;-) naaa, we need a quint32 at the beginning of the zlib stream to get qUncompress()
+            // to eat it
+            const quint32 tmp_size = sizeof (quint32);
+            QByteArray stream(file.size() + tmp_size, '\0');
+
+            file.read(stream.data() + 4, file.size());
+            file.close();
+
+            if (stream[tmp_size] != 'x')
+            {
+                err << "ERROR: file '" << file.fileName() << "' is not zlib compressed" << endl;
+                return 1;
+            }
+
+            stream = qUncompress(stream);
+
+            file.setFileName(file.fileName() + ".decompressed");
+            if (file.open(QIODevice::WriteOnly))
+            {
+                file.write(stream);
+                file.close();
+
+                return 0;
+            }
+            else
+            {
+                err << "ERROR: file '" << file.fileName() << "' is not writeable" << endl;
+                return 1;
+            }
+        }
+        else
+        {
+            err << "ERROR: file '" << file.fileName() << "' is not readable" << endl;
+            return 1;
+        }
+    }
+
+    out << "usage: " << argv[0] << " <.z file to decompress>" << endl;
+
+    return 0;
+}