├── .gitattributes ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── OnexExplorer.pro ├── README.MD ├── Source ├── Converters │ ├── IModelConverter.h │ ├── ImageConverter.cpp │ ├── ImageConverter.h │ ├── LittleEndianConverter.cpp │ ├── LittleEndianConverter.h │ ├── NosModelConverter.cpp │ ├── NosModelConverter.h │ ├── ObjConverter.cpp │ └── ObjConverter.h ├── Decryptors │ ├── INosDecryptor.h │ ├── NosTextDatFileDecryptor.cpp │ ├── NosTextDatFileDecryptor.h │ ├── NosTextOthersFileDecryptor.cpp │ ├── NosTextOthersFileDecryptor.h │ ├── NosZlibDecryptor.cpp │ └── NosZlibDecryptor.h ├── MainWindow.cpp ├── MainWindow.h ├── NosEnumTypes.h ├── Openers │ ├── INosFileOpener.cpp │ ├── INosFileOpener.h │ ├── JsonConfigOpener.cpp │ ├── JsonConfigOpener.h │ ├── NosCCInfOpener.cpp │ ├── NosCCInfOpener.h │ ├── NosTextOpener.cpp │ ├── NosTextOpener.h │ ├── NosZlibOpener.cpp │ └── NosZlibOpener.h ├── Ui │ ├── FileInfo.cpp │ ├── FileInfo.h │ ├── OnexTreeItem.cpp │ ├── OnexTreeItem.h │ ├── Previews │ │ ├── MultiImagePreview.cpp │ │ ├── MultiImagePreview.h │ │ ├── MultiImagePreview.ui │ │ ├── SingleImagePreview.cpp │ │ ├── SingleImagePreview.h │ │ ├── SingleImagePreview.ui │ │ ├── SingleModelPreview.cpp │ │ ├── SingleModelPreview.h │ │ ├── SingleTextFilePreview.cpp │ │ ├── SingleTextFilePreview.h │ │ └── SingleTextFilePreview.ui │ ├── Settings.cpp │ ├── Settings.h │ ├── Settings.ui │ └── TreeItems │ │ ├── OnexNS4BbData.cpp │ │ ├── OnexNS4BbData.h │ │ ├── OnexNSipData.cpp │ │ ├── OnexNSipData.h │ │ ├── OnexNSmcData.cpp │ │ ├── OnexNSmcData.h │ │ ├── OnexNSmnData.cpp │ │ ├── OnexNSmnData.h │ │ ├── OnexNSmpData.cpp │ │ ├── OnexNSmpData.h │ │ ├── OnexNSmpFrame.cpp │ │ ├── OnexNSmpFrame.h │ │ ├── OnexNStcData.cpp │ │ ├── OnexNStcData.h │ │ ├── OnexNStgData.cpp │ │ ├── OnexNStgData.h │ │ ├── OnexNStpData.cpp │ │ ├── OnexNStpData.h │ │ ├── OnexNStpMipMap.cpp │ │ ├── OnexNStpMipMap.h │ │ ├── OnexTreeImage.cpp │ │ ├── OnexTreeImage.h │ │ ├── OnexTreeText.cpp │ │ ├── OnexTreeText.h │ │ ├── OnexTreeZlibItem.cpp │ │ └── OnexTreeZlibItem.h ├── main.cpp └── mainwindow.ui ├── resources.qrc └── resources ├── kofi4.png └── oxe_icon_trans.ico /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | moc_predefs.h 24 | qrc_*.cpp 25 | ui_*.h 26 | Makefile* 27 | *build-* 28 | 29 | # QtCreator 30 | 31 | *.autosave 32 | 33 | # QtCtreator Qml 34 | *.qmlproject.user 35 | *.qmlproject.user.* 36 | 37 | # QtCtreator CMake 38 | CMakeLists.txt.user* 39 | 40 | # Windows trash 41 | *.db 42 | 43 | #VS Code 44 | .vscode/* 45 | .clang-format 46 | 47 | #CLion 48 | .idea/* 49 | cmake-build-debug/* 50 | cmake-build-release/* 51 | 52 | OnexExplorer 53 | *.NOS 54 | 55 | freeglut/* 56 | 57 | *.xmi 58 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(OnexExplorer) 3 | 4 | set(DTARGET_64 ON) 5 | 6 | set(CMAKE_CXX_STANDARD 14) 7 | 8 | set(CMAKE_AUTOMOC ON) 9 | set(CMAKE_AUTOUIC ON) 10 | set(CMAKE_AUTORCC ON) 11 | 12 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 13 | 14 | find_package(Qt5Core REQUIRED) 15 | find_package(Qt5Widgets REQUIRED) 16 | find_package(Qt5Gui REQUIRED) 17 | 18 | set(GLUT_LIBRARY_DIR "freeglut/lib") 19 | set(GLUT_INCLUDE_DIR "freeglut/include/GL") 20 | find_package(GLUT REQUIRED) 21 | include_directories(${GLUT_INCLUDE_DIR}) 22 | 23 | set(SOURCES 24 | Source/main.cpp 25 | Source/MainWindow.cpp 26 | Source/Converters/ImageConverter.cpp 27 | Source/Converters/LittleEndianConverter.cpp 28 | Source/Converters/NosModelConverter.cpp 29 | Source/Converters/ObjConverter.cpp 30 | Source/Decryptors/NosTextDatFileDecryptor.cpp 31 | Source/Decryptors/NosTextOthersFileDecryptor.cpp 32 | Source/Decryptors/NosZlibDecryptor.cpp 33 | Source/Openers/NosZlibOpener.cpp 34 | Source/Openers/NosCCInfOpener.cpp 35 | Source/Openers/NosTextOpener.cpp 36 | Source/Openers/INosFileOpener.cpp 37 | Source/Openers/JsonConfigOpener.cpp 38 | Source/Ui/OnexTreeItem.cpp 39 | Source/Ui/FileInfo.cpp 40 | Source/Ui/Settings.cpp 41 | Source/Ui/Previews/SingleTextFilePreview.cpp 42 | Source/Ui/Previews/SingleImagePreview.cpp 43 | Source/Ui/Previews/MultiImagePreview.cpp 44 | Source/Ui/Previews/SingleModelPreview.cpp 45 | Source/Ui/TreeItems/OnexTreeImage.cpp 46 | Source/Ui/TreeItems/OnexTreeText.cpp 47 | Source/Ui/TreeItems/OnexTreeZlibItem.cpp 48 | Source/Ui/TreeItems/OnexNSipData.cpp 49 | Source/Ui/TreeItems/OnexNS4BbData.cpp 50 | Source/Ui/TreeItems/OnexNStcData.cpp 51 | Source/Ui/TreeItems/OnexNStpData.cpp 52 | Source/Ui/TreeItems/OnexNStpMipMap.cpp 53 | Source/Ui/TreeItems/OnexNSmpData.cpp 54 | Source/Ui/TreeItems/OnexNSmpFrame.cpp 55 | Source/Ui/TreeItems/OnexNStgData.cpp 56 | Source/Ui/TreeItems/OnexNSmnData.cpp 57 | Source/Ui/TreeItems/OnexNSmcData.cpp 58 | ) 59 | 60 | set(MOC_HEADERS 61 | Source/MainWindow.h 62 | Source/NosEnumTypes.h 63 | Source/Converters/ImageConverter.h 64 | Source/Converters/LittleEndianConverter.h 65 | Source/Converters/IModelConverter.h 66 | Source/Converters/NosModelConverter.h 67 | Source/Converters/ObjConverter.h 68 | Source/Decryptors/NosTextDatFileDecryptor.h 69 | Source/Decryptors/NosTextOthersFileDecryptor.h 70 | Source/Decryptors/NosZlibDecryptor.h 71 | Source/Openers/NosZlibOpener.h 72 | Source/Openers/NosCCInfOpener.h 73 | Source/Openers/NosTextOpener.h 74 | Source/Openers/INosFileOpener.h 75 | Source/Openers/JsonConfigOpener.h 76 | Source/Ui/OnexTreeItem.h 77 | Source/Ui/FileInfo.h 78 | Source/Ui/Settings.h 79 | Source/Ui/Previews/SingleTextFilePreview.h 80 | Source/Ui/Previews/SingleImagePreview.h 81 | Source/Ui/Previews/MultiImagePreview.h 82 | Source/Ui/Previews/SingleModelPreview.h 83 | Source/Ui/TreeItems/OnexTreeImage.h 84 | Source/Ui/TreeItems/OnexTreeText.h 85 | Source/Ui/TreeItems/OnexTreeZlibItem.h 86 | Source/Ui/TreeItems/OnexNSipData.h 87 | Source/Ui/TreeItems/OnexNS4BbData.h 88 | Source/Ui/TreeItems/OnexNStcData.h 89 | Source/Ui/TreeItems/OnexNStpData.h 90 | Source/Ui/TreeItems/OnexNStpMipMap.h 91 | Source/Ui/TreeItems/OnexNSmpData.h 92 | Source/Ui/TreeItems/OnexNSmpFrame.h 93 | Source/Ui/TreeItems/OnexNStgData.h 94 | Source/Ui/TreeItems/OnexNSmnData.h 95 | Source/Ui/TreeItems/OnexNSmcData.h 96 | ) 97 | 98 | set(UIS 99 | Source/mainwindow.ui 100 | Source/Ui/Previews/SingleTextFilePreview.ui 101 | Source/Ui/Previews/SingleImagePreview.ui 102 | Source/Ui/Previews/MultiImagePreview.ui 103 | Source/Ui/Settings.ui 104 | ) 105 | 106 | set(RESOURCES 107 | resources.qrc 108 | ) 109 | 110 | 111 | # compile 112 | #add_executable( PROJECT_NAME ${SOURCES} ${MOC_SRCS} ${RES_SOURCES} ${UI_HEADERS} ) 113 | # or use line below instead, if you using Windows ™ Operating System. 114 | add_executable(OnexExplorer WIN32 ${SOURCES} ${MOC_SRCS} ${RES_SOURCES} ${UI_HEADERS} ${RESOURCES}) 115 | 116 | # build it (link libraries) 117 | target_link_libraries(OnexExplorer Qt5::Core) 118 | target_link_libraries(OnexExplorer Qt5::Widgets) 119 | target_link_libraries(OnexExplorer Qt5::Gui) 120 | target_link_libraries(OnexExplorer ${GLUT_LIBRARIES}) 121 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /OnexExplorer.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2017-03-12T15:48:27 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui widgets opengl 8 | 9 | TARGET = OnexExplorer 10 | TEMPLATE = app 11 | 12 | # The following define makes your compiler emit warnings if you use 13 | # any feature of Qt which as been marked as deprecated (the exact warnings 14 | # depend on your compiler). Please consult the documentation of the 15 | # deprecated API in order to know how to port your code away from it. 16 | DEFINES += QT_DEPRECATED_WARNINGS 17 | 18 | # You can also make your code fail to compile if you use deprecated APIs. 19 | # In order to do so, uncomment the following line. 20 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 21 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 22 | 23 | 24 | SOURCES += \ 25 | Source/main.cpp \ 26 | Source/MainWindow.cpp \ 27 | Source/Converters/ImageConverter.cpp \ 28 | Source/Converters/LittleEndianConverter.cpp \ 29 | Source/Converters/NosModelConverter.cpp \ 30 | Source/Converters/ObjConverter.cpp \ 31 | Source/Decryptors/NosTextDatFileDecryptor.cpp \ 32 | Source/Decryptors/NosTextOthersFileDecryptor.cpp \ 33 | Source/Decryptors/NosZlibDecryptor.cpp \ 34 | Source/Openers/NosZlibOpener.cpp \ 35 | Source/Openers/NosCCInfOpener.cpp \ 36 | Source/Openers/NosTextOpener.cpp \ 37 | Source/Openers/INosFileOpener.cpp \ 38 | Source/Openers/JsonConfigOpener.cpp \ 39 | Source/Ui/OnexTreeItem.cpp \ 40 | Source/Ui/FileInfo.cpp \ 41 | Source/Ui/Settings.cpp \ 42 | Source/Ui/Previews/SingleTextFilePreview.cpp \ 43 | Source/Ui/Previews/SingleImagePreview.cpp \ 44 | Source/Ui/Previews/MultiImagePreview.cpp \ 45 | Source/Ui/Previews/SingleModelPreview.cpp \ 46 | Source/Ui/TreeItems/OnexTreeImage.cpp \ 47 | Source/Ui/TreeItems/OnexTreeText.cpp \ 48 | Source/Ui/TreeItems/OnexTreeZlibItem.cpp \ 49 | Source/Ui/TreeItems/OnexNSipData.cpp \ 50 | Source/Ui/TreeItems/OnexNS4BbData.cpp \ 51 | Source/Ui/TreeItems/OnexNStcData.cpp \ 52 | Source/Ui/TreeItems/OnexNStpData.cpp \ 53 | Source/Ui/TreeItems/OnexNStpMipMap.cpp \ 54 | Source/Ui/TreeItems/OnexNSmpData.cpp \ 55 | Source/Ui/TreeItems/OnexNSmpFrame.cpp \ 56 | Source/Ui/TreeItems/OnexNStgData.cpp \ 57 | Source/Ui/TreeItems/OnexNSmnData.cpp \ 58 | Source/Ui/TreeItems/OnexNSmcData.cpp 59 | 60 | 61 | 62 | HEADERS += \ 63 | Source/MainWindow.h \ 64 | Source/NosEnumTypes.h \ 65 | Source/Converters/ImageConverter.h \ 66 | Source/Converters/LittleEndianConverter.h \ 67 | Source/Converters/IModelConverter.h \ 68 | Source/Converters/NosModelConverter.h \ 69 | Source/Converters/ObjConverter.h \ 70 | Source/Decryptors/NosTextDatFileDecryptor.h \ 71 | Source/Decryptors/NosTextOthersFileDecryptor.h \ 72 | Source/Decryptors/NosZlibDecryptor.h \ 73 | Source/Openers/NosZlibOpener.h \ 74 | Source/Openers/NosCCInfOpener.h \ 75 | Source/Openers/NosTextOpener.h \ 76 | Source/Openers/INosFileOpener.h \ 77 | Source/Openers/JsonConfigOpener.h \ 78 | Source/Ui/OnexTreeItem.h \ 79 | Source/Ui/FileInfo.h \ 80 | Source/Ui/settings.h \ 81 | Source/Ui/Previews/SingleTextFilePreview.h \ 82 | Source/Ui/Previews/SingleImagePreview.h \ 83 | Source/Ui/Previews/MultiImagePreview.h \ 84 | Source/Ui/Previews/SingleModelPreview.h \ 85 | Source/Ui/TreeItems/OnexTreeImage.h \ 86 | Source/Ui/TreeItems/OnexTreeText.h \ 87 | Source/Ui/TreeItems/OnexTreeZlibItem.h \ 88 | Source/Ui/TreeItems/OnexNSipData.h \ 89 | Source/Ui/TreeItems/OnexNS4BbData.h \ 90 | Source/Ui/TreeItems/OnexNStcData.h \ 91 | Source/Ui/TreeItems/OnexNStpData.h \ 92 | Source/Ui/TreeItems/OnexNStpMipMap.h \ 93 | Source/Ui/TreeItems/OnexNSmpData.h \ 94 | Source/Ui/TreeItems/OnexNSmpFrame.h \ 95 | Source/Ui/TreeItems/OnexNStgData.h \ 96 | Source/Ui/TreeItems/OnexNSmnData.h \ 97 | Source/Ui/TreeItems/OnexNSmcData.h 98 | 99 | FORMS += \ 100 | Source/mainwindow.ui \ 101 | Source/Ui/Previews/SingleTextFilePreview.ui \ 102 | Source/Ui/Previews/SingleImagePreview.ui \ 103 | Source/Ui/Previews/MultiImagePreview.ui \ 104 | Source/Ui/Settings.ui 105 | 106 | DISTFILES += 107 | 108 | RESOURCES += \ 109 | resources.qrc 110 | 111 | RC_ICONS = ./resources/oxe_icon_trans.ico 112 | 113 | 114 | win32: INCLUDEPATH += $$PWD/freeglut/include/GL 115 | else:unix: /usr/include/GL/ 116 | 117 | win32: LIBS += -L$$PWD/freeglut/lib -lfreeglut 118 | else:unix: LIBS += -lglut -lGLU -lGL 119 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # OnexExplorer # 2 | 3 | OnexExplorer is an open-source tool for unpacking and repacking .NOS data files from game called NosTale. It can open almost all .NOS files and allows to view the Content, export and replace it and even add own Content to them. Our software works on Qt 5.5+ with OpenGl. 4 | 5 | Client Modding Discord: [](https://discord.gg/t73Ayca) 6 | 7 | 8 | ## Currently supported archives ## 9 | 10 | - NSgtdData (Items, quests, skills, mobs etc. data) 11 | - NSlangData (_code_lang_list (ztsXXXe)) 12 | - NScliData (constring.dat) 13 | - NSetcData (Word list for Memory & TabooStr.lst) 14 | - NS4BbData (Big images) 15 | - NSipData (Icons) 16 | - NStpData (Map and model textures) 17 | - NStpeData (Effect textures) 18 | - NStpuData (UI textures) 19 | - NStcData (Map Grids) 20 | - NSmpData (Monster-related sprites) 21 | - NSppData (Player-related sprites) 22 | - NSmnData (Mob-related sprite infos) 23 | - NSpnData (Player-related sprite infos) 24 | - NSmcData (Monster-related animation kits) 25 | - NSpcData (Player-related animation kits) 26 | - NStgData (3D Models) 27 | - NStgeData (3D VFX models) 28 | 29 | 30 | **Other files will be added in future updates.** 31 | 32 | 33 | ## Unsupported archives ## 34 | 35 | - NStuData (Map configs) 36 | - NSeffData (VFX configs) 37 | - NStsData 38 | - NStkData 39 | - NSemData 40 | - NSesData 41 | - NSedData 42 | - NSgrdData 43 | - NSpmData 44 | 45 | 46 | ## License ## 47 | 48 | Our project is based on **Boost Software License**: 49 | > [LICENSE](/LICENSE) 50 | 51 | 52 | ## Installation ## 53 | 54 | **Download** a **[release](https://github.com/Pumbaa98/OnexExplorer/releases)** or: 55 | 56 | 1. Compile given sourcecode using Qt 5.5+ and OpenGl (freeglut) 57 | 2. Move required Qt DLL's to EXE's location 58 | 3. Run the program 59 | 60 | The experimental branch and releases flaged with **pre-release** are in development and don't guarantee that everything works as expected. 61 | **If you have any problems please try to compile the master branch or use regular releases!** 62 | 63 | 64 | ## Report Bugs and suggest features ## 65 | If you have any **problems** with OnexExplorer or want to suggest new **features** feel free to open **[issues](https://github.com/Pumbaa98/OnexExplorer/issues)**. 66 | Please dont forget to mention the **version** you're using. 67 | -------------------------------------------------------------------------------- /Source/Converters/IModelConverter.h: -------------------------------------------------------------------------------- 1 | #ifndef IMODELCONVERTER_H 2 | #define IMODELCONVERTER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "LittleEndianConverter.h" 10 | 11 | struct ModelAnimationPositionFrame { 12 | int timestamp; 13 | QVector3D position; 14 | }; 15 | struct ModelAnimationRotationFrame { 16 | int timestamp; 17 | QVector4D rotation; 18 | }; 19 | struct ModelAnimationScaleFrame { 20 | int timestamp; 21 | QVector3D scale; 22 | }; 23 | struct ModelGroup { 24 | int number; 25 | QVector faces; 26 | int texture; 27 | }; 28 | struct ModelObject { 29 | QVector groups; 30 | QVector3D position; 31 | QVector4D rotation; 32 | QVector3D scale; 33 | QVector animationPositions; 34 | QVector animationRotations; 35 | QVector animationScales; 36 | }; 37 | struct Model { 38 | QVector vertices; 39 | QVector normals; 40 | QVector uv; 41 | QVector groups; 42 | QVector objects; 43 | float uvScale; 44 | }; 45 | 46 | class IModelConverter { 47 | protected: 48 | LittleEndianConverter *littleEndianConverter; 49 | }; 50 | 51 | #endif // IMODELCONVERTER_H 52 | -------------------------------------------------------------------------------- /Source/Converters/ImageConverter.cpp: -------------------------------------------------------------------------------- 1 | #include "ImageConverter.h" 2 | #include 3 | 4 | 5 | QImage ImageConverter::convertGBAR4444(QByteArray &array, int width, int height, int startByte) 6 | /// GBAR = ARGB (endianness) 7 | { 8 | qDebug() << "Opened GBAR4444 image."; 9 | QImage img(width, height, QImage::Format_ARGB32); 10 | img.fill(Qt::transparent); 11 | for (int y = 0; y < height; ++y) { 12 | for (int x = 0; x < width; ++x) { 13 | uchar gb = array.at(startByte + y * 2 * width + x * 2); 14 | uchar ar = array.at(startByte + y * 2 * width + x * 2 + 1); 15 | uchar g = gb >> 4; 16 | uchar b = gb & 0xF; 17 | uchar a = ar >> 4; 18 | uchar r = ar & 0xF; 19 | img.setPixel(x, y, qRgba(r * 0x11, g * 0x11, b * 0x11, a * 0x11)); 20 | } 21 | } 22 | return img; 23 | } 24 | 25 | QImage ImageConverter::convertBGRA8888(QByteArray &array, int width, int height, int startByte) { 26 | qDebug() << "Opened BGRA8888 image."; 27 | QImage img(width, height, QImage::Format_ARGB32); 28 | img.fill(Qt::transparent); 29 | for (int y = 0; y < height; ++y) { 30 | for (int x = 0; x < width; ++x) { 31 | uchar b = array.at(startByte + y * 4 * width + x * 4); 32 | uchar g = array.at(startByte + y * 4 * width + x * 4 + 1); 33 | uchar r = array.at(startByte + y * 4 * width + x * 4 + 2); 34 | uchar a = array.at(startByte + y * 4 * width + x * 4 + 3); 35 | img.setPixel(x, y, qRgba(r, g, b, a)); 36 | } 37 | } 38 | return img; 39 | } 40 | 41 | QImage ImageConverter::convertARGB555(QByteArray &array, int width, int height, int startByte) { 42 | qDebug() << "Opened ARGB555 image."; 43 | QImage img(width, height, QImage::Format_ARGB32); 44 | img.fill(Qt::transparent); 45 | for (int y = 0; y < height; ++y) { 46 | for (int x = 0; x < width; ++x) { 47 | ushort bytes = littleEndianConverter->fromShort(array.mid(startByte + y * 2 * width + x * 2, 2)); 48 | uchar a = 255 * (bytes & 0x8000) >> 15; // 0x8000 = 0b1000000000000000 49 | uchar r = 8 * (bytes & 0x7C00) >> 10; // 0x7C00 = 0b0111110000000000 50 | uchar g = 8 * (bytes & 0x3E0) >> 5; // 0x3E0 = 0b0000001111100000 51 | uchar b = 8 * (bytes & 0x1F); // 0x1F = 0b0000000000011111 52 | 53 | img.setPixel(x, y, qRgba(r, g, b, a)); 54 | } 55 | } 56 | return img; 57 | } 58 | 59 | QImage ImageConverter::convertNSTC(QByteArray &array, int width, int height, int startByte) { 60 | qDebug() << "Opened NSTC image."; 61 | QImage img(width, height, QImage::Format_ARGB32); 62 | img.fill(Qt::transparent); 63 | for (int x = 0; x < width; ++x) { 64 | for (int y = 0; y < height; ++y) { 65 | uchar value = array.at(startByte + y * width + x); 66 | if (colors.contains(value)) 67 | img.setPixel(x, y, colors[value].rgb()); 68 | else 69 | img.setPixel(x, y, colors[0xFF].rgb()); 70 | } 71 | } 72 | return img.scaled(QSize(width * 2, height * 2)); 73 | } 74 | 75 | QImage ImageConverter::convertBGRA8888_INTERLACED(QByteArray &array, int width, int height, int startByte) { 76 | qDebug() << "Opened BGRA888_INTERLACED image."; 77 | QImage img(width, height, QImage::Format_ARGB32); 78 | img.fill(Qt::transparent); 79 | short num = 0, x = 0, y = 0; 80 | for (int i = 0; i < array.size() - startByte;) { 81 | uchar b = array.at(startByte + i++); 82 | uchar g = array.at(startByte + i++); 83 | uchar r = array.at(startByte + i++); 84 | uchar a = array.at(startByte + i++); 85 | img.setPixel(x, y, qRgba(r, g, b, a)); 86 | x++; 87 | if (x == (num + 256) || x == width) { 88 | x = num; 89 | y++; 90 | } 91 | if (y == height) { 92 | y = 0; 93 | num += 256; 94 | x = num; 95 | } 96 | } 97 | return img; 98 | } 99 | 100 | QImage ImageConverter::convertBARG4444(QByteArray &array, int width, int height, int startByte) 101 | /// BARG = RGBA (endianness) 102 | { 103 | qDebug() << "Opened BARG4444 image."; 104 | QImage img(width, height, QImage::Format_ARGB32); 105 | img.fill(Qt::transparent); 106 | for (int y = 0; y < height; ++y) { 107 | for (int x = 0; x < width; ++x) { 108 | uchar ba = array.at(startByte + y * 2 * width + x * 2); 109 | uchar rg = array.at(startByte + y * 2 * width + x * 2 + 1); 110 | uchar b = ba >> 4; 111 | uchar a = ba & 0xF; 112 | uchar r = rg >> 4; 113 | uchar g = rg & 0xF; 114 | img.setPixel(x, y, qRgba(r * 0x11, g * 0x11, b * 0x11, a * 0x11)); 115 | } 116 | } 117 | return img; 118 | } 119 | 120 | QImage ImageConverter::convertGrayscale(QByteArray &array, int width, int height, int startByte) { 121 | //qDebug() << "Opened Grayscale image."; 122 | QImage img(width, height, QImage::Format_ARGB32); 123 | img.fill(Qt::transparent); 124 | for (int y = 0; y < height; ++y) { 125 | for (int x = 0; x < width; ++x) { 126 | uchar gray = array.at(startByte + y * width + x); 127 | img.setPixel(x, y, qRgba(gray, gray, gray, gray)); 128 | } 129 | } 130 | return img; 131 | } 132 | 133 | // //////////////////////////////////////////////////////////////////////////////////////////////////////// 134 | QByteArray ImageConverter::toGBAR4444(QImage &image) { 135 | qDebug() << "Replaced GBAR4444 image."; 136 | QByteArray data; 137 | for (int y = 0; y < image.height(); ++y) { 138 | for (int x = 0; x < image.width(); ++x) { 139 | QRgb currentPixel = image.pixel(x, y); 140 | uchar green = qGreen(currentPixel) / 0x11; 141 | uchar blue = qBlue(currentPixel) / 0x11; 142 | uchar alpha = qAlpha(currentPixel) / 0x11; 143 | uchar red = qRed(currentPixel) / 0x11; 144 | uchar gb = (green << 4) + blue; 145 | uchar ar = (alpha << 4) + red; 146 | data.push_back(gb); 147 | data.push_back(ar); 148 | } 149 | } 150 | return data; 151 | } 152 | 153 | QByteArray ImageConverter::toNSTC(QImage &image) { 154 | qDebug() << "Replaced NSTC image."; 155 | QByteArray data; 156 | int i = 0, j = 0; 157 | for (int y = 0; y < image.height(); ++y) { 158 | for (int x = 0; x < image.width(); ++x) { 159 | QRgb currentPixel = image.pixel(x, y); 160 | QColor pixColor = qRgb(qRed(currentPixel), qGreen(currentPixel), qBlue(currentPixel)); 161 | if (colors.values().contains(pixColor)) { 162 | i++; 163 | data.push_back(colors.key(pixColor)); 164 | } else { 165 | j++; 166 | data.push_back(0xFF); 167 | } 168 | } 169 | } 170 | return data; 171 | } 172 | 173 | QByteArray ImageConverter::toBGRA8888(QImage &image) { 174 | qDebug() << "Replaced BGRA8888 image."; 175 | QByteArray data; 176 | for (int y = 0; y < image.height(); ++y) { 177 | for (int x = 0; x < image.width(); ++x) { 178 | QRgb currentPixel = image.pixel(x, y); 179 | uchar alpha = qAlpha(currentPixel); 180 | uchar red = qRed(currentPixel); 181 | uchar green = qGreen(currentPixel); 182 | uchar blue = qBlue(currentPixel); 183 | data.push_back(blue); 184 | data.push_back(green); 185 | data.push_back(red); 186 | data.push_back(alpha); 187 | } 188 | } 189 | return data; 190 | } 191 | 192 | QByteArray ImageConverter::toARGB555(QImage &image) { 193 | qDebug() << "Replaced ARGB555 image."; 194 | // NOPE 195 | QByteArray data; 196 | for (int y = 0; y < image.height(); ++y) { 197 | for (int x = 0; x < image.width(); ++x) { 198 | QRgb currentPixel = image.pixel(x, y); 199 | uchar alpha = qAlpha(currentPixel) / 0xFF; // full alpha or gtfo 200 | uchar red = qRed(currentPixel) / 0x08; 201 | uchar green = qGreen(currentPixel) / 0x08; 202 | uchar blue = qBlue(currentPixel) / 0x08; 203 | ushort bytes = (alpha << 15) | (red << 10) | (green << 5) | (blue); 204 | uchar first = bytes >> 8; 205 | uchar second = bytes & 0xFF; 206 | data.push_back(second); 207 | data.push_back(first); 208 | } 209 | } 210 | return data; 211 | } 212 | 213 | QByteArray ImageConverter::toBGRA8888_INTERLACED(QImage &image) { 214 | qDebug() << "Replaced BGRA8888_INTERLACED image."; 215 | QByteArray data; 216 | short num = 0, x = 0, y = 0; 217 | for (int i = 0; i < image.width() * image.height(); i++) { 218 | QRgb currentPixel = image.pixel(x, y); 219 | uchar b = qBlue(currentPixel); 220 | uchar g = qGreen(currentPixel); 221 | uchar r = qRed(currentPixel); 222 | uchar a = qAlpha(currentPixel); 223 | data.push_back(b); 224 | data.push_back(g); 225 | data.push_back(r); 226 | data.push_back(a); 227 | x++; 228 | if (x == (num + 256) || x == image.width()) { 229 | x = num; 230 | y++; 231 | } 232 | if (y == image.height()) { 233 | y = 0; 234 | num += 256; 235 | x = num; 236 | } 237 | } 238 | return data; 239 | } 240 | 241 | QByteArray ImageConverter::toBARG4444(QImage &image) { 242 | qDebug() << "Replaced BARG4444 image."; 243 | // only in 2k6 NSip, not tested, should work tho as its the same as GBAR4444 but different order. 244 | QByteArray data; 245 | for (int y = 0; y < image.height(); ++y) { 246 | for (int x = 0; x < image.width(); ++x) { 247 | QRgb currentPixel = image.pixel(x, y); 248 | uchar blue = qBlue(currentPixel) / 0x11; 249 | uchar alpha = qAlpha(currentPixel) / 0x11; 250 | uchar red = qRed(currentPixel) / 0x11; 251 | uchar green = qGreen(currentPixel) / 0x11; 252 | uchar ba = (blue << 4) + alpha; 253 | uchar rg = (red << 4) + green; 254 | data.push_back(ba); 255 | data.push_back(rg); 256 | } 257 | } 258 | return data; 259 | } 260 | 261 | QByteArray ImageConverter::toGrayscale(QImage &image) { 262 | //qDebug() << "Replaced Grayscale image."; 263 | QByteArray data; 264 | for (int y = 0; y < image.height(); ++y) { 265 | for (int x = 0; x < image.width(); ++x) { 266 | QRgb currentPixel = image.pixel(x, y); 267 | uchar gray = qGreen(currentPixel); 268 | data.push_back(gray); 269 | } 270 | } 271 | return data; 272 | } 273 | 274 | ImageConverter::ImageConverter(LittleEndianConverter *littleEndianConverter) : littleEndianConverter(littleEndianConverter) { 275 | colors[0x0] = qRgb(255, 255, 255); 276 | colors[0x1] = qRgb(100, 100, 100); 277 | colors[0x2] = qRgb(100, 150, 100); 278 | colors[0x3] = qRgb(0, 0, 0); 279 | colors[0x9] = qRgb(100, 100, 150); 280 | colors[0x0a] = qRgb(0, 50, 200); 281 | colors[0x0b] = qRgb(150, 100, 100); 282 | colors[0x0d] = qRgb(150, 150, 100); 283 | colors[0x10] = qRgb(150, 100, 150); 284 | colors[0x11] = qRgb(0, 200, 50); 285 | colors[0x12] = qRgb(200, 50, 0); 286 | colors[0x13] = qRgb(250, 230, 10); 287 | colors[0xFF] = qRgb(150, 0, 255); 288 | } 289 | -------------------------------------------------------------------------------- /Source/Converters/ImageConverter.h: -------------------------------------------------------------------------------- 1 | #ifndef IMAGECONVERTER_H 2 | #define IMAGECONVERTER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "LittleEndianConverter.h" 9 | 10 | class ImageConverter { 11 | public: 12 | QImage convertGBAR4444(QByteArray &array, int width, int height, int startByte = 0); 13 | QImage convertBGRA8888(QByteArray &array, int width, int height, int startByte = 0); 14 | QImage convertARGB555(QByteArray &array, int width, int height, int startByte = 0); 15 | QImage convertNSTC(QByteArray &array, int width, int height, int startByte = 0); 16 | QImage convertBGRA8888_INTERLACED(QByteArray &array, int width, int height, int startByte = 0); 17 | QImage convertBARG4444(QByteArray &array, int width, int height, int startByte = 0); 18 | QImage convertGrayscale(QByteArray &array, int width, int height, int startByte = 0); 19 | QByteArray toGBAR4444(QImage &image); 20 | QByteArray toNSTC(QImage &image); 21 | QByteArray toBGRA8888(QImage &image); 22 | QByteArray toARGB555(QImage &image); 23 | QByteArray toBGRA8888_INTERLACED(QImage &image); 24 | QByteArray toBARG4444(QImage &image); 25 | QByteArray toGrayscale(QImage &image); 26 | explicit ImageConverter(LittleEndianConverter *littleEndianConverter); 27 | private: 28 | QMap colors; 29 | LittleEndianConverter *littleEndianConverter; 30 | }; 31 | 32 | #endif // IMAGECONVERTER_H 33 | -------------------------------------------------------------------------------- /Source/Converters/LittleEndianConverter.cpp: -------------------------------------------------------------------------------- 1 | #include "LittleEndianConverter.h" 2 | #include 3 | 4 | short LittleEndianConverter::fromShort(QByteArray array) { 5 | return qFromLittleEndian(reinterpret_cast(array.data())); 6 | } 7 | 8 | int LittleEndianConverter::fromInt(QByteArray array) { 9 | return qFromLittleEndian(reinterpret_cast(array.data())); 10 | } 11 | 12 | float LittleEndianConverter::fromFloat(QByteArray array) { 13 | return qFromLittleEndian(reinterpret_cast(array.data())); 14 | } 15 | 16 | QByteArray LittleEndianConverter::toShort(short number) { 17 | QByteArray writeArray; 18 | writeArray.resize(2); 19 | qToLittleEndian(number, reinterpret_cast(writeArray.data())); 20 | return writeArray; 21 | } 22 | 23 | QByteArray LittleEndianConverter::toInt(int number) { 24 | QByteArray writeArray; 25 | writeArray.resize(4); 26 | qToLittleEndian(number, reinterpret_cast(writeArray.data())); 27 | return writeArray; 28 | } 29 | 30 | QByteArray LittleEndianConverter::toFloat(float number) { 31 | QByteArray writeArray; 32 | writeArray.resize(4); 33 | qToLittleEndian(number, reinterpret_cast(writeArray.data())); 34 | return writeArray; 35 | } 36 | 37 | int LittleEndianConverter::readInt(QFile &file) { 38 | return fromInt(file.read(4)); 39 | } 40 | 41 | short LittleEndianConverter::readShort(QFile &file) { 42 | return fromShort(file.read(2)); 43 | } -------------------------------------------------------------------------------- /Source/Converters/LittleEndianConverter.h: -------------------------------------------------------------------------------- 1 | #ifndef LITTLEENDIANCONVERTER_H 2 | #define LITTLEENDIANCONVERTER_H 3 | 4 | #include 5 | #include 6 | 7 | class LittleEndianConverter { 8 | public: 9 | short fromShort(QByteArray array); 10 | int fromInt(QByteArray array); 11 | float fromFloat(QByteArray array); 12 | QByteArray toShort(short number); 13 | QByteArray toInt(int number); 14 | QByteArray toFloat(float number); 15 | int readInt(QFile &file); 16 | short readShort(QFile &file); 17 | }; 18 | 19 | #endif // LITTLEENDIANCONVERTER_H -------------------------------------------------------------------------------- /Source/Converters/NosModelConverter.cpp: -------------------------------------------------------------------------------- 1 | #include "NosModelConverter.h" 2 | #include "LittleEndianConverter.h" 3 | #include 4 | #include 5 | 6 | NosModelConverter::NosModelConverter() { 7 | this->littleEndianConverter = new LittleEndianConverter(); 8 | } 9 | 10 | Model *NosModelConverter::fromBinary(const QByteArray &obj) { 11 | auto *model = new Model(); 12 | float uvScale = littleEndianConverter->fromFloat(obj.mid(0x30, 4)); 13 | int verticesCount = littleEndianConverter->fromShort(obj.mid(0x34, 2)); 14 | int offset = 0x36; 15 | model->uvScale = uvScale; 16 | model->vertices = readNosVertices(obj, offset, verticesCount); 17 | model->uv = readNosUV(obj, offset, verticesCount, uvScale); 18 | model->normals = readNosNormals(obj, offset, verticesCount); 19 | QVector faces = readNosFaces(obj, offset); 20 | model = readModelConstruction(obj, offset, model, faces); 21 | return model; 22 | } 23 | 24 | QByteArray NosModelConverter::toBinary(Model *model) { 25 | QByteArray newContent; 26 | newContent.append(littleEndianConverter->toFloat(model->uvScale)); 27 | newContent.append(littleEndianConverter->toShort(model->vertices.size())); 28 | for (auto &vertex : model->vertices) { 29 | newContent.append(littleEndianConverter->toFloat(vertex.x())); 30 | newContent.append(littleEndianConverter->toFloat(vertex.y())); 31 | newContent.append(littleEndianConverter->toFloat(vertex.z())); 32 | } 33 | for (int i = 0; i < model->uv.size(); i++) { 34 | float u = model->uv[i].x(); 35 | u /= model->uvScale; 36 | float v = model->uv[i].y(); 37 | v = (1.0 - v) / model->uvScale; 38 | newContent.append(littleEndianConverter->toShort(u)); 39 | newContent.append(littleEndianConverter->toShort(v)); 40 | } 41 | for (auto &normal : model->normals) { 42 | uint8_t x = normal.x() * 0x7F; 43 | uint8_t y = normal.y() * 0x7F; 44 | uint8_t z = normal.z() * 0x7F; 45 | newContent.append(x); 46 | newContent.append(y); 47 | newContent.append(z); 48 | } 49 | newContent.append(littleEndianConverter->toShort(model->groups.size())); 50 | for (auto &group : model->groups) { 51 | newContent.append(littleEndianConverter->toShort(group.faces.size() * 3)); // GroupSize 52 | for (auto &face : group.faces) { 53 | newContent.append(littleEndianConverter->toShort(face.x())); 54 | newContent.append(littleEndianConverter->toShort(face.y())); 55 | newContent.append(littleEndianConverter->toShort(face.z())); 56 | } 57 | } 58 | newContent.append(littleEndianConverter->toShort(model->objects.size())); // ObjectCount 59 | for (int m = 0; m < model->objects.size(); m++) { 60 | newContent.append(littleEndianConverter->toFloat(model->objects[m].position.x())); // x 61 | newContent.append(littleEndianConverter->toFloat(model->objects[m].position.y())); // y 62 | newContent.append(littleEndianConverter->toFloat(model->objects[m].position.z())); // z 63 | 64 | newContent.append(littleEndianConverter->toShort(model->objects[m].rotation.x() * 0x7FFF)); // x rotation 65 | newContent.append(littleEndianConverter->toShort(model->objects[m].rotation.y() * 0x7FFF)); // y rotation 66 | newContent.append(littleEndianConverter->toShort(model->objects[m].rotation.z() * 0x7FFF)); // z rotation 67 | newContent.append(littleEndianConverter->toShort(model->objects[m].rotation.w() * 0x7FFF)); // w rotation 68 | 69 | newContent.append(littleEndianConverter->toFloat(model->objects[m].scale.x())); // x scale 70 | newContent.append(littleEndianConverter->toFloat(model->objects[m].scale.y())); // y scale 71 | newContent.append(littleEndianConverter->toFloat(model->objects[m].scale.z())); // z scale 72 | 73 | newContent.append( 74 | littleEndianConverter->toShort(model->objects[m].animationPositions.size())); // translationFrameCount 75 | for (auto &animationPosition : model->objects[m].animationPositions) { 76 | newContent.append(littleEndianConverter->toShort(animationPosition.timestamp)); 77 | newContent.append(littleEndianConverter->toFloat(animationPosition.position.x())); 78 | newContent.append(littleEndianConverter->toFloat(animationPosition.position.y())); 79 | newContent.append(littleEndianConverter->toFloat(animationPosition.position.z())); 80 | } 81 | newContent.append(littleEndianConverter->toShort(model->objects[m].animationRotations.size())); // rotationFrameCount 82 | for (auto &animationRotation : model->objects[m].animationRotations) { 83 | newContent.append(littleEndianConverter->toShort(animationRotation.timestamp)); 84 | newContent.append(littleEndianConverter->toShort(animationRotation.rotation.x() * 0x7FFF)); 85 | newContent.append(littleEndianConverter->toShort(animationRotation.rotation.y() * 0x7FFF)); 86 | newContent.append(littleEndianConverter->toShort(animationRotation.rotation.z() * 0x7FFF)); 87 | newContent.append(littleEndianConverter->toShort(animationRotation.rotation.w() * 0x7FFF)); 88 | } 89 | newContent.append(littleEndianConverter->toShort(model->objects[m].animationScales.size())); // scaleFrameCount 90 | for (auto &animationScale : model->objects[m].animationScales) { 91 | newContent.append(littleEndianConverter->toShort(animationScale.timestamp)); 92 | newContent.append(littleEndianConverter->toFloat(animationScale.scale.x())); 93 | newContent.append(littleEndianConverter->toFloat(animationScale.scale.y())); 94 | newContent.append(littleEndianConverter->toFloat(animationScale.scale.z())); 95 | } 96 | newContent.append(littleEndianConverter->toShort(model->objects[m].groups.size())); 97 | for (int i = 0; i < model->objects[m].groups.size(); i++) { 98 | newContent.append(littleEndianConverter->toInt(model->groups[model->objects[m].groups[i]].texture)); 99 | newContent.append((char) 0); // bool 100 | newContent.append(littleEndianConverter->toShort(model->groups[model->objects[m].groups[i]].number)); // group 101 | } 102 | newContent.append(littleEndianConverter->toShort(0)); 103 | } 104 | return newContent; 105 | } 106 | 107 | QVector NosModelConverter::readNosVertices(const QByteArray &obj, int &offset, int verticesCount) { 108 | QVector vertices; 109 | for (int i = 0; i < verticesCount; i++) { 110 | float x = littleEndianConverter->fromFloat(obj.mid(offset, 4)); 111 | float y = littleEndianConverter->fromFloat(obj.mid(offset + 4, 4)); 112 | float z = littleEndianConverter->fromFloat(obj.mid(offset + 8, 4)); 113 | vertices.append(QVector3D(x, y, z)); 114 | offset += 12; 115 | } 116 | return vertices; 117 | } 118 | 119 | QVector NosModelConverter::readNosNormals(const QByteArray &obj, int &offset, int verticesCount) { 120 | QVector normals; 121 | for (int i = 0; i < verticesCount; i++) { 122 | float x = (float) obj.at(offset) / 0x7F; 123 | float y = (float) obj.at(offset + 1) / 0x7F; 124 | float z = (float) obj.at(offset + 2) / 0x7F; 125 | normals.append(QVector3D(x, y, z)); 126 | offset += 3; 127 | } 128 | return normals; 129 | } 130 | 131 | QVector NosModelConverter::readNosUV(const QByteArray &obj, int &offset, int verticesCount, float uvScale) { 132 | QVector uv; 133 | for (int i = 0; i < verticesCount; i++) { 134 | float u = uvScale * littleEndianConverter->fromShort(obj.mid(offset, 2)); 135 | float v = 1.0 - (uvScale * littleEndianConverter->fromShort(obj.mid(offset + 2, 2))); 136 | uv.append(QVector2D(u, v)); 137 | offset += 4; 138 | } 139 | return uv; 140 | } 141 | 142 | QVector NosModelConverter::readNosFaces(const QByteArray &obj, int &offset) { 143 | QVector groups; 144 | int groupCount = littleEndianConverter->fromShort(obj.mid(offset, 2)); 145 | offset += 2; 146 | for (int g = 0; g < groupCount; g++) { 147 | ModelGroup group; 148 | group.number = g; 149 | int points = (uint16_t) littleEndianConverter->fromShort(obj.mid(offset, 2)); 150 | offset += 2; 151 | for (int i = 0; i < points / 3; i++) { 152 | int p1 = littleEndianConverter->fromShort(obj.mid(offset, 2)); 153 | int p2 = littleEndianConverter->fromShort(obj.mid(offset + 2, 2)); 154 | int p3 = littleEndianConverter->fromShort(obj.mid(offset + 4, 2)); 155 | group.faces.append(QVector3D(p1, p2, p3)); 156 | offset += 6; 157 | } 158 | groups.append(group); 159 | } 160 | return groups; 161 | } 162 | 163 | Model *NosModelConverter::readModelConstruction(const QByteArray &obj, int &offset, Model *model, QVector groups) { 164 | int parts = littleEndianConverter->fromShort(obj.mid(offset, 2)); 165 | offset += 2; 166 | for (int p = 0; p < parts; p++) { 167 | ModelObject modelPart; 168 | float x = littleEndianConverter->fromFloat(obj.mid(offset, 4)); 169 | float y = littleEndianConverter->fromFloat(obj.mid(offset + 4, 4)); 170 | float z = littleEndianConverter->fromFloat(obj.mid(offset + 8, 4)); 171 | modelPart.position = QVector3D(x, y, z); 172 | offset += 12; 173 | float rotation_x = (float) littleEndianConverter->fromShort(obj.mid(offset, 2)) / 0x7FFF; 174 | float rotation_y = (float) littleEndianConverter->fromShort(obj.mid(offset + 2, 2)) / 0x7FFF; 175 | float rotation_z = (float) littleEndianConverter->fromShort(obj.mid(offset + 4, 2)) / 0x7FFF; 176 | float rotation_w = (float) littleEndianConverter->fromShort(obj.mid(offset + 6, 2)) / 0x7FFF; 177 | modelPart.rotation = QVector4D(rotation_x, rotation_y, rotation_z, rotation_w); 178 | offset += 8; 179 | float scale_x = littleEndianConverter->fromFloat(obj.mid(offset, 4)); 180 | float scale_y = littleEndianConverter->fromFloat(obj.mid(offset + 4, 4)); 181 | float scale_z = littleEndianConverter->fromFloat(obj.mid(offset + 8, 4)); 182 | modelPart.scale = QVector3D(scale_x, scale_y, scale_z); 183 | offset += 12; 184 | int translationFrameCount = littleEndianConverter->fromShort(obj.mid(offset, 2)); 185 | offset += 2; 186 | for (int i = 0; i < translationFrameCount; i++) { 187 | short time = littleEndianConverter->fromShort(obj.mid(offset, 2)); 188 | float frame_x = littleEndianConverter->fromFloat(obj.mid(offset + 2, 4)); 189 | float frame_y = littleEndianConverter->fromFloat(obj.mid(offset + 6, 4)); 190 | float frame_z = littleEndianConverter->fromFloat(obj.mid(offset + 10, 4)); 191 | ModelAnimationPositionFrame aPos; 192 | aPos.timestamp = time; 193 | aPos.position = QVector3D(frame_x, frame_y, frame_z); 194 | modelPart.animationPositions.append(aPos); 195 | offset += 14; 196 | } 197 | int rotationFrameCount = littleEndianConverter->fromShort(obj.mid(offset, 2)); 198 | offset += 2; 199 | for (int i = 0; i < rotationFrameCount; i++) { 200 | short time = littleEndianConverter->fromShort(obj.mid(offset, 2)); 201 | float frame_rotation_x = (float) littleEndianConverter->fromShort(obj.mid(offset + 2, 2)) / 0x7FFF; 202 | float frame_rotation_y = (float) littleEndianConverter->fromShort(obj.mid(offset + 4, 2)) / 0x7FFF; 203 | float frame_rotation_z = (float) littleEndianConverter->fromShort(obj.mid(offset + 6, 2)) / 0x7FFF; 204 | float frame_rotation_w = (float) littleEndianConverter->fromShort(obj.mid(offset + 8, 2)) / 0x7FFF; 205 | ModelAnimationRotationFrame aPos; 206 | aPos.timestamp = time; 207 | aPos.rotation = QVector4D(frame_rotation_x, frame_rotation_y, frame_rotation_z, frame_rotation_w); 208 | modelPart.animationRotations.append(aPos); 209 | offset += 10; 210 | } 211 | int scaleFrameCount = littleEndianConverter->fromShort(obj.mid(offset, 2)); 212 | offset += 2; 213 | for (int i = 0; i < scaleFrameCount; i++) { 214 | short time = littleEndianConverter->fromShort(obj.mid(offset, 2)); 215 | float frame_scale_x = littleEndianConverter->fromFloat(obj.mid(offset + 2, 4)); 216 | float frame_scale_y = littleEndianConverter->fromFloat(obj.mid(offset + 6, 4)); 217 | float frame_scale_z = littleEndianConverter->fromFloat(obj.mid(offset + 10, 4)); 218 | ModelAnimationScaleFrame aPos; 219 | aPos.timestamp = time; 220 | aPos.scale = QVector3D(frame_scale_x, frame_scale_y, frame_scale_z); 221 | modelPart.animationScales.append(aPos); 222 | offset += 14; 223 | } 224 | int c = littleEndianConverter->fromShort(obj.mid(offset, 2)); 225 | offset += 2; 226 | for (int i = 0; i < c; i++) { 227 | int img = littleEndianConverter->fromInt(obj.mid(offset, 4)); 228 | offset += 4; 229 | bool smooth = obj.at(offset); // maybe means something else 230 | offset++; 231 | int grp = littleEndianConverter->fromShort(obj.mid(offset, 2)); // equals i? 232 | groups[grp].texture = img; 233 | model->groups.append(groups[grp]); 234 | modelPart.groups.append(grp); 235 | offset += 2; 236 | } 237 | littleEndianConverter->fromShort(obj.mid(offset, 2)); // seems to be always 0? 238 | offset += 2; 239 | model->objects.append(modelPart); 240 | } 241 | return model; 242 | } 243 | -------------------------------------------------------------------------------- /Source/Converters/NosModelConverter.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSMODELCONVERTER_H 2 | #define NOSMODELCONVERTER_H 3 | 4 | #include "IModelConverter.h" 5 | #include "LittleEndianConverter.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | class NosModelConverter : public IModelConverter { 14 | public: 15 | NosModelConverter(); 16 | Model *fromBinary(const QByteArray &obj); 17 | QByteArray toBinary(Model *model); 18 | private: 19 | QVector readNosVertices(const QByteArray &obj, int &offset, int verticesCount); 20 | QVector readNosNormals(const QByteArray &obj, int &offset, int verticesCount); 21 | QVector readNosUV(const QByteArray &obj, int &offset, int verticesCount, float uvScale); 22 | QVector readNosFaces(const QByteArray &obj, int &offset); 23 | Model *readModelConstruction(const QByteArray &obj, int &offset, Model *model, QVector groups); 24 | }; 25 | 26 | #endif // NOSMODELCONVERTER_H 27 | -------------------------------------------------------------------------------- /Source/Converters/ObjConverter.cpp: -------------------------------------------------------------------------------- 1 | #include "ObjConverter.h" 2 | #include 3 | #include 4 | #include 5 | 6 | ObjConverter::ObjConverter() { 7 | this->littleEndianConverter = new LittleEndianConverter(); 8 | } 9 | 10 | Model *ObjConverter::fromObj(const QString &obj) { 11 | QVector fileVertices; 12 | QVector fileNormals; 13 | QVector fileUV; 14 | QVector faces; 15 | QVector textures; 16 | int group = -1; 17 | int object = -1; 18 | QStringList lines = obj.split("\n"); 19 | for (const QString &line : lines) { 20 | QStringList parts = line.split(" ", QString::SplitBehavior::SkipEmptyParts); 21 | if (!parts.empty()) { 22 | if (parts[0] == "v") { 23 | fileVertices.append(QVector3D(parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat())); 24 | } else if (parts[0] == "vn") { 25 | fileNormals.append(QVector3D(parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat())); 26 | } else if (parts[0] == "vt") { 27 | fileUV.append(QVector2D(parts[1].toFloat(), parts[2].toFloat())); 28 | } else if (parts[0] == "f") { 29 | QStringList faceOneInfos = parts[1].split("/"); 30 | QStringList faceTwoInfos = parts[2].split("/"); 31 | QStringList faceThreeInfos = parts[3].split("/"); 32 | Face f; 33 | if (object == -1) 34 | object = 0; 35 | if (group == -1) 36 | group = 0; 37 | f.object = object; 38 | f.group = group; 39 | f.v = QVector3D(faceOneInfos[0].toFloat() - 1, faceTwoInfos[0].toFloat() - 1, 40 | faceThreeInfos[0].toFloat() - 1); 41 | f.vt = QVector3D(faceOneInfos[1].toFloat() - 1, faceTwoInfos[1].toFloat() - 1, 42 | faceThreeInfos[1].toFloat() - 1); 43 | f.vn = QVector3D(faceOneInfos[2].toFloat() - 1, faceTwoInfos[2].toFloat() - 1, 44 | faceThreeInfos[2].toFloat() - 1); 45 | faces.append(f); 46 | } else if (parts[0] == "usemtl") { 47 | QStringList texturemtl = parts[1].split("mtl-", QString::SplitBehavior::SkipEmptyParts); 48 | textures.append(texturemtl.at(0).toInt()); 49 | group++; 50 | } else if (parts[0] == "o") { 51 | object++; 52 | } 53 | } 54 | } 55 | QVector vertices; 56 | QVector normals; 57 | QVector uv; 58 | vertices.resize(fileVertices.size()); 59 | normals.resize(fileVertices.size()); 60 | uv.resize(fileVertices.size()); 61 | for (auto &face : faces) { 62 | if ((!normals[face.v.x()].isNull() && normals[face.v.x()] != fileNormals[face.vn.x()]) || 63 | (!uv[face.v.x()].isNull() && uv[face.v.x()] != fileUV[face.vt.x()])) { 64 | vertices.append(fileVertices[face.v.x()]); 65 | normals.append(fileNormals[face.vn.x()]); 66 | uv.append(fileUV[face.vt.x()]); 67 | face.v.setX(vertices.size() - 1); 68 | face.vn.setX(normals.size() - 1); 69 | face.vt.setX(uv.size() - 1); 70 | } else { 71 | vertices[face.v.x()] = fileVertices[face.v.x()]; 72 | normals[face.v.x()] = fileNormals[face.vn.x()]; 73 | uv[face.v.x()] = fileUV[face.vt.x()]; 74 | } 75 | if ((!normals[face.v.y()].isNull() && normals[face.v.y()] != fileNormals[face.vn.y()]) || 76 | (!uv[face.v.y()].isNull() && uv[face.v.y()] != fileUV[face.vt.y()])) { 77 | vertices.append(fileVertices[face.v.y()]); 78 | normals.append(fileNormals[face.vn.y()]); 79 | uv.append(fileUV[face.vt.y()]); 80 | face.v.setY(vertices.size() - 1); 81 | face.vn.setY(normals.size() - 1); 82 | face.vt.setY(uv.size() - 1); 83 | } else { 84 | vertices[face.v.y()] = fileVertices[face.v.y()]; 85 | normals[face.v.y()] = fileNormals[face.vn.y()]; 86 | uv[face.v.y()] = fileUV[face.vt.y()]; 87 | } 88 | if ((!normals[face.v.z()].isNull() && normals[face.v.z()] != fileNormals[face.vn.z()]) || 89 | (!uv[face.v.z()].isNull() && uv[face.v.z()] != fileUV[face.vt.z()])) { 90 | vertices.append(fileVertices[face.v.z()]); 91 | normals.append(fileNormals[face.vn.z()]); 92 | uv.append(fileUV[face.vt.z()]); 93 | face.v.setZ(vertices.size() - 1); 94 | face.vn.setZ(normals.size() - 1); 95 | face.vt.setZ(uv.size() - 1); 96 | } else { 97 | vertices[face.v.z()] = fileVertices[face.v.z()]; 98 | normals[face.v.z()] = fileNormals[face.vn.z()]; 99 | uv[face.v.z()] = fileUV[face.vt.z()]; 100 | } 101 | } 102 | Model *model = new Model(); 103 | model->vertices = vertices; 104 | model->normals = normals; 105 | model->uv = uv; 106 | model->uvScale = 0.0005; 107 | for (int i = 0; i <= object; i++) { 108 | ModelObject mo; 109 | mo.position = QVector3D(0, 0, 0); 110 | mo.rotation = QVector4D(0, 0, 0, 0); 111 | mo.scale = QVector3D(1, 1, 1); 112 | model->objects.append(mo); 113 | } 114 | for (int i = 0; i <= group; i++) { 115 | ModelGroup mg; 116 | mg.number = i; 117 | mg.texture = textures[i]; 118 | model->groups.append(mg); 119 | } 120 | for (auto &face : faces) { 121 | model->groups[face.group].faces.append(face.v); 122 | if (!model->objects[face.object].groups.contains(face.group)) 123 | model->objects[face.object].groups.append(face.group); 124 | } 125 | return model; 126 | } 127 | 128 | QStringList ObjConverter::toObj(Model *model, QString name) { 129 | QStringList list; 130 | list.append(generateObjFile(model, name)); 131 | list.append(generateMtlFile(model)); 132 | return list; 133 | } 134 | 135 | QString ObjConverter::generateObjFile(Model *model, const QString &name) { 136 | QString obj = ""; 137 | obj += "mtllib " + name + ".mtl\n\n"; 138 | for (QVector3D v : model->vertices) { 139 | obj += "v " + QString::number(v.x()) + " " + QString::number(v.y()) + " " + QString::number(v.z()) + "\n"; 140 | } 141 | for (QVector2D p : model->uv) { 142 | obj += "vt " + QString::number(p.x()) + " " + QString::number(p.y()) + "\n"; 143 | } 144 | for (QVector3D vn : model->normals) { 145 | obj += "vn " + QString::number(vn.x()) + " " + QString::number(vn.y()) + " " + QString::number(vn.z()) + "\n"; 146 | } 147 | for (int o = 0; o < model->objects.size(); o++) { 148 | obj += "\no " + QString::number(o) + "\n"; 149 | for (int g = 0; g < model->objects[o].groups.size(); g++) { 150 | obj += "usemtl mtl-" + QString::number(model->groups[model->objects[o].groups[g]].texture) + "\n"; 151 | for (QVector3D f : model->groups[model->objects[o].groups[g]].faces) { 152 | obj += "f " + QString::number(f.x() + 1) + "/" + QString::number(f.x() + 1) + "/" + 153 | QString::number(f.x() + 1) + " " + QString::number(f.y() + 1) + "/" + 154 | QString::number(f.y() + 1) + "/" + QString::number(f.y() + 1) + " " + 155 | QString::number(f.z() + 1) + "/" + QString::number(f.z() + 1) + "/" + 156 | QString::number(f.z() + 1) + "\n"; 157 | } 158 | } 159 | } 160 | return obj; 161 | } 162 | 163 | QString ObjConverter::generateMtlFile(Model *model) { 164 | QString mtl = ""; 165 | for (auto &group : model->groups) { 166 | mtl += "newmtl mtl-" + QString::number(group.texture) + "\n"; 167 | mtl += "Ns 10\nKa 0 0 0\nKd 0 0 0\nKs 0 0 0\n"; 168 | mtl += "map_Kd " + QString::number(group.texture) + ".png\n"; 169 | } 170 | return mtl; 171 | } 172 | -------------------------------------------------------------------------------- /Source/Converters/ObjConverter.h: -------------------------------------------------------------------------------- 1 | #ifndef OBJCONVERTER_H 2 | #define OBJCONVERTER_H 3 | 4 | #include "IModelConverter.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | struct Face { 13 | int object; 14 | int group; 15 | QVector3D v; 16 | QVector3D vt; 17 | QVector3D vn; 18 | }; 19 | 20 | class ObjConverter : public IModelConverter { 21 | public: 22 | ObjConverter(); 23 | Model *fromObj(const QString &obj); 24 | QStringList toObj(Model *model, QString name); 25 | private: 26 | QString generateObjFile(Model *model, const QString &name); 27 | QString generateMtlFile(Model *model); 28 | }; 29 | 30 | #endif // OBJCONVERTER_H 31 | -------------------------------------------------------------------------------- /Source/Decryptors/INosDecryptor.h: -------------------------------------------------------------------------------- 1 | #ifndef INOSDECRYPTOR_H 2 | #define INOSDECRYPTOR_H 3 | 4 | #include 5 | 6 | class INosDecryptor { 7 | public: 8 | virtual QByteArray encrypt(QByteArray &array) = 0; 9 | virtual QByteArray decrypt(QByteArray &array) = 0; 10 | }; 11 | 12 | #endif // INOSDECRYPTOR_H 13 | -------------------------------------------------------------------------------- /Source/Decryptors/NosTextDatFileDecryptor.cpp: -------------------------------------------------------------------------------- 1 | #include "NosTextDatFileDecryptor.h" 2 | 3 | NosTextDatFileDecryptor::NosTextDatFileDecryptor() 4 | : cryptoArray( 5 | {0x00, 0x20, 0x2D, 0x2E, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x0A, 0x00}) {} 6 | 7 | QByteArray NosTextDatFileDecryptor::encrypt(QByteArray &array) { 8 | std::vector mask = getMask(array); 9 | QByteArray string; 10 | int iterator = 0; 11 | int size = mask.size(); 12 | unsigned char switchByte = 0; 13 | while (iterator < size) { 14 | int len = iterator; 15 | while ((iterator < mask.size() && mask.at(iterator) == 0x30) || (iterator + 1 == mask.size()) || 16 | (iterator + 2 == mask.size()) || (iterator + 1 < mask.size() && mask.at(iterator + 1) == 0x30) || 17 | (iterator + 2 < mask.size() && mask.at(iterator + 2) == 0x30)) 18 | ++iterator; 19 | if (iterator > len) { 20 | for (int j = iterator - len; j > 0; j -= 0x7E) { 21 | int checker = j; 22 | if (checker > 0x7E) { 23 | checker = 0x7E; 24 | } 25 | string.push_back(checker); 26 | for (; checker > 0; --checker) { 27 | unsigned char byteToAdd = array[len] ^0x33; 28 | len++; 29 | string.push_back(byteToAdd); 30 | } 31 | } 32 | } 33 | if (iterator >= size) 34 | break; 35 | len = iterator; 36 | int v25 = 1; 37 | while (iterator < mask.size() && mask.at(iterator) == 0x31) 38 | ++iterator; 39 | if (iterator > len) { 40 | for (int j = iterator - len; j > 0; j -= 0x7E) { 41 | int checker = j; 42 | if (j > 0x7E) 43 | checker = 0x7E; 44 | string.push_back(checker | 0x80); 45 | for (; checker > 0; --checker) { 46 | unsigned char byteToAdd = array[len]; 47 | len++; 48 | switch (byteToAdd) { 49 | case 32: 50 | switchByte = 1; 51 | break; 52 | case 45: 53 | switchByte = 2; 54 | break; 55 | case 46: 56 | switchByte = 3; 57 | break; 58 | case 0xFF: 59 | switchByte = 14; 60 | break; 61 | default: 62 | switchByte = byteToAdd - 0x2C; 63 | break; 64 | } 65 | if (v25) { 66 | string.push_back(0x10 * switchByte); 67 | v25 = 0; 68 | } else { 69 | string[string.size() - 1] = string[string.size() - 1] | switchByte; 70 | v25 = 1; 71 | } 72 | } 73 | } 74 | } 75 | } 76 | string.push_back(0xFF); 77 | return string; 78 | } 79 | 80 | QByteArray NosTextDatFileDecryptor::decrypt(QByteArray &array) { 81 | QByteArray decryptedFile; 82 | int currIndex = 0; 83 | while (currIndex < array.size()) { 84 | unsigned char currentByte = array.at(currIndex); 85 | currIndex++; 86 | if (currentByte == 0xFF) { 87 | decryptedFile.push_back(0xD); 88 | continue; 89 | } 90 | int validate = currentByte & 0x7F; 91 | if (currentByte & 0x80) { 92 | for (; validate > 0; validate -= 2) { 93 | if (currIndex >= array.size()) 94 | break; 95 | currentByte = array.at(currIndex); 96 | currIndex++; 97 | int firstByte = cryptoArray.at((currentByte & 0xF0) >> 4); 98 | decryptedFile.push_back(firstByte); 99 | if (validate <= 1) 100 | break; 101 | int secondByte = cryptoArray.at(currentByte & 0xF); 102 | if (!secondByte) 103 | break; 104 | decryptedFile.push_back(secondByte); 105 | } 106 | } else { 107 | for (; validate > 0; --validate) { 108 | if (currIndex >= array.size()) 109 | break; 110 | currentByte = array.at(currIndex); 111 | currIndex++; 112 | decryptedFile.push_back(currentByte ^ 0x33); 113 | } 114 | } 115 | } 116 | return decryptedFile; 117 | } 118 | 119 | NosTextDatFileDecryptor::~NosTextDatFileDecryptor() = default; 120 | 121 | std::vector NosTextDatFileDecryptor::getMask(QByteArray &array) { 122 | std::vector mask(array.size(), 0x30); 123 | for (std::size_t i = 0; i < mask.size(); i++) { 124 | unsigned char ch = array.at(i); 125 | if (!ch) 126 | break; 127 | if (ch -= 0x20) { 128 | ch -= 0xD; 129 | if (ch >= 2) { 130 | ch -= 0x3; 131 | bool test1 = ch < 0xA; 132 | ch -= 0xA; 133 | if (!test1 && ch != 0xC5) 134 | continue; 135 | } 136 | } 137 | mask[i] = 0x31; 138 | } 139 | return mask; 140 | } 141 | -------------------------------------------------------------------------------- /Source/Decryptors/NosTextDatFileDecryptor.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTEXTFILEDECRYPTOR_H 2 | #define NOSTEXTFILEDECRYPTOR_H 3 | 4 | #include "INosDecryptor.h" 5 | #include 6 | #include 7 | 8 | class NosTextDatFileDecryptor : public INosDecryptor { 9 | public: 10 | NosTextDatFileDecryptor(); 11 | ~NosTextDatFileDecryptor(); 12 | QByteArray encrypt(QByteArray &array); 13 | QByteArray decrypt(QByteArray &array); 14 | private: 15 | std::array cryptoArray; 16 | std::vector getMask(QByteArray &array); 17 | }; 18 | 19 | #endif // NOSTEXTFILEDECRYPTOR_H 20 | -------------------------------------------------------------------------------- /Source/Decryptors/NosTextOthersFileDecryptor.cpp: -------------------------------------------------------------------------------- 1 | #include "NosTextOthersFileDecryptor.h" 2 | 3 | NosTextOthersFileDecryptor::NosTextOthersFileDecryptor(LittleEndianConverter *littleEndianConverter) { 4 | this->littleEndianConverter = littleEndianConverter; 5 | } 6 | 7 | QByteArray NosTextOthersFileDecryptor::encrypt(QByteArray &array) { 8 | QByteArray result = littleEndianConverter->toInt(array.size()); 9 | for (auto &byte : array) { 10 | result.push_back(byte ^ 0x1); 11 | } 12 | return result; 13 | } 14 | 15 | QByteArray NosTextOthersFileDecryptor::decrypt(QByteArray &array) { 16 | QByteArray result; 17 | int lines = littleEndianConverter->fromInt(array.mid(0, 4)); 18 | int pos = 4; 19 | for (int i = 0; i < lines; i++) { 20 | int strLen = littleEndianConverter->fromInt(array.mid(pos, 4)); 21 | pos += 4; 22 | QByteArray str = array.mid(pos, strLen); 23 | pos += strLen; 24 | for (auto &byte : str) 25 | result.push_back(byte ^ 0x1); 26 | result.push_back('\n'); 27 | } 28 | return result; 29 | } 30 | -------------------------------------------------------------------------------- /Source/Decryptors/NosTextOthersFileDecryptor.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTEXTOTHERSFILEDECRYPTOR_H 2 | #define NOSTEXTOTHERSFILEDECRYPTOR_H 3 | 4 | #include "INosDecryptor.h" 5 | #include 6 | #include 7 | #include "../Converters/LittleEndianConverter.h" 8 | 9 | class NosTextOthersFileDecryptor : public INosDecryptor { 10 | public: 11 | NosTextOthersFileDecryptor(LittleEndianConverter *littleEndianConverter); 12 | QByteArray encrypt(QByteArray &array); 13 | QByteArray decrypt(QByteArray &array); 14 | private: 15 | LittleEndianConverter *littleEndianConverter; 16 | }; 17 | 18 | #endif // NOSTEXTOTHERSFILEDECRYPTOR_H 19 | -------------------------------------------------------------------------------- /Source/Decryptors/NosZlibDecryptor.cpp: -------------------------------------------------------------------------------- 1 | #include "NosZlibDecryptor.h" 2 | 3 | NosZlibDecryptor::NosZlibDecryptor() = default; 4 | 5 | QByteArray NosZlibDecryptor::encrypt(QByteArray &array, int level) { 6 | return qCompress(array, level); 7 | } 8 | 9 | QByteArray NosZlibDecryptor::decrypt(QByteArray &array) { 10 | return qUncompress(array); 11 | } 12 | -------------------------------------------------------------------------------- /Source/Decryptors/NosZlibDecryptor.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSZLIBDECRYPTOR_H 2 | #define NOSZLIBDECRYPTOR_H 3 | 4 | #include "INosDecryptor.h" 5 | #include 6 | 7 | class NosZlibDecryptor { 8 | public: 9 | NosZlibDecryptor(); 10 | QByteArray encrypt(QByteArray &array, int level = 1); 11 | QByteArray decrypt(QByteArray &array); 12 | }; 13 | 14 | #endif // NOSZLIBDECRYPTOR_H 15 | -------------------------------------------------------------------------------- /Source/MainWindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include "Openers/NosCCInfOpener.h" 5 | #include "Openers/NosTextOpener.h" 6 | #include "Openers/NosZlibOpener.h" 7 | #include "Ui/Previews/SingleImagePreview.h" 8 | #include "Ui/Previews/SingleTextFilePreview.h" 9 | #include "Ui/Settings.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace Ui { 18 | class MainWindow; 19 | } 20 | class MainWindow : public QMainWindow { 21 | Q_OBJECT 22 | public: 23 | explicit MainWindow(QWidget *parent = nullptr); 24 | ~MainWindow() override; 25 | const QString VERSION = "v0.7.1"; 26 | public slots: 27 | void on_actionOpen_triggered(); 28 | void on_treeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev); 29 | void replaceInfo(FileInfo *info); 30 | void on_actionReplace_triggered(); 31 | void on_actionExport_triggered(); 32 | void on_actionExport_to_raw_triggered(); 33 | void on_actionReplace_with_raw_triggered(); 34 | void on_actionSave_triggered(); 35 | void on_actionSave_as_triggered(); 36 | void on_actionExport_with_config_triggered(); 37 | void on_actionImport_from_config_triggered(); 38 | void on_actionApplyPatch_triggered(); 39 | void on_addButton_clicked(); 40 | void on_deleteButton_clicked(); 41 | void filterItems(); 42 | void onCustomMenuShow(const QPoint &point); 43 | void clearMenu(); 44 | void on_actionClose_selected_triggered(); 45 | void on_actionClose_all_triggered(); 46 | void on_actionSettings_triggered(); 47 | void on_actionExit_triggered(); 48 | void on_actionAbout_triggered(); 49 | void on_actionHelp_triggered(); 50 | void on_actionRename_triggered(); 51 | void itemEdited(QTreeWidgetItem *item, int column); 52 | private: 53 | Ui::MainWindow *ui; 54 | Settings *settings; 55 | QMenu *contextMenu = nullptr; 56 | NosTextOpener textOpener; 57 | NosZlibOpener zlibOpener; 58 | NosCCInfOpener ccinfOpener; 59 | JsonConfigOpener jsonOpener; 60 | void openFile(const QString &path); 61 | void handleOpenResults(OnexTreeItem *item, const QString &path); 62 | INosFileOpener *getOpener(const QByteArray &header); 63 | template 64 | void fileOperationOnSelectedItems(TreeFunction treeFunction, const QString &defaultPath, QString operationName, bool saveDialog, 65 | QString filter = QString()); 66 | QString getSelectedDirectory(const QString &suggestion); 67 | QString getOpenFile(const QString &suggestion, const QString &filter); 68 | QStringList getOpenFiles(const QString &suggestion, const QString &filter); 69 | QString getSaveFile(const QString &suggestion, const QString &filter); 70 | void dropEvent(QDropEvent *e) override; 71 | void dragEnterEvent(QDragEnterEvent *e) override; 72 | OnexTreeItem *getTreeRoot(); 73 | QString neatPath(QString path); 74 | }; 75 | 76 | #endif // MAINWINDOW_H 77 | -------------------------------------------------------------------------------- /Source/NosEnumTypes.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSENUMTYPES_H 2 | #define NOSENUMTYPES_H 3 | #define PCHPKG 1 4 | #define NStuData 2 5 | #define NStkData 3 6 | #define NStcData 5 7 | #define NStgData 6 8 | #define NStpData 7 9 | #define NStsData 9 10 | #define NStgeData 10 11 | #define NStpeData 11 12 | #define NStpuData 12 13 | #define NSpcData 13 14 | #define NSppData 14 15 | #define NSpmData 15 16 | #define NSmcData 16 17 | #define NSmpData 17 18 | #define NSedData 20 19 | #define NSemData 21 20 | #define NSesData 22 21 | #define NSeffData 23 22 | #define NSipData 24 23 | #define NSgrdData 26 24 | #define NS4BbData 101 // temporary 25 | #define NSipData2006 103 26 | 27 | /* 28 | #define NSpnData 29 | #define NSmnData 30 | 31 | #define PCHPKG_DATA 32 | 33 | #define NScliData 34 | #define NSetcData 35 | #define NSlangData 36 | #define NSgtdData 37 | */ 38 | 39 | #endif // NOSENUMTYPES_H 40 | -------------------------------------------------------------------------------- /Source/Openers/INosFileOpener.cpp: -------------------------------------------------------------------------------- 1 | #include "INosFileOpener.h" 2 | 3 | QString INosFileOpener::neatFileName(QString fileName) { 4 | QStringList list = fileName.split("/"); 5 | if (list.empty()) 6 | return fileName; 7 | return list.back(); 8 | } 9 | 10 | LittleEndianConverter *INosFileOpener::getLittleEndianConverter() { 11 | return &littleEndianConverter; 12 | } -------------------------------------------------------------------------------- /Source/Openers/INosFileOpener.h: -------------------------------------------------------------------------------- 1 | #ifndef INOSFILEOPENER_H 2 | #define INOSFILEOPENER_H 3 | 4 | #include 5 | #include 6 | #include "../Converters/LittleEndianConverter.h" 7 | 8 | class OnexTreeItem; 9 | 10 | class INosFileOpener { 11 | public: 12 | virtual OnexTreeItem *decrypt(QFile &file) = 0; 13 | virtual QByteArray encrypt(OnexTreeItem *item) = 0; 14 | LittleEndianConverter *getLittleEndianConverter(); 15 | virtual OnexTreeItem *getEmptyItem(const QByteArray &header) = 0; 16 | protected: 17 | LittleEndianConverter littleEndianConverter; 18 | QString neatFileName(QString fileName); 19 | }; 20 | 21 | #endif // INOSFILEOPENER_H 22 | -------------------------------------------------------------------------------- /Source/Openers/JsonConfigOpener.cpp: -------------------------------------------------------------------------------- 1 | #include "JsonConfigOpener.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | OnexTreeItem *JsonConfigOpener::load(QFile &file, const QString &directory) { 13 | QJsonObject jo = QJsonDocument::fromJson(file.readAll()).object(); 14 | int headerNumber = getNTHeaderNumber(QByteArray::fromHex(jo["Header"].toString().toLocal8Bit())); 15 | if (headerNumber == -1) { 16 | QMessageBox::critical(nullptr, "Not supported!", "This feature is not available for this archive"); 17 | return nullptr; 18 | } 19 | 20 | OnexTreeItem *root = generateRoot(headerNumber, jo); 21 | 22 | QJsonArray contentArray = jo["content"].toArray(); 23 | for (auto &&i : contentArray) { 24 | OnexTreeItem *item = generateItem(headerNumber, i.toObject(), directory); 25 | root->addChild(item); 26 | } 27 | 28 | return root; 29 | } 30 | 31 | void JsonConfigOpener::load(OnexTreeItem *root, QFile &file, const QString &directory) { 32 | QJsonObject jo = QJsonDocument::fromJson(file.readAll()).object(); 33 | int headerNumber = getNTHeaderNumber(QByteArray::fromHex(jo["Header"].toString().toLocal8Bit())); 34 | if (headerNumber == -1) { 35 | QMessageBox::critical(nullptr, "Not supported!", "This feature is not available for this archive"); 36 | return; 37 | } 38 | 39 | if (root->getContent() != QByteArray::fromHex(jo["Header"].toString().toLocal8Bit())) { 40 | QMessageBox::critical(nullptr, "Error!", "The patch is not valid for this .NOS!"); 41 | return; 42 | } 43 | 44 | QJsonArray contentArray = jo["content"].toArray(); 45 | int added = 0; 46 | int replaced = 0; 47 | for (auto &&i : contentArray) { 48 | OnexTreeItem *item = generateItem(headerNumber, i.toObject(), directory); 49 | if (findChildByName(root, item->getName()) != -1) { 50 | delete root->takeChild(findChildByName(root, item->getName())); 51 | replaced++; 52 | } else 53 | added++; 54 | root->addChild(item); 55 | } 56 | 57 | QMessageBox::information(nullptr, "End of operation", "Replaced " + QString::number(replaced) + " and added " + QString::number(added) + " file(s)"); 58 | } 59 | 60 | OnexTreeItem *JsonConfigOpener::generateRoot(int headerNumber, const QJsonObject &jo) { 61 | switch (headerNumber) { 62 | case NSipData: 63 | return new OnexNSipData(jo["Archive"].toString(), QByteArray::fromHex(jo["Header"].toString().toLocal8Bit()), &zlibOpener); 64 | case NS4BbData: 65 | return new OnexNS4BbData(jo["Archive"].toString(), QByteArray::fromHex(jo["Header"].toString().toLocal8Bit()), &zlibOpener); 66 | case NStcData: 67 | return new OnexNStcData(jo["Archive"].toString(), QByteArray::fromHex(jo["Header"].toString().toLocal8Bit()), &zlibOpener); 68 | case NStpData: 69 | case NStpeData: 70 | case NStpuData: 71 | return new OnexNStpData(jo["Archive"].toString(), QByteArray::fromHex(jo["Header"].toString().toLocal8Bit()), &zlibOpener); 72 | case NSmpData: 73 | case NSppData: 74 | return new OnexNSmpData(jo["Archive"].toString(), QByteArray::fromHex(jo["Header"].toString().toLocal8Bit()), &zlibOpener); 75 | case NStgData: 76 | case NStgeData: 77 | return new OnexNStgData(jo["Archive"].toString(), QByteArray::fromHex(jo["Header"].toString().toLocal8Bit()), &zlibOpener); 78 | case NSmcData: 79 | case NSpcData: 80 | return new OnexNSmcData(jo["Archive"].toString(), QByteArray::fromHex(jo["Header"].toString().toLocal8Bit()), &zlibOpener); 81 | case 199: 82 | return new OnexTreeText(jo["Archive"].toString(), &textOpener, jo["Last Edit"].toString()); 83 | default: 84 | return nullptr; 85 | } 86 | } 87 | 88 | OnexTreeItem *JsonConfigOpener::generateItem(int headerNumber, const QJsonObject &jo, const QString &directory) { 89 | switch (headerNumber) { 90 | case NSipData: 91 | return new OnexNSipData(jo, &zlibOpener, directory); 92 | case NS4BbData: 93 | return new OnexNS4BbData(jo, &zlibOpener, directory); 94 | case NStcData: 95 | return new OnexNStcData(jo, &zlibOpener, directory); 96 | case NStpData: 97 | case NStpeData: 98 | case NStpuData: 99 | return new OnexNStpData(jo, &zlibOpener, directory); 100 | case NSmpData: 101 | case NSppData: 102 | return new OnexNSmpData(jo, &zlibOpener, directory); 103 | case NStgData: 104 | case NStgeData: 105 | return new OnexNStgData(jo, &zlibOpener, directory); 106 | case NSmcData: 107 | case NSpcData: 108 | return new OnexNSmcData(jo, &zlibOpener, directory); 109 | case 199: 110 | return new OnexTreeText(jo, &textOpener, directory); 111 | default: 112 | return nullptr; 113 | } 114 | } 115 | 116 | int JsonConfigOpener::getNTHeaderNumber(const QString &header) { 117 | if (header.mid(0, 7) == "NT Data") 118 | return header.mid(8, 2).toInt(); 119 | else if (header.mid(0, 10) == "32GBS V1.0") 120 | return NS4BbData; 121 | else if (header.mid(0, 10) == "ITEMS V1.0") 122 | return NSipData2006; 123 | else if (header.isEmpty()) 124 | return 199; 125 | else 126 | return -1; 127 | } 128 | 129 | int JsonConfigOpener::findChildByName(OnexTreeItem *item, const QString &searched) { 130 | for (int c = 0; c < item->childCount(); c++) { 131 | if (item->child(c)->text(0) == searched) 132 | return c; 133 | } 134 | return -1; 135 | } -------------------------------------------------------------------------------- /Source/Openers/JsonConfigOpener.h: -------------------------------------------------------------------------------- 1 | #ifndef JSONCONFIGOPENER_H 2 | #define JSONCONFIGOPENER_H 3 | 4 | 5 | #include 6 | #include 7 | #include "NosZlibOpener.h" 8 | #include "NosTextOpener.h" 9 | 10 | class JsonConfigOpener { 11 | public: 12 | OnexTreeItem *load(QFile &file, const QString &directory); 13 | void load(OnexTreeItem *root, QFile &file, const QString &directory); 14 | private: 15 | NosTextOpener textOpener; 16 | NosZlibOpener zlibOpener; 17 | OnexTreeItem *generateRoot(int headerNumber, const QJsonObject &jo); 18 | OnexTreeItem *generateItem(int headerNumber, const QJsonObject &jo, const QString &directory); 19 | int getNTHeaderNumber(const QString &header); 20 | int findChildByName(OnexTreeItem *item, const QString &searched); 21 | }; 22 | 23 | 24 | #endif //JSONCONFIGOPENER_H 25 | -------------------------------------------------------------------------------- /Source/Openers/NosCCInfOpener.cpp: -------------------------------------------------------------------------------- 1 | #include "NosCCInfOpener.h" 2 | #include "../Ui/TreeItems/OnexNSmnData.h" 3 | #include 4 | #include 5 | #include 6 | 7 | NosCCInfOpener::NosCCInfOpener() = default; 8 | 9 | OnexTreeItem *NosCCInfOpener::decrypt(QFile &file) { 10 | file.seek(0); 11 | 12 | QByteArray header = file.read(0xC); 13 | int creationDate = littleEndianConverter.readInt(file); 14 | 15 | OnexNSmnData *item = new OnexNSmnData(neatFileName(file.fileName()), creationDate, this, header); 16 | bool playerInfos = item->getName().contains("NSpn"); 17 | 18 | int fileSize = littleEndianConverter.readInt(file); 19 | littleEndianConverter.readInt(file); // second time the same bytes, idk why 20 | file.read(1); // 0x00 21 | int fileAmount = littleEndianConverter.readInt(file); 22 | 23 | for (int i = 0; i < fileAmount; i++) { 24 | 25 | QJsonObject jo; 26 | jo["direction"] = file.read(1).at(0); 27 | jo["animation"] = file.read(1).at(0); 28 | 29 | if (playerInfos) { 30 | uint8_t classSex = (file.read(1).at(0)); 31 | jo["sex"] = (uint8_t) ((classSex & 0x80) != 0); 32 | jo["class"] = classSex & 0x0F; 33 | jo["morph"] = file.read(1).at(0); 34 | } else { 35 | jo["monster"] = littleEndianConverter.readShort(file); 36 | } 37 | 38 | jo["base"] = littleEndianConverter.readInt(file); 39 | jo["nspm"] = littleEndianConverter.readInt(file); 40 | jo["kit"] = littleEndianConverter.readInt(file); 41 | 42 | QJsonObject parts; 43 | for (int j = 0; j < 7; j++) { 44 | QString textureName = QString::number(j); 45 | if (playerInfos) { 46 | textureName = getTextureName(j); 47 | } 48 | 49 | int count = file.read(1).at(0); 50 | QJsonArray sprites; 51 | for (int c = 0; c < count; c++) { 52 | QJsonObject sprite; 53 | sprite["index"] = littleEndianConverter.readShort(file); 54 | sprite["id"] = littleEndianConverter.readInt(file); 55 | sprites.append(sprite); 56 | } 57 | parts.insert(textureName, sprites); 58 | } 59 | jo.insert("parts", parts); 60 | 61 | QByteArray array = QJsonDocument(jo).toJson(); 62 | item->addChild(new OnexNSmnData(QString::number(i), creationDate, this, array)); 63 | } 64 | return item; 65 | } 66 | 67 | QByteArray NosCCInfOpener::encrypt(OnexTreeItem *item) { 68 | bool playerInfos = item->getName().contains("NSpn"); 69 | OnexNSmnData *nsmnItem = static_cast(item); 70 | 71 | QByteArray content; 72 | QByteArray head; 73 | 74 | content.append(littleEndianConverter.toInt(item->childCount())); 75 | for (int i = 0; i != item->childCount(); ++i) { 76 | OnexNSmnData *ct = static_cast(item->child(i)); 77 | QJsonObject jo = QJsonDocument::fromJson(ct->getContent()).object(); 78 | content.append((uint8_t) jo["direction"].toInt()); 79 | content.append((uint8_t) jo["animation"].toInt()); 80 | if (playerInfos) { 81 | int sex = jo["sex"].toInt() == 0 ? 0 : 0x80; 82 | int cClass = jo["class"].toInt(); 83 | uint8_t classSex = sex + cClass; 84 | content.append((uint8_t) classSex); 85 | content.append((uint8_t) jo["morph"].toInt()); 86 | } else { 87 | content.append(littleEndianConverter.toShort(jo["monster"].toInt())); 88 | } 89 | 90 | content.append(littleEndianConverter.toInt(jo["base"].toInt())); 91 | content.append(littleEndianConverter.toInt(jo["nspm"].toInt())); 92 | content.append(littleEndianConverter.toInt(jo["kit"].toInt())); 93 | 94 | QJsonObject parts = jo["parts"].toObject(); 95 | 96 | for (int j = 0; j < 7; j++) { 97 | QString textureName = QString::number(j); 98 | if (playerInfos) { 99 | textureName = getTextureName(j); 100 | } 101 | 102 | QJsonArray sprites = parts[textureName].toArray(); 103 | content.append((uint8_t) sprites.size()); 104 | for (QJsonValueRef sprite : sprites) { 105 | content.append(littleEndianConverter.toShort(sprite.toObject()["index"].toInt())); 106 | content.append(littleEndianConverter.toInt(sprite.toObject()["id"].toInt())); 107 | } 108 | } 109 | } 110 | 111 | head.append(nsmnItem->getContent()); 112 | head.append(littleEndianConverter.toInt(nsmnItem->getCreationDate())); 113 | head.append(littleEndianConverter.toInt(content.size())); // fileSize 114 | head.append(littleEndianConverter.toInt(content.size())); // fileSize 115 | head.append((char) 0x0); 116 | 117 | content = head.append(content); 118 | 119 | return content; 120 | } 121 | 122 | QString NosCCInfOpener::getTextureName(int index) { 123 | switch (index) { 124 | case 0: 125 | return "armor"; 126 | case 1: 127 | return "hair"; 128 | case 2: 129 | return "hat"; 130 | case 3: 131 | return "mask"; 132 | case 4: 133 | return "secondary-Weapon"; 134 | case 5: 135 | return "first-Weapon"; 136 | case 6: 137 | return "weapon-part"; 138 | default: 139 | return QString::number(index); 140 | } 141 | } 142 | 143 | OnexTreeItem *NosCCInfOpener::getEmptyItem(const QByteArray &header) { 144 | return nullptr; 145 | } 146 | -------------------------------------------------------------------------------- /Source/Openers/NosCCInfOpener.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSCCINFOPENER_H 2 | #define NOSCCINFOPENER_H 3 | 4 | #include "INosFileOpener.h" 5 | #include "../Ui/OnexTreeItem.h" 6 | #include 7 | #include 8 | 9 | class NosCCInfOpener : public QObject, public INosFileOpener { 10 | Q_OBJECT 11 | public: 12 | NosCCInfOpener(); 13 | OnexTreeItem *decrypt(QFile &file); 14 | QByteArray encrypt(OnexTreeItem *item); 15 | OnexTreeItem *getEmptyItem(const QByteArray &header) override; 16 | private: 17 | QString getTextureName(int index); 18 | }; 19 | 20 | #endif // NOSCCINFOPENER_H 21 | -------------------------------------------------------------------------------- /Source/Openers/NosTextOpener.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "NosTextOpener.h" 3 | 4 | NosTextOpener::NosTextOpener() { 5 | datDecryptor = new NosTextDatFileDecryptor(); 6 | lstDecryptor = new NosTextOthersFileDecryptor(&littleEndianConverter); 7 | } 8 | 9 | OnexTreeItem *NosTextOpener::decrypt(QFile &file) { 10 | file.seek(0); 11 | auto *item = new OnexTreeText(neatFileName(file.fileName()), this); 12 | int fileAmount = littleEndianConverter.readInt(file); 13 | for (int i = 0; i < fileAmount; ++i) { 14 | int fileNumber = littleEndianConverter.readInt(file); 15 | int stringNameSize = littleEndianConverter.readInt(file); 16 | QString stringName = file.read(stringNameSize); 17 | int isDat = littleEndianConverter.readInt(file); 18 | int fileSize = littleEndianConverter.readInt(file); 19 | QByteArray fileContent = file.read(fileSize); 20 | QByteArray decryptedArray; 21 | if (isDat || stringName.endsWith(".dat")) 22 | decryptedArray = datDecryptor->decrypt(fileContent); 23 | else //.lst 24 | decryptedArray = lstDecryptor->decrypt(fileContent); 25 | item->addChild(new OnexTreeText(stringName, this, fileNumber, isDat, decryptedArray)); 26 | } 27 | 28 | if (file.bytesAvailable() == 12) { 29 | file.seek(file.pos() + 8); 30 | if (file.read(4) == QByteArrayLiteral("\xEE\x3E\x32\x01")) { 31 | file.seek(file.pos() - 12); 32 | QString time = readOLETIME(file.read(8)); 33 | item->setTime(time, true); 34 | } 35 | } 36 | 37 | return item; 38 | } 39 | 40 | QByteArray NosTextOpener::encrypt(OnexTreeItem *item) { 41 | if (item->hasParent()) 42 | return QByteArray(); 43 | QByteArray result; 44 | result.push_back(littleEndianConverter.toInt(item->childCount())); 45 | for (int i = 0; i < item->childCount(); ++i) { 46 | auto *currentItem = dynamic_cast(item->child(i)); 47 | result.push_back(littleEndianConverter.toInt(currentItem->getFileNumber())); 48 | result.push_back(littleEndianConverter.toInt(currentItem->getName().size())); 49 | result.push_back(currentItem->getName().toLocal8Bit()); 50 | result.push_back(littleEndianConverter.toInt(currentItem->getIsCompressed())); 51 | QList splited; 52 | QByteArray encrypted; 53 | if (currentItem->getIsCompressed() || currentItem->getName().endsWith(".dat")) { 54 | splited = currentItem->getContent().split(0xD); 55 | if (splited[splited.size() - 1].isEmpty()) 56 | splited = splited.mid(0, splited.size() - 1); 57 | } else { 58 | splited = currentItem->getContent().split(0xA); 59 | if (splited[splited.size() - 1].isEmpty()) 60 | splited = splited.mid(0, splited.size() - 1); 61 | encrypted.push_back(littleEndianConverter.toInt(splited.size())); 62 | } 63 | for (auto &line : splited) { 64 | if (currentItem->getIsCompressed() || currentItem->getName().endsWith(".dat")) 65 | encrypted.push_back(datDecryptor->encrypt(line)); 66 | else 67 | encrypted.push_back(lstDecryptor->encrypt(line)); 68 | } 69 | result.push_back(littleEndianConverter.toInt(encrypted.size())); 70 | result.push_back(encrypted); 71 | } 72 | 73 | QByteArray time = generateOLETIME(); 74 | result.push_back(time); 75 | result.push_back(QByteArrayLiteral("\xEE\x3E\x32\x01")); 76 | 77 | auto *text = static_cast(item); 78 | text->setTime(readOLETIME(time), true); 79 | 80 | return result; 81 | } 82 | 83 | OnexTreeItem *NosTextOpener::getEmptyItem(const QByteArray &header) { 84 | return new OnexTreeText("", this); 85 | } 86 | 87 | QByteArray NosTextOpener::generateOLETIME() { 88 | QDateTime dt = QDateTime::currentDateTime(); 89 | dt.setTimeSpec(Qt::TimeSpec::UTC); 90 | double variant = ((dt.toSecsSinceEpoch() + 2208988800) / 86400.0) + 2.00001; 91 | QByteArray writeArray; 92 | writeArray.resize(8); 93 | qToLittleEndian(variant, reinterpret_cast(writeArray.data())); 94 | return writeArray; 95 | } 96 | 97 | QString NosTextOpener::readOLETIME(QByteArray array) { 98 | double variant = qFromLittleEndian(reinterpret_cast(array.data())); 99 | unsigned long unixTime = -2208988800 + ((variant - 2.00001) * 86400); 100 | QDateTime dt; 101 | dt.setTimeSpec(Qt::TimeSpec::UTC); 102 | dt.setSecsSinceEpoch(unixTime); 103 | return dt.toString("dd/MM/yyyy hh:mm:ss"); 104 | } -------------------------------------------------------------------------------- /Source/Openers/NosTextOpener.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTEXTOPENER_H 2 | #define NOSTEXTOPENER_H 3 | 4 | #include "../Decryptors/NosTextDatFileDecryptor.h" 5 | #include "../Decryptors/NosTextOthersFileDecryptor.h" 6 | #include "../Ui/TreeItems/OnexTreeText.h" 7 | #include "INosFileOpener.h" 8 | #include 9 | #include 10 | 11 | class NosTextOpener : public QObject, public INosFileOpener { 12 | Q_OBJECT 13 | public: 14 | NosTextOpener(); 15 | OnexTreeItem *decrypt(QFile &file); 16 | QByteArray encrypt(OnexTreeItem *item); 17 | OnexTreeItem *getEmptyItem(const QByteArray &header) override; 18 | private: 19 | NosTextDatFileDecryptor *datDecryptor; 20 | NosTextOthersFileDecryptor *lstDecryptor; 21 | QByteArray generateOLETIME(); 22 | QString readOLETIME(QByteArray array); 23 | }; 24 | 25 | #endif // NOSTEXTOPENER_H 26 | -------------------------------------------------------------------------------- /Source/Openers/NosZlibOpener.cpp: -------------------------------------------------------------------------------- 1 | #include "NosZlibOpener.h" 2 | #include "../Ui/TreeItems/OnexNS4BbData.h" 3 | #include "../Ui/TreeItems/OnexNSipData.h" 4 | #include "../Ui/TreeItems/OnexNSmpData.h" 5 | #include "../Ui/TreeItems/OnexNStcData.h" 6 | #include "../Ui/TreeItems/OnexNStpData.h" 7 | #include "../Ui/TreeItems/OnexNStgData.h" 8 | #include "../Ui/TreeItems/OnexNSmcData.h" 9 | 10 | NosZlibOpener::NosZlibOpener() = default; 11 | 12 | OnexTreeItem *NosZlibOpener::decrypt(QFile &file) { 13 | file.seek(0); 14 | QByteArray header = file.read(NOS_HEADER_SIZE); 15 | int fileAmount = littleEndianConverter.readInt(file); 16 | OnexTreeItem *item = createItemFromHeader(header, neatFileName(file.fileName()), header); 17 | QByteArray separatorByte = file.read(1); 18 | for (int i = 0; i != fileAmount; ++i) { 19 | int id = littleEndianConverter.readInt(file); 20 | int offset = littleEndianConverter.readInt(file); 21 | int previousOffset = file.pos(); 22 | file.seek(offset); 23 | int creationDate = littleEndianConverter.readInt(file); 24 | int dataSize = littleEndianConverter.readInt(file); 25 | int compressedDataSize = littleEndianConverter.readInt(file); 26 | bool isCompressed = file.read(1).at(0); 27 | QByteArray data = file.read(compressedDataSize); 28 | if (isCompressed) { 29 | QByteArray bigEndian = toBigEndian(dataSize); 30 | data.push_front(bigEndian); 31 | data = decryptor.decrypt(data); 32 | } 33 | 34 | QString name = QString::number(id); 35 | if (containsName(item, name)) { 36 | int a = 2; 37 | while (containsName(item, name + "_" + QString::number(a))) 38 | a++; 39 | name = name + "_" + QString::number(a); 40 | } 41 | 42 | item->addChild(createItemFromHeader(header, name, data, id, creationDate, isCompressed)); 43 | file.seek(previousOffset); 44 | } 45 | return item; 46 | } 47 | 48 | QByteArray NosZlibOpener::encrypt(OnexTreeItem *item) { 49 | if (item->hasParent()) 50 | return QByteArray(); 51 | QByteArray fileHeader = item->getContent(); 52 | fileHeader.push_back(littleEndianConverter.toInt(item->childCount())); 53 | fileHeader.push_back((char) 0x0); // separator byte 54 | 55 | QByteArray offsetArray; 56 | int sizeOfOffsetArray = item->childCount() * 8; 57 | QByteArray contentArray; 58 | int firstFileOffset = fileHeader.size() + sizeOfOffsetArray; 59 | for (int i = 0; i != item->childCount(); ++i) { 60 | int currentFileOffset = firstFileOffset + contentArray.size(); 61 | auto *currentItem = dynamic_cast(item->child(i)); 62 | contentArray.push_back(littleEndianConverter.toInt(currentItem->getCreationDate())); 63 | QByteArray content = currentItem->getContent(); 64 | contentArray.push_back(littleEndianConverter.toInt(content.size())); 65 | if (currentItem->isCompressed()) { 66 | int headerNumber = getNTHeaderNumber(fileHeader); 67 | if (headerNumber == NS4BbData || headerNumber == NStcData || headerNumber == NStuData) 68 | content = decryptor.encrypt(content, 9); 69 | else 70 | content = decryptor.encrypt(content); 71 | } 72 | int compressedContentSize = content.size(); 73 | if (currentItem->isCompressed()) 74 | compressedContentSize -= 4; // qCompress add the size at the front of array 75 | 76 | contentArray.push_back(littleEndianConverter.toInt(compressedContentSize)); 77 | contentArray.push_back(currentItem->isCompressed()); 78 | if (currentItem->isCompressed()) 79 | contentArray.push_back(content.mid(4)); 80 | else 81 | contentArray.push_back(content); 82 | offsetArray.push_back(littleEndianConverter.toInt(currentItem->getId())); 83 | offsetArray.push_back(littleEndianConverter.toInt(currentFileOffset)); 84 | } 85 | QByteArray result; 86 | result.push_back(fileHeader); 87 | result.push_back(offsetArray); 88 | result.push_back(contentArray); 89 | return result; 90 | } 91 | 92 | OnexTreeItem *NosZlibOpener::getEmptyItem(const QByteArray &header) { 93 | return createItemFromHeader(header, "", QByteArray()); 94 | } 95 | 96 | OnexTreeItem *NosZlibOpener::createItemFromHeader(QByteArray header, const QString &name, const QByteArray &array, int fileId, 97 | int creationDate, bool compressed) { 98 | int headerNumber = getNTHeaderNumber(header); 99 | switch (headerNumber) { 100 | case NSipData: 101 | return new OnexNSipData(name, array, this, fileId, creationDate, compressed); 102 | case NS4BbData: 103 | return new OnexNS4BbData(name, array, this, fileId, creationDate, compressed); 104 | case NStcData: 105 | return new OnexNStcData(name, array, this, fileId, creationDate, compressed); 106 | case NStpData: 107 | case NStpeData: 108 | case NStpuData: 109 | return new OnexNStpData(name, array, this, fileId, creationDate, compressed); 110 | case NSmpData: 111 | case NSppData: 112 | return new OnexNSmpData(name, array, this, fileId, creationDate, compressed); 113 | case NStgData: 114 | case NStgeData: 115 | return new OnexNStgData(name, array, this, fileId, creationDate, compressed); 116 | case NSmcData: 117 | case NSpcData: 118 | return static_cast(new OnexNSmcData(name, array, this, fileId, creationDate, compressed)); 119 | default: 120 | return new OnexTreeZlibItem(name, this, array, fileId, creationDate, compressed); 121 | } 122 | } 123 | 124 | int NosZlibOpener::getNTHeaderNumber(QByteArray &array) { 125 | if (array.mid(0, 7) == "NT Data") 126 | return array.mid(8, 2).toInt(); 127 | else if (array.mid(0, 10) == "32GBS V1.0") 128 | return NS4BbData; 129 | else if (array.mid(0, 10) == "ITEMS V1.0") 130 | return NSipData2006; 131 | else 132 | return 199; 133 | } 134 | 135 | QByteArray NosZlibOpener::toBigEndian(qint32 value) { 136 | QByteArray result; 137 | result.resize(4); 138 | qToBigEndian(value, reinterpret_cast(result.data())); 139 | return result; 140 | } 141 | 142 | bool NosZlibOpener::containsName(OnexTreeItem *item, QString searched) { 143 | for (int c = 0; c < item->childCount(); c++) { 144 | if (item->child(c)->text(0) == searched) 145 | return true; 146 | } 147 | return false; 148 | } 149 | -------------------------------------------------------------------------------- /Source/Openers/NosZlibOpener.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSZLIBOPENER_H 2 | #define NOSZLIBOPENER_H 3 | 4 | #include "../Decryptors/NosZlibDecryptor.h" 5 | #include "../Converters/ImageConverter.h" 6 | #include "../NosEnumTypes.h" 7 | #include "INosFileOpener.h" 8 | #include 9 | 10 | class NosZlibOpener : public INosFileOpener { 11 | public: 12 | NosZlibOpener(); 13 | OnexTreeItem *decrypt(QFile &file) override; 14 | QByteArray encrypt(OnexTreeItem *item) override; 15 | OnexTreeItem *getEmptyItem(const QByteArray &header) override; 16 | private: 17 | static const int NOS_HEADER_SIZE = 0x10; 18 | NosZlibDecryptor decryptor; 19 | OnexTreeItem *createItemFromHeader(QByteArray header, const QString &name, const QByteArray &array, int fileId = -1, 20 | int creationDate = 0, bool compressed = false); 21 | int getNTHeaderNumber(QByteArray &array); 22 | QByteArray toBigEndian(qint32 value); 23 | bool containsName(OnexTreeItem *item, QString searched); 24 | }; 25 | 26 | #endif // NOSZLIBOPENER_H 27 | -------------------------------------------------------------------------------- /Source/Ui/FileInfo.cpp: -------------------------------------------------------------------------------- 1 | #include "FileInfo.h" 2 | 3 | FileInfo::FileInfo(QWidget *parent) : QWidget(parent) { 4 | grid = new QGridLayout(); 5 | setLayout(grid); 6 | setMinimumWidth(225); 7 | setMaximumWidth(225); 8 | rows = 0; 9 | } 10 | 11 | QLineEdit *FileInfo::addStringLineEdit(const QString &title, const QString &value) { 12 | auto *input = addLineEdit(title, value); 13 | input->setProperty("type", "QString"); 14 | return input; 15 | } 16 | 17 | QLineEdit *FileInfo::addIntLineEdit(const QString &title, int value) { 18 | auto *input = addLineEdit(title, QString::number(value)); 19 | input->setProperty("type", "Integer"); 20 | return input; 21 | } 22 | 23 | QLineEdit *FileInfo::addFloatLineEdit(const QString &title, float value) { 24 | auto *input = addLineEdit(title, QString::number(value)); 25 | input->setProperty("type", "Float"); 26 | return input; 27 | } 28 | 29 | QCheckBox *FileInfo::addCheckBox(const QString &title, bool value) { 30 | auto *cb = new QCheckBox(title); 31 | cb->setChecked(value); 32 | grid->addWidget(cb, rows, 0, 1, 2); 33 | rows++; 34 | return cb; 35 | } 36 | 37 | QComboBox *FileInfo::addSelection(const QString &title, const QStringList &items, int value) { 38 | auto *label = new QLabel(title); 39 | label->setMinimumWidth(50); 40 | grid->addWidget(label, rows, 0); 41 | auto *box = new QComboBox(); 42 | for (const QString &s : items) { 43 | box->addItem(s); 44 | } 45 | box->setCurrentIndex(value); 46 | grid->addWidget(box, rows, 1); 47 | rows++; 48 | return box; 49 | } 50 | 51 | QPushButton *FileInfo::addReplaceButton(const QString &title) { 52 | auto *button = new QPushButton(title); 53 | connect(button, &QPushButton::clicked, [=]() { emit replaceFile(); }); 54 | grid->addWidget(button, rows, 0, 1, 2); 55 | rows++; 56 | return button; 57 | } 58 | 59 | QPushButton *FileInfo::addReplaceRawButton(const QString &title) { 60 | auto *button = new QPushButton(title); 61 | connect(button, &QPushButton::clicked, [=]() { emit replaceRawFile(); }); 62 | grid->addWidget(button, rows, 0, 1, 2); 63 | rows++; 64 | return button; 65 | } 66 | 67 | void FileInfo::update(const QString &title, const QString &value) { 68 | for (int i = 0; i < grid->rowCount(); i++) { 69 | if (grid->itemAtPosition(i, 0) == nullptr || dynamic_cast(grid->itemAtPosition(i, 0)->widget()) == nullptr) 70 | continue; 71 | if (dynamic_cast(grid->itemAtPosition(i, 0)->widget())->text() == title) { 72 | if (grid->itemAtPosition(i, 1) != nullptr) { 73 | auto *in = dynamic_cast(grid->itemAtPosition(i, 1)->widget()); 74 | if (in != nullptr) { 75 | in->setText(value); 76 | break; 77 | } else { 78 | auto *box = dynamic_cast(grid->itemAtPosition(i, 1)->widget()); 79 | if (box != nullptr) { 80 | box->setCurrentIndex(value.toInt()); 81 | break; 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | void FileInfo::update(const QString &title, int value) { 90 | update(title, QString::number(value)); 91 | } 92 | 93 | void FileInfo::update(const QString &title, float value) { 94 | update(title, QString::number(value)); 95 | } 96 | 97 | void FileInfo::update(const QString &title, bool value) { 98 | for (int i = 0; i < grid->rowCount(); i++) { 99 | if (grid->itemAtPosition(i, 0) == nullptr) 100 | continue; 101 | auto *current = dynamic_cast(grid->itemAtPosition(i, 0)->widget()); 102 | if (current != nullptr && current->text() == title) { 103 | auto *cb = dynamic_cast(grid->itemAtPosition(i, 0)->widget()); 104 | cb->setChecked(value); 105 | break; 106 | } 107 | } 108 | } 109 | 110 | void FileInfo::replace(FileInfo *info) { 111 | emit updateInfo(info); 112 | } 113 | 114 | QJsonObject FileInfo::toJson() { 115 | QJsonObject jo; 116 | 117 | for (int i = 0; i < grid->rowCount(); i++) { 118 | if (grid->itemAtPosition(i, 0) == nullptr) 119 | continue; 120 | if (dynamic_cast(grid->itemAtPosition(i, 0)->widget()) != nullptr) { 121 | if (grid->itemAtPosition(i, 1) == nullptr) 122 | continue; 123 | QString key = dynamic_cast(grid->itemAtPosition(i, 0)->widget())->text(); 124 | if (key == "Width" || key == "Height") 125 | continue; 126 | if (dynamic_cast(grid->itemAtPosition(i, 1)->widget()) != nullptr) { 127 | auto *lineEdit = dynamic_cast(grid->itemAtPosition(i, 1)->widget()); 128 | if (key == "Header") 129 | jo[key] = QString(lineEdit->text().toLocal8Bit().toHex()); 130 | else if (lineEdit->property("type") == "QString") 131 | jo[key] = lineEdit->text(); 132 | else if (lineEdit->property("type") == "Integer") 133 | jo[key] = lineEdit->text().toInt(); 134 | else if (lineEdit->property("type") == "Float") 135 | jo[key] = lineEdit->text().toFloat(); 136 | } else if (dynamic_cast(grid->itemAtPosition(i, 1)->widget()) != nullptr) { 137 | auto *box = dynamic_cast(grid->itemAtPosition(i, 1)->widget()); 138 | int value = box->currentIndex(); 139 | jo[key] = value; 140 | } 141 | } else if (dynamic_cast(grid->itemAtPosition(i, 0)->widget()) != nullptr) { 142 | auto *box = dynamic_cast(grid->itemAtPosition(i, 0)->widget()); 143 | QString key = box->text(); 144 | bool value = box->isChecked(); 145 | jo[key] = value; 146 | } 147 | } 148 | return jo; 149 | } 150 | 151 | QLineEdit *FileInfo::addLineEdit(const QString &title, const QString &value) { 152 | auto *label = new QLabel(title); 153 | label->setMinimumWidth(50); 154 | grid->addWidget(label, rows, 0); 155 | auto *input = new QLineEdit(value); 156 | input->setMaximumWidth(120); 157 | grid->addWidget(input, rows, 1); 158 | rows++; 159 | return input; 160 | } 161 | -------------------------------------------------------------------------------- /Source/Ui/FileInfo.h: -------------------------------------------------------------------------------- 1 | #ifndef FILEINFO_H 2 | #define FILEINFO_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class FileInfo : public QWidget { 15 | Q_OBJECT 16 | public: 17 | QGridLayout *grid; 18 | explicit FileInfo(QWidget *parent = nullptr); 19 | QLineEdit *addStringLineEdit(const QString &title, const QString &value); 20 | QLineEdit *addIntLineEdit(const QString &title, int value); 21 | QLineEdit *addFloatLineEdit(const QString &title, float value); 22 | QCheckBox *addCheckBox(const QString &title, bool checked); 23 | QComboBox *addSelection(const QString &title, const QStringList &items, int value); 24 | QPushButton *addReplaceButton(const QString &title); 25 | QPushButton *addReplaceRawButton(const QString &title); 26 | QJsonObject toJson(); 27 | public slots: 28 | void update(const QString &title, const QString &value); 29 | void update(const QString &title, int value); 30 | void update(const QString &title, float value); 31 | void update(const QString &title, bool value); 32 | void replace(FileInfo *newInfo); 33 | signals: 34 | void updateInfo(FileInfo *info); 35 | void replaceFile(); 36 | void replaceRawFile(); 37 | private: 38 | int rows; 39 | QLineEdit *addLineEdit(const QString &title, const QString &value); 40 | }; 41 | 42 | #endif // FILEINFO_H 43 | -------------------------------------------------------------------------------- /Source/Ui/OnexTreeItem.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexTreeItem.h" 2 | 3 | OnexTreeItem::OnexTreeItem(const QString &name, INosFileOpener *opener, QByteArray content) 4 | : name(name), opener(opener), content(content) { 5 | this->setText(0, name); 6 | this->setFlags(this->flags() | Qt::ItemIsEditable); 7 | } 8 | 9 | OnexTreeItem::~OnexTreeItem() = default; 10 | 11 | QWidget *OnexTreeItem::getPreview() { 12 | return nullptr; 13 | } 14 | 15 | FileInfo *OnexTreeItem::getInfos() { 16 | FileInfo *infos = generateInfos(); 17 | if (infos != nullptr) 18 | connect(this, SIGNAL(replaceInfo(FileInfo * )), infos, SLOT(replace(FileInfo * ))); 19 | return infos; 20 | } 21 | 22 | bool OnexTreeItem::hasParent() { 23 | return QTreeWidgetItem::parent() != nullptr; 24 | } 25 | 26 | QString OnexTreeItem::getName() { 27 | return name; 28 | } 29 | 30 | QByteArray OnexTreeItem::getContent() { 31 | return content; 32 | } 33 | 34 | int OnexTreeItem::getContentSize() { 35 | return content.size(); 36 | } 37 | 38 | bool OnexTreeItem::isEmpty() { 39 | return getContent().isEmpty(); 40 | } 41 | 42 | QString OnexTreeItem::getExportExtension() { 43 | return ""; 44 | } 45 | 46 | QString OnexTreeItem::getExportExtensionFilter() { 47 | if (getExportExtension() == ".png") 48 | return "PNG Image (*.png)"; 49 | else if (getExportExtension() == ".txt") 50 | return "Text File (*.txt)"; 51 | else if (getExportExtension() == ".lst") 52 | return "List File (*.lst)"; 53 | else if (getExportExtension() == ".dat") 54 | return "DAT File (*.dat)"; 55 | else if (getExportExtension() == ".obj") 56 | return "OBJ File (*.obj)"; 57 | else if (getExportExtension() == ".json") 58 | return "JSON File (*.json)"; 59 | return "All files (*.*)"; 60 | } 61 | 62 | void OnexTreeItem::setName(QString name) { 63 | this->name = name; 64 | setText(0, name); 65 | } 66 | 67 | void OnexTreeItem::setContent(QByteArray content) { 68 | this->content = content; 69 | } 70 | 71 | int OnexTreeItem::onExport(QString directory) { 72 | if (childCount() > 0) { 73 | int count = 0; 74 | for (int i = 0; i != this->childCount(); ++i) { 75 | auto *item = dynamic_cast(this->child(i)); 76 | count += item->onExport(directory); 77 | } 78 | return count; 79 | } else { 80 | QString path = getCorrectPath(directory); 81 | return saveAsFile(path); 82 | } 83 | } 84 | 85 | int OnexTreeItem::onExportRaw(QString directory) { 86 | QString path = getCorrectPath(directory, ".bin"); 87 | QFile file(path); 88 | file.open(QIODevice::WriteOnly); 89 | if (file.write(this->getContent()) == -1) { 90 | return 0; 91 | } 92 | file.close(); 93 | return 1; 94 | } 95 | 96 | int OnexTreeItem::onExportAsOriginal(QString path) { 97 | if (path.isEmpty()) 98 | return 0; 99 | if (!path.endsWith(".NOS")) 100 | path += ".NOS"; 101 | QFile file(path); 102 | if (!file.open(QIODevice::WriteOnly)) { 103 | QMessageBox::critical(nullptr, "Woops", "Couldn't open this file for writing"); 104 | return 0; 105 | } 106 | if (file.write(opener->encrypt(this)) == -1) { 107 | QMessageBox::critical(nullptr, "Woops", "Couldn't write to this file"); 108 | return 0; 109 | } 110 | file.close(); 111 | QMessageBox::information(nullptr, "Yeah", "File exported into .NOS"); 112 | return 1; 113 | } 114 | 115 | int OnexTreeItem::onReplace(QString directory) { 116 | int count = 0; 117 | if (this->childCount() > 0) { 118 | for (int i = 0; i < this->childCount(); i++) { 119 | auto *item = dynamic_cast(this->child(i)); 120 | count += item->onReplace(directory); 121 | } 122 | if (hasParent()) 123 | afterReplace(QByteArray()); 124 | } 125 | if (count > 0) 126 | return count; 127 | QString path = getCorrectPath(directory); 128 | QFile file(path); 129 | if (file.open(QIODevice::ReadOnly)) 130 | return afterReplace(file.readAll()); 131 | else { 132 | QMessageBox::critical(nullptr, "Woops", "Couldn't open " + path); 133 | return 0; 134 | } 135 | } 136 | 137 | int OnexTreeItem::onReplaceRaw(QString directory) { 138 | QString path = getCorrectPath(directory, ".bin"); 139 | QFile file(path); 140 | if (file.open(QIODevice::ReadOnly)) { 141 | content = file.readAll(); 142 | file.close(); 143 | return 1; 144 | } 145 | return 0; 146 | } 147 | 148 | int OnexTreeItem::afterReplace(QByteArray content) { 149 | emit replaceInfo(generateInfos()); 150 | return 1; 151 | } 152 | 153 | QJsonObject OnexTreeItem::generateConfig() { 154 | QJsonObject jo = generateInfos()->toJson(); 155 | 156 | if (childCount() > 0) { 157 | QJsonArray contentArray; 158 | for (int i = 0; i < childCount(); i++) { 159 | auto item = static_cast(child(i)); 160 | contentArray.append(item->generateConfig()); 161 | } 162 | jo["content"] = contentArray; 163 | } else { 164 | QString path = this->getName(); 165 | if (!path.endsWith(getExportExtension())) 166 | path += getExportExtension(); 167 | jo["path"] = path; 168 | } 169 | return jo; 170 | } 171 | 172 | QString OnexTreeItem::getCorrectPath(QString input, QString extension) { 173 | if (extension.isEmpty()) 174 | extension = getExportExtension(); 175 | 176 | if (!input.endsWith(extension)) { 177 | input = input + this->getName(); 178 | if (!input.endsWith(extension)) 179 | input += extension; 180 | } 181 | return input; 182 | } 183 | 184 | FileInfo *OnexTreeItem::generateInfos() { 185 | FileInfo *infos = new FileInfo(); 186 | 187 | if (hasParent() && isEmpty()) { 188 | infos->addReplaceButton("Load from File"); 189 | infos->addReplaceRawButton("Load from raw File"); 190 | } else if (hasParent()) { 191 | infos->addReplaceButton("Replace"); 192 | infos->addReplaceRawButton("Replace with raw"); 193 | } 194 | if (!hasParent()) { 195 | infos->addStringLineEdit("Archive", text(0))->setEnabled(false); 196 | } 197 | 198 | connect(this, SIGNAL(changeSignal(QString, QString)), infos, SLOT(update(QString, QString))); 199 | connect(this, SIGNAL(changeSignal(QString, int)), infos, SLOT(update(QString, int))); 200 | connect(this, SIGNAL(changeSignal(QString, float)), infos, SLOT(update(QString, float))); 201 | connect(this, SIGNAL(changeSignal(QString, bool)), infos, SLOT(update(QString, bool))); 202 | return infos; 203 | } 204 | 205 | int OnexTreeItem::saveAsFile(const QString &path, QByteArray content) { 206 | QFile file(path); 207 | if (file.open(QIODevice::WriteOnly)) { 208 | file.write(content.isEmpty() ? getContent() : content); 209 | file.close(); 210 | return 1; 211 | } 212 | return 0; 213 | } 214 | 215 | bool OnexTreeItem::operator<(const QTreeWidgetItem &other) const { 216 | int column = treeWidget()->sortColumn(); 217 | static QRegExp regExp("^(\\d*)_(\\d*)x(\\d*)$"); 218 | 219 | bool t1IsInt; 220 | bool t2IsInt; 221 | int t1 = text(column).toInt(&t1IsInt); 222 | int t2 = other.text(column).toInt(&t2IsInt); 223 | if (t1IsInt && t2IsInt || regExp.exactMatch(text(column))) 224 | return t1 < t2; 225 | else 226 | return false; 227 | // return text(column).toLower() < other.text(column).toLower(); 228 | } 229 | -------------------------------------------------------------------------------- /Source/Ui/OnexTreeItem.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXTREEITEM_H 2 | #define ONEXTREEITEM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "../Openers/INosFileOpener.h" 11 | #include "FileInfo.h" 12 | #include "../Converters/LittleEndianConverter.h" 13 | 14 | class OnexTreeItem : public QObject, public QTreeWidgetItem { 15 | Q_OBJECT 16 | public: 17 | OnexTreeItem(const QString &name, INosFileOpener *opener, QByteArray content = QByteArray()); 18 | ~OnexTreeItem() override; 19 | virtual QWidget *getPreview(); 20 | virtual FileInfo *getInfos(); 21 | bool hasParent(); 22 | virtual QString getName(); 23 | virtual QByteArray getContent(); 24 | int getContentSize(); 25 | virtual bool isEmpty(); 26 | virtual QString getExportExtension(); 27 | QString getExportExtensionFilter(); 28 | public slots: 29 | virtual void setName(QString name); 30 | virtual int onExportAsOriginal(QString path); 31 | virtual void setContent(QByteArray content); 32 | virtual int onExport(QString directory); 33 | virtual int onExportRaw(QString directory); 34 | virtual int onReplace(QString directory); 35 | virtual int onReplaceRaw(QString directory); 36 | virtual int afterReplace(QByteArray content); 37 | virtual QJsonObject generateConfig(); 38 | signals: 39 | void changeSignal(QString title, QString value); 40 | void changeSignal(QString title, int value); 41 | void changeSignal(QString title, float value); 42 | void changeSignal(QString title, bool value); 43 | void replaceInfo(FileInfo *info); 44 | protected: 45 | QByteArray content; 46 | QString name; 47 | INosFileOpener *opener; 48 | virtual FileInfo *generateInfos(); 49 | virtual int saveAsFile(const QString &path, QByteArray content = QByteArray()); 50 | QString getCorrectPath(QString input, QString extension = QString()); 51 | bool operator<(const QTreeWidgetItem &other) const override; 52 | }; 53 | 54 | #endif // ONEXTREEITEM_H 55 | -------------------------------------------------------------------------------- /Source/Ui/Previews/MultiImagePreview.cpp: -------------------------------------------------------------------------------- 1 | #include "MultiImagePreview.h" 2 | #include "ui_MultiImagePreview.h" 3 | #include 4 | 5 | MultiImagePreview::MultiImagePreview(QList *images, QWidget *parent) 6 | : QWidget(parent), ui(new Ui::MultiImagePreview) { 7 | ui->setupUi(this); 8 | this->images = images; 9 | QImage image = generateImage(); 10 | ui->imgContent->setPixmap(QPixmap::fromImage(image)); 11 | } 12 | 13 | MultiImagePreview::~MultiImagePreview() { 14 | delete ui; 15 | } 16 | 17 | void MultiImagePreview::onReplaced(QList *newImages) { 18 | images = newImages; 19 | ui->imgContent->setPixmap(QPixmap::fromImage(generateImage())); 20 | } 21 | 22 | QImage MultiImagePreview::generateImage() { 23 | QPair res = this->getResolution(); 24 | QImage image = QImage(res.first, res.second, QImage::Format_ARGB32); 25 | image.fill(Qt::transparent); 26 | int offset = 0; 27 | for (auto img : *this->images) { 28 | QPainter painter(&image); 29 | painter.drawImage(QRect(0, offset, img.width(), img.height()), img, 30 | QRect(0, 0, img.width(), img.height())); 31 | offset += img.height() + 20; 32 | } 33 | return image; 34 | } 35 | 36 | QPair MultiImagePreview::getResolution() { 37 | QPair r; 38 | for (auto img : *this->images) { 39 | if (img.width() > r.first) 40 | r.first = img.width(); 41 | r.second += img.height() + 20; 42 | } 43 | return r; 44 | } -------------------------------------------------------------------------------- /Source/Ui/Previews/MultiImagePreview.h: -------------------------------------------------------------------------------- 1 | #ifndef MULTIIMAGEPREVIEW_H 2 | #define MULTIIMAGEPREVIEW_H 3 | 4 | #include "../../Converters/ImageConverter.h" 5 | #include "../../NosEnumTypes.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace Ui { 15 | class MultiImagePreview; 16 | } 17 | class MultiImagePreview : public QWidget { 18 | Q_OBJECT 19 | public: 20 | explicit MultiImagePreview(QList *image, QWidget *parent = nullptr); 21 | ~MultiImagePreview() override; 22 | private slots: 23 | void onReplaced(QList *newImages); 24 | private: 25 | Ui::MultiImagePreview *ui; 26 | QList *images; 27 | QImage generateImage(); 28 | QPair getResolution(); 29 | }; 30 | 31 | #endif // MULTIIMAGEPREVIEW_H -------------------------------------------------------------------------------- /Source/Ui/Previews/MultiImagePreview.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MultiImagePreview 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Image 15 | 16 | 17 | 18 | 19 | 20 | TextLabel 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Source/Ui/Previews/SingleImagePreview.cpp: -------------------------------------------------------------------------------- 1 | #include "SingleImagePreview.h" 2 | #include "ui_SingleImagePreview.h" 3 | 4 | SingleImagePreview::SingleImagePreview(const QImage &image, QWidget *parent) 5 | : QWidget(parent), ui(new Ui::SingleImagePreview) { 6 | ui->setupUi(this); 7 | ui->imgContent->setPixmap(QPixmap::fromImage(image)); 8 | } 9 | 10 | SingleImagePreview::~SingleImagePreview() { 11 | delete ui; 12 | } 13 | 14 | void SingleImagePreview::onReplaced(const QImage &newImage) { 15 | ui->imgContent->setPixmap(QPixmap::fromImage(newImage)); 16 | } 17 | -------------------------------------------------------------------------------- /Source/Ui/Previews/SingleImagePreview.h: -------------------------------------------------------------------------------- 1 | #ifndef SINGLEIMAGEPREVIEW_H 2 | #define SINGLEIMAGEPREVIEW_H 3 | 4 | #include "../../Converters/ImageConverter.h" 5 | #include "../../NosEnumTypes.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace Ui { 15 | class SingleImagePreview; 16 | } 17 | class SingleImagePreview : public QWidget { 18 | Q_OBJECT 19 | public: 20 | explicit SingleImagePreview(const QImage &image, QWidget *parent = nullptr); 21 | ~SingleImagePreview() override; 22 | private slots: 23 | void onReplaced(const QImage &newImage); 24 | private: 25 | Ui::SingleImagePreview *ui; 26 | }; 27 | 28 | #endif // SINGLEIMAGEPREVIEW_H 29 | -------------------------------------------------------------------------------- /Source/Ui/Previews/SingleImagePreview.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SingleImagePreview 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Image 15 | 16 | 17 | 18 | 19 | 20 | TextLabel 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Source/Ui/Previews/SingleModelPreview.cpp: -------------------------------------------------------------------------------- 1 | #include "SingleModelPreview.h" 2 | #include 3 | #include 4 | #include 5 | 6 | SingleModelPreview::SingleModelPreview(Model *model, QWidget *parent) : QOpenGLWidget(parent) { 7 | this->model = model; 8 | camera.X = 0; 9 | camera.Y = 0; 10 | camera.Z = -20; 11 | camera.angleX = 20; 12 | camera.angleY = 0; 13 | mouse.X = 0; 14 | mouse.Y = 0; 15 | setMinimumHeight(600); 16 | setMaximumHeight(600); 17 | setMinimumWidth(650); 18 | setMaximumWidth(650); 19 | } 20 | 21 | SingleModelPreview::~SingleModelPreview() = default; 22 | 23 | void SingleModelPreview::onReplaced(Model *model) { 24 | this->model = model; 25 | repaint(); 26 | } 27 | 28 | void SingleModelPreview::initializeGL() { 29 | glClearColor(1, 1, 1, 1); 30 | glMatrixMode(GL_PROJECTION); 31 | glLoadIdentity(); 32 | glViewport(0, 0, width(), height()); 33 | gluPerspective(65, (float) width() / (float) height(), 0.1, 300); 34 | glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); 35 | // glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 36 | 37 | glEnable(GL_LIGHTING); 38 | } 39 | 40 | void SingleModelPreview::paintGL() { 41 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 42 | glMatrixMode(GL_MODELVIEW); 43 | glLoadIdentity(); 44 | glPushMatrix(); 45 | // Camera 46 | glTranslatef(camera.X, camera.Y, camera.Z); 47 | glRotatef(camera.angleY, 0, 1, 0); 48 | glRotatef(camera.angleX, 1, 0, 0); 49 | 50 | // glEnable(GL_LIGHT0); 51 | // glEnable(GL_COLOR_MATERIAL); 52 | // glColor3f(0.5f, 0.5f, 0.5f); 53 | 54 | for (auto &object : model->objects) { 55 | glPushMatrix(); 56 | QQuaternion qr(object.rotation); 57 | float xAxis, yAxis, zAxis, angle; 58 | qr.getAxisAndAngle(&xAxis, &yAxis, &zAxis, &angle); 59 | glTranslatef(object.position.x(), object.position.y(), object.position.z()); 60 | glRotatef(angle, xAxis, yAxis, zAxis); 61 | glScalef(object.scale.x(), object.scale.y(), object.scale.z()); 62 | for (int group : object.groups) { 63 | for (auto &face : model->groups[group].faces) { 64 | glBegin(GL_TRIANGLES); 65 | glPushMatrix(); 66 | int p1 = face.x(); 67 | int p2 = face.y(); 68 | int p3 = face.z(); 69 | glNormal3f(model->normals[p1].x(), model->normals[p1].y(), model->normals[p1].z()); 70 | glVertex3f(model->vertices[p1].x(), model->vertices[p1].y(), model->vertices[p1].z()); 71 | glNormal3f(model->normals[p2].x(), model->normals[p2].y(), model->normals[p2].z()); 72 | glVertex3f(model->vertices[p2].x(), model->vertices[p2].y(), model->vertices[p2].z()); 73 | glNormal3f(model->normals[p3].x(), model->normals[p3].y(), model->normals[p3].z()); 74 | glVertex3f(model->vertices[p3].x(), model->vertices[p3].y(), model->vertices[p3].z()); 75 | glPopMatrix(); 76 | glEnd(); 77 | } 78 | } 79 | glPopMatrix(); 80 | } 81 | glPopMatrix(); 82 | } 83 | 84 | void SingleModelPreview::mousePressEvent(QMouseEvent *event) { 85 | mouse.X = event->x(); 86 | mouse.Y = event->y(); 87 | } 88 | 89 | void SingleModelPreview::mouseMoveEvent(QMouseEvent *event) { 90 | int x = event->x(); 91 | int y = event->y(); 92 | Qt::MouseButtons buttons = event->buttons(); 93 | if (buttons & Qt::LeftButton) { 94 | if (y < mouse.Y) { 95 | camera.angleX -= 1; 96 | } else if (y > mouse.Y) { 97 | camera.angleX += 1; 98 | } 99 | if (x < mouse.X) { 100 | camera.angleY -= 2; 101 | } else if (x > mouse.X) { 102 | camera.angleY += 2; 103 | } 104 | } else if (buttons & Qt::RightButton) { 105 | camera.X += (x - mouse.X) / 5; 106 | camera.Y -= (y - mouse.Y) / 5; 107 | } 108 | mouse.X = x; 109 | mouse.Y = y; 110 | repaint(); 111 | } 112 | 113 | void SingleModelPreview::wheelEvent(QWheelEvent *event) { 114 | camera.Z += event->delta() / 120; 115 | repaint(); 116 | } 117 | 118 | -------------------------------------------------------------------------------- /Source/Ui/Previews/SingleModelPreview.h: -------------------------------------------------------------------------------- 1 | #ifndef SINGLEMODELPREVIEW_H 2 | #define SINGLEMODELPREVIEW_H 3 | 4 | #include "../../Converters/IModelConverter.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct Mouse { 12 | int X; 13 | int Y; 14 | }; 15 | struct Camera { 16 | float X; 17 | float Y; 18 | float Z; 19 | float angleX; 20 | float angleY; 21 | }; 22 | 23 | class SingleModelPreview : public QOpenGLWidget { 24 | Q_OBJECT 25 | public: 26 | explicit SingleModelPreview(Model *model, QWidget *parent = nullptr); 27 | ~SingleModelPreview() override; 28 | private slots: 29 | void onReplaced(Model *model); 30 | private: 31 | Model *model; 32 | Camera camera{}; 33 | Mouse mouse{}; 34 | void initializeGL() override; 35 | void paintGL() override; 36 | void mousePressEvent(QMouseEvent *event) override; 37 | void mouseMoveEvent(QMouseEvent *event) override; 38 | void wheelEvent(QWheelEvent *event) override; 39 | }; 40 | 41 | #endif // SINGLEMODELPREVIEW_H 42 | -------------------------------------------------------------------------------- /Source/Ui/Previews/SingleTextFilePreview.cpp: -------------------------------------------------------------------------------- 1 | #include "SingleTextFilePreview.h" 2 | #include "ui_SingleTextFilePreview.h" 3 | 4 | SingleTextFilePreview::SingleTextFilePreview(QByteArray &item, const QString &encoding, QWidget *parent) 5 | : QWidget(parent), ui(new Ui::SingleTextFilePreview) { 6 | ui->setupUi(this); 7 | codec = QTextCodec::codecForName(encoding.toLocal8Bit()); 8 | QString encodeContent = codec->toUnicode(item); 9 | ui->label_encoding->setText(encoding); 10 | ui->plainTextEdit->appendPlainText(encodeContent); 11 | ui->plainTextEdit->moveCursor(QTextCursor::Start); 12 | } 13 | 14 | SingleTextFilePreview::~SingleTextFilePreview() { 15 | delete ui; 16 | } 17 | 18 | void SingleTextFilePreview::onReplaced(const QByteArray &text) { 19 | ui->plainTextEdit->clear(); 20 | QString encodeContent = codec->toUnicode(text); 21 | ui->plainTextEdit->appendPlainText(encodeContent); 22 | ui->plainTextEdit->moveCursor(QTextCursor::Start); 23 | } -------------------------------------------------------------------------------- /Source/Ui/Previews/SingleTextFilePreview.h: -------------------------------------------------------------------------------- 1 | #ifndef SINGLETEXTFILEPREVIEW_H 2 | #define SINGLETEXTFILEPREVIEW_H 3 | 4 | #include "../TreeItems/OnexTreeText.h" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace Ui { 10 | class SingleTextFilePreview; 11 | } 12 | class SingleTextFilePreview : public QWidget { 13 | Q_OBJECT 14 | public: 15 | explicit SingleTextFilePreview(QByteArray &item, const QString &encoding = "Windows-1250", QWidget *parent = nullptr); 16 | ~SingleTextFilePreview() override; 17 | private slots: 18 | void onReplaced(const QByteArray &text); 19 | private: 20 | Ui::SingleTextFilePreview *ui; 21 | QTextCodec *codec; 22 | }; 23 | 24 | #endif // SINGLETEXTFILEPREVIEW_H 25 | -------------------------------------------------------------------------------- /Source/Ui/Previews/SingleTextFilePreview.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SingleTextFilePreview 4 | 5 | 6 | 7 | 0 8 | 0 9 | 531 10 | 421 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Text 21 | 22 | 23 | 24 | 25 | 26 | Encoding: 27 | 28 | 29 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | true 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Source/Ui/Settings.cpp: -------------------------------------------------------------------------------- 1 | #include "Settings.h" 2 | #include "ui_settings.h" 3 | #include 4 | #include 5 | #include 6 | 7 | Settings::Settings(QWidget *parent) : QDialog(parent), ui(new Ui::Settings) { 8 | ui->setupUi(this); 9 | settingsPath = QApplication::applicationDirPath().left(1) + ":/Settings.ini"; 10 | load(); 11 | } 12 | 13 | Settings::~Settings() { 14 | delete ui; 15 | } 16 | 17 | QString Settings::getExportPath() const { 18 | return ui->label_export->text() + "/"; 19 | } 20 | 21 | QString Settings::getReplacePath() const { 22 | return ui->label_replace->text() + "/"; 23 | } 24 | 25 | QString Settings::getOpenPath() const { 26 | return ui->label_open->text() + "/"; 27 | } 28 | 29 | QString Settings::getSavePath() const { 30 | return ui->label_save->text() + "/"; 31 | } 32 | 33 | void Settings::on_btn_export_clicked() { 34 | QString dir = QFileDialog::getExistingDirectory(nullptr, tr("Select Directory")); 35 | if (dir.isEmpty()) 36 | return; 37 | ui->label_export->setText(dir); 38 | if (ui->label_replace->text().isEmpty()) 39 | ui->label_replace->setText(dir); 40 | } 41 | 42 | void Settings::on_btn_replace_clicked() { 43 | QString dir = QFileDialog::getExistingDirectory(nullptr, tr("Select Directory")); 44 | if (dir.isEmpty()) 45 | return; 46 | ui->label_replace->setText(dir); 47 | if (ui->label_export->text().isEmpty()) 48 | ui->label_export->setText(dir); 49 | } 50 | 51 | void Settings::on_btn_open_clicked() { 52 | QString dir = QFileDialog::getExistingDirectory(nullptr, tr("Select Directory")); 53 | if (dir.isEmpty()) 54 | return; 55 | ui->label_open->setText(dir); 56 | if (ui->label_save->text().isEmpty()) 57 | ui->label_save->setText(dir); 58 | } 59 | 60 | void Settings::on_btn_save_clicked() { 61 | QString dir = QFileDialog::getExistingDirectory(nullptr, tr("Select Directory")); 62 | if (dir.isEmpty()) 63 | return; 64 | ui->label_save->setText(dir); 65 | if (ui->label_open->text().isEmpty()) 66 | ui->label_open->setText(dir); 67 | } 68 | 69 | void Settings::on_btn_reset_clicked() { 70 | ui->label_export->clear(); 71 | ui->label_replace->clear(); 72 | ui->label_open->clear(); 73 | ui->label_save->clear(); 74 | } 75 | 76 | void Settings::accept() { 77 | save(); 78 | this->close(); 79 | } 80 | 81 | void Settings::showEvent(QShowEvent *event) { 82 | QDialog::showEvent(event); 83 | load(); 84 | } 85 | 86 | void Settings::save() { 87 | QSettings settingsFile(settingsPath, QSettings::IniFormat); 88 | settingsFile.setValue("ExportPath", ui->label_export->text()); 89 | settingsFile.setValue("ReplacePath", ui->label_replace->text()); 90 | settingsFile.setValue("OpenPath", ui->label_open->text()); 91 | settingsFile.setValue("SavePath", ui->label_save->text()); 92 | } 93 | 94 | void Settings::load() { 95 | QSettings settingsFile(settingsPath, QSettings::IniFormat); 96 | ui->label_export->setText(settingsFile.value("ExportPath", "").toString()); 97 | ui->label_replace->setText(settingsFile.value("ReplacePath", "").toString()); 98 | ui->label_open->setText(settingsFile.value("OpenPath", "").toString()); 99 | ui->label_save->setText(settingsFile.value("SavePath", "").toString()); 100 | } -------------------------------------------------------------------------------- /Source/Ui/Settings.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_H 2 | #define SETTINGS_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class Settings; 8 | } 9 | 10 | class Settings : public QDialog { 11 | Q_OBJECT 12 | 13 | public: 14 | explicit Settings(QWidget *parent = nullptr); 15 | ~Settings() override; 16 | QString getExportPath() const; 17 | QString getReplacePath() const; 18 | QString getOpenPath() const; 19 | QString getSavePath() const; 20 | 21 | private slots: 22 | void on_btn_export_clicked(); 23 | void on_btn_replace_clicked(); 24 | void on_btn_open_clicked(); 25 | void on_btn_save_clicked(); 26 | void on_btn_reset_clicked(); 27 | 28 | protected: 29 | void accept() override; 30 | void showEvent(QShowEvent *event) override; 31 | 32 | private: 33 | Ui::Settings *ui; 34 | QString settingsPath; 35 | void save(); 36 | void load(); 37 | }; 38 | 39 | #endif // SETTINGS_H 40 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNS4BbData.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexNS4BbData.h" 2 | 3 | OnexNS4BbData::OnexNS4BbData(QString name, QByteArray content, NosZlibOpener *opener, int id, 4 | int creationDate, bool compressed) 5 | : OnexTreeImage(name, opener, content, id, creationDate, compressed) { 6 | if (content.isEmpty()) 7 | this->content = QByteArrayLiteral("\x00\x00\x00\x00"); 8 | } 9 | 10 | OnexNS4BbData::OnexNS4BbData(QJsonObject jo, NosZlibOpener *opener, const QString &directory) : OnexTreeImage(jo["ID"].toString(), opener) { 11 | this->content = QByteArrayLiteral("\x00\x00\x00\x00"); 12 | setId(jo["ID"].toInt(), true); 13 | setCreationDate(jo["Date"].toString(), true); 14 | setCompressed(jo["isCompressed"].toBool(), true); 15 | onReplace(directory + jo["path"].toString()); 16 | } 17 | 18 | OnexNS4BbData::~OnexNS4BbData() = default; 19 | 20 | QImage OnexNS4BbData::getImage() { 21 | ImageResolution resolution = this->getResolution(); 22 | return imageConverter->convertBGRA8888_INTERLACED(content, resolution.x, resolution.y, 4); 23 | } 24 | 25 | ImageResolution OnexNS4BbData::getResolution() { 26 | int x = opener->getLittleEndianConverter()->fromShort(content.mid(0, 2)); 27 | int y = opener->getLittleEndianConverter()->fromShort(content.mid(2, 2)); 28 | return ImageResolution{x, y}; 29 | } 30 | 31 | int OnexNS4BbData::afterReplace(QImage image) { 32 | QByteArray newContent; 33 | newContent.push_back(opener->getLittleEndianConverter()->toShort(image.width())); 34 | newContent.push_back(opener->getLittleEndianConverter()->toShort(image.height())); 35 | newContent.push_back(imageConverter->toBGRA8888_INTERLACED(image)); 36 | setContent(newContent); 37 | setWidth(image.width(), true); 38 | setHeight(image.height(), true); 39 | 40 | emit OnexTreeImage::replaceSignal(this->getImage()); 41 | emit replaceInfo(generateInfos()); 42 | return 1; 43 | } 44 | 45 | void OnexNS4BbData::setWidth(int width, bool update) { 46 | content.replace(0, 2, opener->getLittleEndianConverter()->toShort(width)); 47 | if (update) 48 | emit changeSignal("Width", width); 49 | } 50 | 51 | void OnexNS4BbData::setHeight(int height, bool update) { 52 | content.replace(2, 2, opener->getLittleEndianConverter()->toShort(height)); 53 | if (update) 54 | emit changeSignal("Height", height); 55 | } 56 | 57 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNS4BbData.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXNS4BBDATA_H 2 | #define ONEXNS4BBDATA_H 3 | 4 | #include "OnexTreeImage.h" 5 | 6 | class OnexNS4BbData : public OnexTreeImage { 7 | Q_OBJECT 8 | public: 9 | OnexNS4BbData(QString name, QByteArray content, NosZlibOpener *opener, int id = -1, 10 | int creationDate = 0, bool compressed = false); 11 | OnexNS4BbData(QJsonObject jo, NosZlibOpener *opener, const QString &directory); 12 | ~OnexNS4BbData() override; 13 | QImage getImage() override; 14 | ImageResolution getResolution() override; 15 | public slots: 16 | int afterReplace(QImage image) override; 17 | void setWidth(int width, bool update = false) override; 18 | void setHeight(int height, bool update = false) override; 19 | }; 20 | 21 | #endif // ONEXNS4BBDATA_H 22 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNSipData.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexNSipData.h" 2 | 3 | OnexNSipData::OnexNSipData(QString name, QByteArray content, NosZlibOpener *opener, int id, 4 | int creationDate, bool compressed) 5 | : OnexTreeImage(name, opener, content, id, creationDate, compressed) { 6 | if (content.isEmpty()) 7 | this->content = QByteArrayLiteral("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x0D\x00\x00\x00"); 8 | } 9 | 10 | OnexNSipData::OnexNSipData(QJsonObject jo, NosZlibOpener *opener, const QString &directory) : OnexTreeImage(jo["ID"].toString(), opener) { 11 | this->content = QByteArrayLiteral("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x0D\x00\x00\x00"); 12 | setId(jo["ID"].toInt(), true); 13 | setCreationDate(jo["Date"].toString(), true); 14 | setCompressed(jo["isCompressed"].toBool(), true); 15 | setCenterX(jo["Center-X"].toInt(), true); 16 | setCenterY(jo["Center-Y"].toInt(), true); 17 | onReplace(directory + jo["path"].toString()); 18 | } 19 | 20 | OnexNSipData::~OnexNSipData() = default; 21 | 22 | QImage OnexNSipData::getImage() { 23 | ImageResolution resolution = this->getResolution(); 24 | return imageConverter->convertGBAR4444(content, resolution.x, resolution.y, 13); 25 | } 26 | 27 | ImageResolution OnexNSipData::getResolution() { 28 | int x = opener->getLittleEndianConverter()->fromShort(content.mid(1, 2)); 29 | int y = opener->getLittleEndianConverter()->fromShort(content.mid(3, 2)); 30 | return ImageResolution{x, y}; 31 | } 32 | 33 | ImageResolution OnexNSipData::getCenter() { 34 | int x = opener->getLittleEndianConverter()->fromShort(content.mid(5, 2)); 35 | int y = opener->getLittleEndianConverter()->fromShort(content.mid(7, 2)); 36 | return ImageResolution{x, y}; 37 | } 38 | 39 | int OnexNSipData::afterReplace(QImage image) { 40 | QByteArray newContent; 41 | newContent.push_back(content.mid(0, 1)); 42 | newContent.push_back(opener->getLittleEndianConverter()->toShort(image.width())); 43 | newContent.push_back(opener->getLittleEndianConverter()->toShort(image.height())); 44 | newContent.push_back(content.mid(5, 8)); 45 | newContent.push_back(imageConverter->toGBAR4444(image)); 46 | setContent(newContent); 47 | setWidth(image.width(), true); 48 | setHeight(image.height(), true); 49 | 50 | emit OnexTreeImage::replaceSignal(this->getImage()); 51 | emit replaceInfo(generateInfos()); 52 | return 1; 53 | } 54 | 55 | void OnexNSipData::setWidth(int width, bool update) { 56 | content.replace(1, 2, opener->getLittleEndianConverter()->toShort(width)); 57 | if (update) 58 | emit changeSignal("Width", width); 59 | } 60 | 61 | void OnexNSipData::setHeight(int height, bool update) { 62 | content.replace(3, 2, opener->getLittleEndianConverter()->toShort(height)); 63 | if (update) 64 | emit changeSignal("Height", height); 65 | } 66 | 67 | void OnexNSipData::setCenterX(int center, bool update) { 68 | content.replace(5, 2, opener->getLittleEndianConverter()->toShort(center)); 69 | if (update) 70 | emit changeSignal("Center-X", center); 71 | } 72 | 73 | void OnexNSipData::setCenterY(int center, bool update) { 74 | content.replace(7, 2, opener->getLittleEndianConverter()->toShort(center)); 75 | if (update) 76 | emit changeSignal("Center-Y", center); 77 | } 78 | 79 | FileInfo *OnexNSipData::generateInfos() { 80 | FileInfo *infos = OnexTreeImage::generateInfos(); 81 | if (hasParent()) { 82 | connect(infos->addIntLineEdit("Center-X", getCenter().x), &QLineEdit::textChanged, 83 | [=](const QString &value) { setCenterY(value.toInt()); }); 84 | connect(infos->addIntLineEdit("Center-Y", getCenter().y), &QLineEdit::textChanged, 85 | [=](const QString &value) { setCenterX(value.toInt()); }); 86 | } 87 | return infos; 88 | } 89 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNSipData.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXNSIPDATA_H 2 | #define ONEXNSIPDATA_H 3 | 4 | #include "OnexTreeImage.h" 5 | 6 | class OnexNSipData : public OnexTreeImage { 7 | Q_OBJECT 8 | public: 9 | OnexNSipData(QString name, QByteArray content, NosZlibOpener *opener, int id = -1, 10 | int creationDate = 0, bool compressed = false); 11 | OnexNSipData(QJsonObject jo, NosZlibOpener *opener, const QString &directory); 12 | ~OnexNSipData() override; 13 | QImage getImage() override; 14 | ImageResolution getResolution() override; 15 | ImageResolution getCenter(); 16 | public slots: 17 | int afterReplace(QImage image) override; 18 | void setWidth(int width, bool update = false); 19 | void setHeight(int height, bool update = false); 20 | void setCenterX(int center, bool update = false); 21 | void setCenterY(int center, bool update = false); 22 | protected: 23 | FileInfo *generateInfos() override; 24 | }; 25 | 26 | #endif // ONEXNSIPDATA_H 27 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNSmcData.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexNSmcData.h" 2 | #include "../Previews/SingleTextFilePreview.h" 3 | #include 4 | #include 5 | #include 6 | 7 | OnexNSmcData::OnexNSmcData(const QString &name, QByteArray content, NosZlibOpener *opener, int id, int creationDate, 8 | bool compressed) : OnexTreeZlibItem(name, opener, content, id, creationDate, compressed) { 9 | } 10 | 11 | OnexNSmcData::OnexNSmcData(QJsonObject jo, NosZlibOpener *opener, const QString &directory) : OnexTreeZlibItem(jo["ID"].toString(), opener) { 12 | setId(jo["ID"].toInt(), true); 13 | setCreationDate(jo["Date"].toString(), true); 14 | setCompressed(jo["isCompressed"].toBool(), true); 15 | onReplace(directory + jo["path"].toString()); 16 | } 17 | 18 | OnexNSmcData::~OnexNSmcData() = default; 19 | 20 | QWidget *OnexNSmcData::getPreview() { 21 | if (!hasParent()) 22 | return nullptr; 23 | QByteArray json = QJsonDocument(toJson()).toJson(); 24 | auto *textPreview = new SingleTextFilePreview(json); 25 | connect(this, SIGNAL(replaceSignal(QByteArray)), textPreview, SLOT(onReplaced(QByteArray))); 26 | return textPreview; 27 | } 28 | 29 | QString OnexNSmcData::getExportExtension() { 30 | return ".json"; 31 | } 32 | 33 | int OnexNSmcData::saveAsFile(const QString &path, QByteArray content) { 34 | return OnexTreeItem::saveAsFile(path, QJsonDocument(toJson()).toJson()); 35 | } 36 | 37 | int OnexNSmcData::afterReplace(QByteArray content) { 38 | setContent(fromJson(content)); 39 | emit replaceSignal(this->getContent()); 40 | return 1; 41 | } 42 | 43 | QJsonObject OnexNSmcData::toJson() { 44 | QJsonObject jo; 45 | int amount = content.at(0); 46 | uint8_t flag = content.at(1); 47 | jo["loop"] = (bool) (flag >> 7); 48 | jo["unknown"] = (bool) ((flag >> 6) & 1); 49 | int offset = 2; 50 | 51 | QJsonArray array; 52 | for (int i = 0; i < amount; i++) { 53 | QJsonObject frame; 54 | frame["spriteIndex"] = content.at(offset); 55 | frame["triggerEffect"] = content.at(offset + 1) == 9; // 0=false, 9=true 56 | array.append(frame); 57 | offset += 2; 58 | } 59 | jo["frames"] = array; 60 | 61 | return jo; 62 | } 63 | 64 | QByteArray OnexNSmcData::fromJson(const QByteArray &data) { 65 | QJsonObject jo = QJsonDocument::fromJson(data).object(); 66 | QJsonArray array = jo["frames"].toArray(); 67 | QByteArray newContent; 68 | newContent.append((uint8_t) array.size()); 69 | bool loop = jo["loop"].toBool(); 70 | bool unknown = jo["unknown"].toBool(); 71 | uint8_t flag = (loop << 7) + (unknown << 6); 72 | newContent.append(flag); 73 | for (QJsonValueRef value : array) { 74 | QJsonObject frame = value.toObject(); 75 | newContent.append((uint8_t) frame["spriteIndex"].toInt()); 76 | newContent.append((uint8_t) (frame["triggerEffect"].toBool() ? 9 : 0)); 77 | } 78 | return newContent; 79 | } 80 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNSmcData.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXNSMCDATA_H 2 | #define ONEXNSMCDATA_H 3 | 4 | 5 | #include "OnexTreeZlibItem.h" 6 | 7 | class OnexNSmcData : public OnexTreeZlibItem { 8 | Q_OBJECT 9 | public: 10 | OnexNSmcData(const QString &name, QByteArray content, NosZlibOpener *opener, int id = -1, int creationDate = 0, 11 | bool compressed = false); 12 | OnexNSmcData(QJsonObject jo, NosZlibOpener *opener, const QString &directory); 13 | ~OnexNSmcData() override; 14 | QWidget *getPreview() override; 15 | QString getExportExtension() override; 16 | public slots: 17 | int afterReplace(QByteArray content) override; 18 | signals: 19 | void replaceSignal(QByteArray text); 20 | protected: 21 | int saveAsFile(const QString &path, QByteArray content) override; 22 | QJsonObject toJson(); 23 | QByteArray fromJson(const QByteArray &data); 24 | }; 25 | 26 | 27 | #endif //ONEXNSMCDATA_H 28 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNSmnData.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexNSmnData.h" 2 | #include "../Previews/SingleTextFilePreview.h" 3 | #include 4 | #include 5 | #include 6 | 7 | OnexNSmnData::OnexNSmnData(const QString &name, int creationDate, INosFileOpener *opener, QByteArray content) : OnexTreeItem(name, opener, content), 8 | creationDate(creationDate) { 9 | 10 | } 11 | 12 | int OnexNSmnData::getCreationDate() { 13 | return creationDate; 14 | } 15 | 16 | QString OnexNSmnData::getDateAsString() { 17 | int year = (getCreationDate() & 0xFFFF0000) >> 0x10; 18 | int month = (getCreationDate() & 0xFF00) >> 0x08; 19 | int day = getCreationDate() & 0xFF; 20 | return QString("%1/%2/%3").arg(day, 2, 16, QChar('0')).arg(month, 2, 16, QChar('0')).arg(year, 4, 16, QChar('0')); 21 | } 22 | 23 | void OnexNSmnData::loadJson(QJsonArray array) { 24 | for (auto &&i : array) { 25 | QJsonObject jo = i.toObject(); 26 | addChild(new OnexNSmnData(QString::number(childCount()), creationDate, opener, QJsonDocument(jo).toJson())); 27 | } 28 | } 29 | 30 | void OnexNSmnData::setCreationDate(const QString &date, bool update) { 31 | QStringList parts = date.split("/", QString::SplitBehavior::SkipEmptyParts); 32 | if (parts.size() != 3) 33 | this->creationDate = 0; 34 | else { 35 | int year = parts[2].toInt(nullptr, 16) << 0x10; 36 | int month = parts[1].toInt(nullptr, 16) << 0x08; 37 | int day = parts[0].toInt(nullptr, 16); 38 | this->creationDate = year + month + day; 39 | } 40 | if (update) 41 | emit changeSignal("Date", getDateAsString()); 42 | } 43 | 44 | FileInfo *OnexNSmnData::generateInfos() { 45 | auto *infos = OnexTreeItem::generateInfos(); 46 | if (!hasParent()) { 47 | infos->addStringLineEdit("Header", getContent())->setEnabled(false); 48 | connect(infos->addStringLineEdit("Date", getDateAsString()), &QLineEdit::textChanged, 49 | [=](const QString &value) { setCreationDate(value); }); 50 | } 51 | return infos; 52 | } 53 | 54 | QWidget *OnexNSmnData::getPreview() { 55 | if (!hasParent()) 56 | return nullptr; 57 | auto *textPreview = new SingleTextFilePreview(content); 58 | connect(this, SIGNAL(replaceSignal(QByteArray)), textPreview, SLOT(onReplaced(QByteArray))); 59 | return textPreview; 60 | } 61 | 62 | QString OnexNSmnData::getExportExtension() { 63 | return ".json"; 64 | } 65 | 66 | int OnexNSmnData::onExport(QString directory) { 67 | if (!hasParent()) { 68 | QMessageBox::StandardButton message = QMessageBox::question(nullptr, "Export?", "Export all items together as one JSON?"); 69 | if (message == QMessageBox::Yes) { 70 | QJsonArray fullJson; 71 | for (int i = 0; i < childCount(); i++) { 72 | auto *ct = static_cast(child(i)); 73 | QJsonObject jo = QJsonDocument::fromJson(ct->getContent()).object(); 74 | fullJson.append(jo); 75 | } 76 | saveAsFile(directory + name + ".json", QJsonDocument(fullJson).toJson()); 77 | return 1; 78 | } else { 79 | return OnexTreeItem::onExport(directory); 80 | } 81 | } 82 | return OnexTreeItem::onExport(directory); 83 | } 84 | 85 | int OnexNSmnData::onReplace(QString directory) { 86 | if (!hasParent()) { 87 | QMessageBox::StandardButton message = QMessageBox::question(nullptr, "Replace?", "Replace all items together from one JSON?"); 88 | if (message == QMessageBox::Yes) { 89 | QString path = directory + name + ".json"; 90 | QFile file(path); 91 | if (file.open(QIODevice::ReadOnly)) { 92 | QJsonArray fullJson = QJsonDocument::fromJson(file.readAll()).array(); 93 | file.close(); 94 | takeChildren().clear(); 95 | loadJson(fullJson); 96 | return childCount(); 97 | } else { 98 | QMessageBox::critical(nullptr, "Woops", "Couldn't open " + path); 99 | return 0; 100 | } 101 | } 102 | } 103 | return OnexTreeItem::onReplace(directory); 104 | } 105 | 106 | int OnexNSmnData::afterReplace(QByteArray content) { 107 | setContent(content); 108 | emit replaceSignal(this->getContent()); 109 | return 1; 110 | } -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNSmnData.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXNSMNDATA_H 2 | #define ONEXNSMNDATA_H 3 | 4 | #include "../OnexTreeItem.h" 5 | 6 | class OnexNSmnData : public OnexTreeItem { 7 | Q_OBJECT 8 | 9 | public: 10 | OnexNSmnData(const QString &name, int creationDate, INosFileOpener *opener, QByteArray content); 11 | QWidget *getPreview() override; 12 | QString getExportExtension() override; 13 | int getCreationDate(); 14 | public slots: 15 | int onExport(QString directory) override; 16 | int onReplace(QString directory) override; 17 | int afterReplace(QByteArray content) override; 18 | void setCreationDate(const QString &date, bool update = false); 19 | signals: 20 | void replaceSignal(QByteArray text); 21 | protected: 22 | int creationDate; 23 | void loadJson(QJsonArray array); 24 | FileInfo *generateInfos() override; 25 | QString getDateAsString(); 26 | }; 27 | 28 | 29 | #endif // ONEXNSMNDATA_H -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNSmpData.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexNSmpData.h" 2 | #include "../Previews/MultiImagePreview.h" 3 | #include "OnexNSmpFrame.h" 4 | 5 | OnexNSmpData::OnexNSmpData(const QString &name, QByteArray content, NosZlibOpener *opener, 6 | int id, int creationDate, bool compressed) 7 | : OnexTreeZlibItem(name, opener, content, id, creationDate, compressed) { 8 | if (content.isEmpty()) 9 | this->content = QByteArray(0x0); 10 | if (id == -1) 11 | return; 12 | int amount = content.at(0); 13 | for (int i = 0; i < amount; i++) { 14 | int width = opener->getLittleEndianConverter()->fromShort(content.mid(1 + i * 12, 2)); 15 | int height = opener->getLittleEndianConverter()->fromShort(content.mid(3 + i * 12, 2)); 16 | int xOrigin = opener->getLittleEndianConverter()->fromShort(content.mid(5 + i * 12, 2)); 17 | int yOrigin = opener->getLittleEndianConverter()->fromShort(content.mid(7 + i * 12, 2)); 18 | int offset = opener->getLittleEndianConverter()->fromInt(content.mid(9 + i * 12, 4)); 19 | QByteArray subContent = content.mid(offset, (width * 2 * height)); 20 | addFrame(subContent, width, height, xOrigin, yOrigin); 21 | } 22 | } 23 | 24 | OnexNSmpData::OnexNSmpData(QJsonObject jo, NosZlibOpener *opener, const QString &directory) : OnexTreeZlibItem(jo["ID"].toString(), opener) { 25 | this->content = QByteArrayLiteral("\x00"); 26 | setId(jo["ID"].toInt(), true); 27 | setCreationDate(jo["Date"].toString(), true); 28 | setCompressed(jo["isCompressed"].toBool(), true); 29 | 30 | QJsonArray contentArray = jo["content"].toArray(); 31 | 32 | for (auto &&i : contentArray) { 33 | QJsonObject frameJo = i.toObject(); 34 | this->addChild(new OnexNSmpFrame(name + "_" + QString::number(this->childCount()), frameJo, opener, directory)); 35 | } 36 | } 37 | 38 | OnexNSmpData::~OnexNSmpData() = default; 39 | 40 | QWidget *OnexNSmpData::getPreview() { 41 | if (!hasParent()) 42 | return nullptr; 43 | auto *images = new QList(); 44 | for (int i = 0; i != this->childCount(); ++i) { 45 | auto *item = dynamic_cast(this->child(i)); 46 | images->append(item->getImage()); 47 | } 48 | auto *imagePreview = new MultiImagePreview(images); 49 | connect(this, SIGNAL(replaceSignal(QList * )), imagePreview, SLOT(onReplaced(QList * ))); 50 | return imagePreview; 51 | } 52 | 53 | QByteArray OnexNSmpData::getContent() { 54 | int amount = childCount(); 55 | if (!hasParent() || amount <= 0) 56 | return content; 57 | QByteArray offsetArray; 58 | offsetArray.push_back((uint8_t) amount); 59 | int sizeOfOffsetArray = 1 + amount * 12; 60 | QByteArray contentArray; 61 | for (int i = 0; i < amount; i++) { 62 | int currentFileOffset = sizeOfOffsetArray + contentArray.size(); 63 | auto *currentItem = dynamic_cast(this->child(i)); 64 | offsetArray.push_back(opener->getLittleEndianConverter()->toShort(currentItem->getWidth())); 65 | offsetArray.push_back(opener->getLittleEndianConverter()->toShort(currentItem->getHeight())); 66 | offsetArray.push_back(opener->getLittleEndianConverter()->toShort(currentItem->getXOrigin())); 67 | offsetArray.push_back(opener->getLittleEndianConverter()->toShort(currentItem->getYOrigin())); 68 | offsetArray.push_back(opener->getLittleEndianConverter()->toInt(currentFileOffset)); 69 | contentArray.push_back(currentItem->getContent()); 70 | } 71 | this->content = QByteArray(); 72 | content.push_back(offsetArray); 73 | content.push_back(contentArray); 74 | return content; 75 | } 76 | 77 | void OnexNSmpData::setName(QString name) { 78 | OnexTreeZlibItem::setName(name); 79 | QList childList = takeChildren(); 80 | for (int i = 0; i < childList.size(); i++) { 81 | auto *item = static_cast(childList.at(i)); 82 | item->OnexTreeItem::setName(name + "_" + QString::number(i)); 83 | } 84 | addChildren(childList); 85 | } 86 | 87 | int OnexNSmpData::afterReplace(QByteArray content) { 88 | auto *images = new QList(); 89 | for (int i = 0; i != this->childCount(); ++i) { 90 | auto *item = dynamic_cast(this->child(i)); 91 | images->append(item->getImage()); 92 | } 93 | emit replaceSignal(images); 94 | emit replaceInfo(generateInfos()); 95 | return 1; 96 | } 97 | 98 | OnexTreeItem *OnexNSmpData::addFrame(QByteArray content, short width, short height, short xOrigin, short yOrigin) { 99 | OnexTreeItem *frame = new OnexNSmpFrame(name + "_" + QString::number(this->childCount()), content, width, height, xOrigin, yOrigin, 100 | (NosZlibOpener *) opener, id, creationDate, compressed); 101 | this->addChild(frame); 102 | return frame; 103 | } 104 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNSmpData.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXNSMPDATA_H 2 | #define ONEXNSMPDATA_H 3 | 4 | #include "../../Openers/NosZlibOpener.h" 5 | #include "OnexTreeZlibItem.h" 6 | 7 | class OnexNSmpData : public OnexTreeZlibItem { 8 | Q_OBJECT 9 | public: 10 | OnexNSmpData(const QString &name, QByteArray content, NosZlibOpener *opener, 11 | int id = -1, int creationDate = 0, bool compressed = false); 12 | OnexNSmpData(QJsonObject jo, NosZlibOpener *opener, const QString &directory); 13 | ~OnexNSmpData() override; 14 | QWidget *getPreview() override; 15 | QByteArray getContent() override; 16 | OnexTreeItem *addFrame(QByteArray content = QByteArray(), short width = 0, short height = 0, short xOrigin = 0, short yOrigin = 0); 17 | public slots: 18 | void setName(QString name) override; 19 | int afterReplace(QByteArray content) override; 20 | signals: 21 | void replaceSignal(QList *newImages); 22 | }; 23 | 24 | #endif // ONEXNSMPDATA_H 25 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNSmpFrame.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexNSmpFrame.h" 2 | 3 | OnexNSmpFrame::OnexNSmpFrame(QString name, QByteArray content, int width, int height, int xOrigin, int yOrigin, NosZlibOpener *opener, 4 | int id, int creationDate, bool compressed) 5 | : OnexTreeImage(name, opener, content, id, creationDate, compressed) { 6 | setFlags(this->flags() & (~Qt::ItemIsEditable)); 7 | this->width = width; 8 | this->height = height; 9 | this->xOrigin = xOrigin; 10 | this->yOrigin = yOrigin; 11 | } 12 | 13 | OnexNSmpFrame::OnexNSmpFrame(QString name, QJsonObject jo, NosZlibOpener *opener, const QString &directory) : OnexTreeImage(name, opener) { 14 | setXOrigin(jo["x-Origin"].toInt(), true); 15 | setYOrigin(jo["y-Origin"].toInt(), true); 16 | onReplace(directory + jo["path"].toString()); 17 | } 18 | 19 | OnexNSmpFrame::~OnexNSmpFrame() = default; 20 | 21 | QImage OnexNSmpFrame::getImage() { 22 | ImageResolution resolution = this->getResolution(); 23 | return imageConverter->convertGBAR4444(content, resolution.x, resolution.y); 24 | } 25 | 26 | ImageResolution OnexNSmpFrame::getResolution() { 27 | return ImageResolution{this->width, this->height}; 28 | } 29 | 30 | int OnexNSmpFrame::afterReplace(QImage image) { 31 | setWidth(image.width(), true); 32 | setHeight(image.height(), true); 33 | setContent(imageConverter->toGBAR4444(image)); 34 | emit OnexTreeImage::replaceSignal(this->getImage()); 35 | FileInfo *newInfo = generateInfos(); 36 | emit replaceInfo(newInfo); 37 | emit replaceInfo(generateInfos()); 38 | return 1; 39 | } 40 | 41 | int OnexNSmpFrame::getWidth() { 42 | return width; 43 | } 44 | 45 | int OnexNSmpFrame::getHeight() { 46 | return height; 47 | } 48 | 49 | int OnexNSmpFrame::getXOrigin() { 50 | return xOrigin; 51 | } 52 | 53 | int OnexNSmpFrame::getYOrigin() { 54 | return yOrigin; 55 | } 56 | 57 | void OnexNSmpFrame::setId(int id, bool update) { 58 | this->id = id; 59 | } 60 | 61 | void OnexNSmpFrame::setWidth(int width, bool update) { 62 | this->width = width; 63 | if (update) 64 | emit changeSignal("Width", width); 65 | } 66 | 67 | void OnexNSmpFrame::setHeight(int height, bool update) { 68 | this->height = height; 69 | if (update) 70 | emit changeSignal("Height", height); 71 | } 72 | 73 | void OnexNSmpFrame::setXOrigin(int xOrigin, bool update) { 74 | this->xOrigin = xOrigin; 75 | if (update) 76 | emit changeSignal("x-Origin", xOrigin); 77 | } 78 | 79 | void OnexNSmpFrame::setYOrigin(int yOrigin, bool update) { 80 | this->yOrigin = yOrigin; 81 | if (update) 82 | emit changeSignal("y-Origin", yOrigin); 83 | } 84 | 85 | FileInfo *OnexNSmpFrame::generateInfos() { 86 | FileInfo *infos = OnexTreeItem::generateInfos(); 87 | if (hasParent()) { 88 | infos->addIntLineEdit("Width", getWidth())->setDisabled(true); 89 | infos->addIntLineEdit("Height", getHeight())->setDisabled(true); 90 | connect(infos->addIntLineEdit("x-Origin", getXOrigin()), &QLineEdit::textChanged, 91 | [=](const QString &value) { setXOrigin(value.toInt()); }); 92 | connect(infos->addIntLineEdit("y-Origin", getYOrigin()), &QLineEdit::textChanged, 93 | [=](const QString &value) { setYOrigin(value.toInt()); }); 94 | } 95 | return infos; 96 | } 97 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNSmpFrame.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXNSMPFRAME_H 2 | #define ONEXNSMPFRAME_H 3 | 4 | #include "OnexTreeImage.h" 5 | 6 | class OnexNSmpFrame : public OnexTreeImage { 7 | Q_OBJECT 8 | public: 9 | OnexNSmpFrame(QString name, QByteArray content, int width, int height, int xOrigin, int yOrigin, NosZlibOpener *opener, 10 | int id = -1, int creationDate = 0, bool compressed = false); 11 | OnexNSmpFrame(QString name, QJsonObject jo, NosZlibOpener *opener, const QString &directory); 12 | ~OnexNSmpFrame() override; 13 | QImage getImage() override; 14 | ImageResolution getResolution() override; 15 | int getWidth(); 16 | int getHeight(); 17 | int getXOrigin(); 18 | int getYOrigin(); 19 | public slots: 20 | void setId(int id, bool update = false) override; 21 | int afterReplace(QImage image) override; 22 | void setWidth(int width, bool update = false) override; 23 | void setHeight(int height, bool update = false) override; 24 | void setXOrigin(int xOrigin, bool update = false); 25 | void setYOrigin(int yOrigin, bool update = false); 26 | protected: 27 | int width; 28 | int height; 29 | int xOrigin; 30 | int yOrigin; 31 | FileInfo *generateInfos() override; 32 | }; 33 | 34 | #endif // ONEXNSMPFRAME_H 35 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNStcData.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexNStcData.h" 2 | 3 | OnexNStcData::OnexNStcData(QString name, QByteArray content, NosZlibOpener *opener, int id, 4 | int creationDate, bool compressed) 5 | : OnexTreeImage(name, opener, content, id, creationDate, compressed) { 6 | if (content.isEmpty()) 7 | this->content = QByteArrayLiteral("\x00\x00\x00\x00\x00\x00\x00\x00"); 8 | } 9 | 10 | OnexNStcData::OnexNStcData(QJsonObject jo, NosZlibOpener *opener, const QString &directory) : OnexTreeImage(jo["ID"].toString(), opener) { 11 | this->content = QByteArrayLiteral("\x00\x00\x00\x00\x00\x00\x00\x00"); 12 | setId(jo["ID"].toInt(), true); 13 | setCreationDate(jo["Date"].toString(), true); 14 | setCompressed(jo["isCompressed"].toBool(), true); 15 | onReplace(directory + jo["path"].toString()); 16 | } 17 | 18 | OnexNStcData::~OnexNStcData() = default; 19 | 20 | QImage OnexNStcData::getImage() { 21 | ImageResolution resolution = this->getResolution(); 22 | return imageConverter->convertNSTC(content, resolution.x, resolution.y, 4); 23 | } 24 | 25 | ImageResolution OnexNStcData::getResolution() { 26 | int x = opener->getLittleEndianConverter()->fromShort(content.mid(0, 2)); 27 | int y = opener->getLittleEndianConverter()->fromShort(content.mid(2, 2)); 28 | return ImageResolution{x, y}; 29 | } 30 | 31 | int OnexNStcData::afterReplace(QImage image) { 32 | QByteArray newContent; 33 | newContent.push_back(opener->getLittleEndianConverter()->toShort(image.width())); 34 | newContent.push_back(opener->getLittleEndianConverter()->toShort(image.height())); 35 | newContent.push_back(imageConverter->toNSTC(image)); 36 | setContent(newContent); 37 | setWidth(image.width(), true); 38 | setHeight(image.height(), true); 39 | 40 | emit OnexTreeImage::replaceSignal(this->getImage()); 41 | emit replaceInfo(generateInfos()); 42 | return 1; 43 | } 44 | 45 | void OnexNStcData::setWidth(int width, bool update) { 46 | content.replace(0, 2, opener->getLittleEndianConverter()->toShort(width)); 47 | if (update) 48 | emit changeSignal("Width", width); 49 | } 50 | 51 | void OnexNStcData::setHeight(int height, bool update) { 52 | content.replace(2, 2, opener->getLittleEndianConverter()->toShort(height)); 53 | if (update) 54 | emit changeSignal("Width", height); 55 | } 56 | 57 | bool OnexNStcData::hasGoodResolution(int x, int y) { 58 | ImageResolution currentResolution = this->getResolution(); 59 | return (x / 2 == currentResolution.x && y / 2 == currentResolution.y); 60 | } 61 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNStcData.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXNSTCDATA_H 2 | #define ONEXNSTCDATA_H 3 | 4 | #include "OnexTreeImage.h" 5 | 6 | class OnexNStcData : public OnexTreeImage { 7 | Q_OBJECT 8 | public: 9 | OnexNStcData(QString name, QByteArray content, NosZlibOpener *opener, int id = -1, 10 | int creationDate = 0, bool compressed = false); 11 | OnexNStcData(QJsonObject jo, NosZlibOpener *opener, const QString &directory); 12 | ~OnexNStcData() override; 13 | QImage getImage() override; 14 | ImageResolution getResolution() override; 15 | public slots: 16 | int afterReplace(QImage image) override; 17 | void setWidth(int width, bool update = false); 18 | void setHeight(int height, bool update = false); 19 | protected: 20 | bool hasGoodResolution(int x, int y) override; 21 | }; 22 | 23 | #endif // ONEXNSTCDATA_H 24 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNStgData.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexNStgData.h" 2 | #include "../Previews/SingleModelPreview.h" 3 | 4 | ObjConverter OnexNStgData::objConverter = ObjConverter(); 5 | NosModelConverter OnexNStgData::nosModelConverter = NosModelConverter(); 6 | 7 | OnexNStgData::OnexNStgData(QString name, QByteArray content, NosZlibOpener *opener, int id, 8 | int creationDate, bool compressed) 9 | : OnexTreeZlibItem(name, opener, content, id, creationDate, compressed) { 10 | model = nullptr; 11 | } 12 | 13 | OnexNStgData::OnexNStgData(QJsonObject jo, NosZlibOpener *opener, const QString &directory) : OnexTreeZlibItem(jo["ID"].toString(), opener) { 14 | model = new Model(); 15 | setId(jo["ID"].toInt(), true); 16 | setCreationDate(jo["Date"].toString(), true); 17 | setCompressed(jo["isCompressed"].toBool(), true); 18 | model->uvScale = jo["UV-Scale"].toDouble(); 19 | onReplace(directory + jo["path"].toString()); 20 | 21 | for (int i = 0; i < model->objects.size(); i++) { 22 | model->objects[i].position.setX(jo["O-" + QString::number(i) + "-x-Position"].toDouble()); 23 | model->objects[i].position.setY(jo["O-" + QString::number(i) + "-y-Position"].toDouble()); 24 | model->objects[i].position.setZ(jo["O-" + QString::number(i) + "-z-Position"].toDouble()); 25 | model->objects[i].rotation.setX(jo["O-" + QString::number(i) + "-x-Rotation"].toDouble()); 26 | model->objects[i].rotation.setY(jo["O-" + QString::number(i) + "-y-Rotation"].toDouble()); 27 | model->objects[i].rotation.setZ(jo["O-" + QString::number(i) + "-z-Rotation"].toDouble()); 28 | model->objects[i].rotation.setW(jo["O-" + QString::number(i) + "-w-Rotation"].toDouble()); 29 | model->objects[i].scale.setX(jo["O-" + QString::number(i) + "-x-Scale"].toDouble()); 30 | model->objects[i].scale.setY(jo["O-" + QString::number(i) + "-y-Scale"].toDouble()); 31 | model->objects[i].scale.setZ(jo["O-" + QString::number(i) + "-z-Scale"].toDouble()); 32 | } 33 | } 34 | 35 | OnexNStgData::~OnexNStgData() = default; 36 | 37 | QWidget *OnexNStgData::getPreview() { 38 | if (!hasParent()) 39 | return nullptr; 40 | if (model == nullptr) { 41 | model = nosModelConverter.fromBinary(content); 42 | } 43 | auto *modelPreview = new SingleModelPreview(model); 44 | connect(this, SIGNAL(replaceSignal(Model * )), modelPreview, SLOT(onReplaced(Model * ))); 45 | 46 | auto *wrapper = new QWidget(); 47 | wrapper->setLayout(new QGridLayout()); 48 | wrapper->layout()->addWidget(modelPreview); 49 | return wrapper; 50 | } 51 | 52 | QByteArray OnexNStgData::getContent() { 53 | if (!hasParent() || model == nullptr) 54 | return content; 55 | QByteArray newContent = content.mid(0, 0x30); 56 | newContent.append(nosModelConverter.toBinary(model)); 57 | content = newContent; 58 | return content; 59 | } 60 | 61 | int OnexNStgData::saveAsFile(const QString &path, QByteArray content) { 62 | if (model == nullptr) { 63 | model = nosModelConverter.fromBinary(this->content); 64 | } 65 | QStringList obj = objConverter.toObj(model, name); 66 | 67 | if (OnexTreeItem::saveAsFile(path, obj.at(0).toLocal8Bit()) == 0) 68 | return 0; 69 | if (OnexTreeItem::saveAsFile(path.split(".").at(0) + ".mtl", obj.at(1).toLocal8Bit()) == 0) 70 | return 0; 71 | return 1; 72 | } 73 | 74 | QString OnexNStgData::getExportExtension() { 75 | return ".obj"; 76 | } 77 | 78 | int OnexNStgData::afterReplace(QByteArray content) { 79 | float scale = model->uvScale; 80 | model = objConverter.fromObj(content); 81 | model->uvScale = scale; 82 | 83 | emit replaceSignal(this->model); 84 | emit replaceInfo(generateInfos()); 85 | return 1; 86 | } 87 | 88 | void OnexNStgData::setXPosition(int index, float x, bool update) { 89 | model->objects[index].position.setX(x); 90 | if (update) 91 | emit changeSignal("O-" + QString::number(index) + "-x-Position", x); 92 | } 93 | 94 | void OnexNStgData::setYPosition(int index, float y, bool update) { 95 | model->objects[index].position.setY(y); 96 | if (update) 97 | emit changeSignal("O-" + QString::number(index) + "-y-Position", y); 98 | } 99 | 100 | void OnexNStgData::setZPosition(int index, float z, bool update) { 101 | model->objects[index].position.setZ(z); 102 | if (update) 103 | emit changeSignal("O-" + QString::number(index) + "-z-Position", z); 104 | } 105 | 106 | void OnexNStgData::setXRotation(int index, float x, bool update) { 107 | model->objects[index].rotation.setX(x); 108 | if (update) 109 | emit changeSignal("O-" + QString::number(index) + "-x-Rotation", x); 110 | } 111 | 112 | void OnexNStgData::setYRotation(int index, float y, bool update) { 113 | model->objects[index].rotation.setY(y); 114 | if (update) 115 | emit changeSignal("O-" + QString::number(index) + "-y-Rotation", y); 116 | } 117 | 118 | void OnexNStgData::setZRotation(int index, float z, bool update) { 119 | model->objects[index].rotation.setZ(z); 120 | if (update) 121 | emit changeSignal("O-" + QString::number(index) + "-z-Rotation", z); 122 | } 123 | 124 | void OnexNStgData::setWRotation(int index, float w, bool update) { 125 | model->objects[index].rotation.setW(w); 126 | if (update) 127 | emit changeSignal("O-" + QString::number(index) + "-w-Rotation", w); 128 | } 129 | 130 | void OnexNStgData::setXScale(int index, float x, bool update) { 131 | model->objects[index].scale.setX(x); 132 | if (update) 133 | emit changeSignal("O-" + QString::number(index) + "-x-Scale", x); 134 | } 135 | 136 | void OnexNStgData::setYScale(int index, float y, bool update) { 137 | model->objects[index].scale.setY(y); 138 | if (update) 139 | emit changeSignal("O-" + QString::number(index) + "-y-Scale", y); 140 | } 141 | 142 | void OnexNStgData::setZScale(int index, float z, bool update) { 143 | model->objects[index].scale.setZ(z); 144 | if (update) 145 | emit changeSignal("O-" + QString::number(index) + "-z-Scale", z); 146 | } 147 | 148 | void OnexNStgData::setTexture(int index, int texture, bool update) { 149 | model->groups[index].texture = texture; 150 | if (update) 151 | emit changeSignal("Texture-" + QString::number(index), texture); 152 | } 153 | 154 | void OnexNStgData::setUVScale(float scale, bool update) { 155 | model->uvScale = scale; 156 | if (update) 157 | emit changeSignal("UV-Scale", scale); 158 | } 159 | 160 | FileInfo *OnexNStgData::generateInfos() { 161 | FileInfo *infos = OnexTreeZlibItem::generateInfos(); 162 | if (hasParent()) { 163 | for (int i = 0; i < model->objects.size(); i++) { 164 | connect(infos->addFloatLineEdit("UV-Scale", model->uvScale), 165 | &QLineEdit::textChanged, [=](const QString &value) { setUVScale(value.toFloat()); }); 166 | connect(infos->addFloatLineEdit("O-" + QString::number(i) + "-x-Position", model->objects[i].position.x()), 167 | &QLineEdit::textChanged, [=](const QString &value) { setXPosition(i, value.toFloat()); }); 168 | connect(infos->addFloatLineEdit("O-" + QString::number(i) + "-y-Position", model->objects[i].position.y()), 169 | &QLineEdit::textChanged, [=](const QString &value) { setYPosition(i, value.toFloat()); }); 170 | connect(infos->addFloatLineEdit("O-" + QString::number(i) + "-z-Position", model->objects[i].position.z()), 171 | &QLineEdit::textChanged, [=](const QString &value) { setZPosition(i, value.toFloat()); }); 172 | connect(infos->addFloatLineEdit("O-" + QString::number(i) + "-x-Rotation", model->objects[i].rotation.x()), 173 | &QLineEdit::textChanged, [=](const QString &value) { setXRotation(i, value.toFloat()); }); 174 | connect(infos->addFloatLineEdit("O-" + QString::number(i) + "-y-Rotation", model->objects[i].rotation.y()), 175 | &QLineEdit::textChanged, [=](const QString &value) { setYRotation(i, value.toFloat()); }); 176 | connect(infos->addFloatLineEdit("O-" + QString::number(i) + "-z-Rotation", model->objects[i].rotation.z()), 177 | &QLineEdit::textChanged, [=](const QString &value) { setZRotation(i, value.toFloat()); }); 178 | connect(infos->addFloatLineEdit("O-" + QString::number(i) + "-w-Rotation", model->objects[i].rotation.w()), 179 | &QLineEdit::textChanged, [=](const QString &value) { setWRotation(i, value.toFloat()); }); 180 | connect(infos->addFloatLineEdit("O-" + QString::number(i) + "-x-Scale", model->objects[i].scale.x()), 181 | &QLineEdit::textChanged, [=](const QString &value) { setXScale(i, value.toFloat()); }); 182 | connect(infos->addFloatLineEdit("O-" + QString::number(i) + "-y-Scale", model->objects[i].scale.y()), 183 | &QLineEdit::textChanged, [=](const QString &value) { setYScale(i, value.toFloat()); }); 184 | connect(infos->addFloatLineEdit("O-" + QString::number(i) + "-z-Scale", model->objects[i].scale.z()), 185 | &QLineEdit::textChanged, [=](const QString &value) { setZScale(i, value.toFloat()); }); 186 | } 187 | for (int i = 0; i < model->groups.size(); i++) { 188 | connect(infos->addIntLineEdit("Texture-" + QString::number(i), model->groups[i].texture), 189 | &QLineEdit::textChanged, [=](const QString &newValue) { setTexture(i, newValue.toInt()); }); 190 | } 191 | } 192 | 193 | return infos; 194 | } 195 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNStgData.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXNSTGDATA_H 2 | #define ONEXNSTGDATA_H 3 | 4 | #include "../../Openers/NosZlibOpener.h" 5 | #include "OnexTreeZlibItem.h" 6 | #include "../../Converters/NosModelConverter.h" 7 | #include "../../Converters/ObjConverter.h" 8 | #include 9 | #include 10 | 11 | class OnexNStgData : public OnexTreeZlibItem { 12 | Q_OBJECT 13 | public: 14 | OnexNStgData(QString name, QByteArray content, NosZlibOpener *opener, int id = -1, 15 | int creationDate = 0, bool compressed = false); 16 | OnexNStgData(QJsonObject jo, NosZlibOpener *opener, const QString &directory); 17 | ~OnexNStgData() override; 18 | QWidget *getPreview() override; 19 | QByteArray getContent() override; 20 | int saveAsFile(const QString &path, QByteArray content = QByteArray()) override; 21 | QString getExportExtension() override; 22 | 23 | private slots: 24 | int afterReplace(QByteArray content) override; 25 | void setXPosition(int index, float x, bool update = false); 26 | void setYPosition(int index, float y, bool update = false); 27 | void setZPosition(int index, float z, bool update = false); 28 | void setXRotation(int index, float x, bool update = false); 29 | void setYRotation(int index, float y, bool update = false); 30 | void setZRotation(int index, float z, bool update = false); 31 | void setWRotation(int index, float w, bool update = false); 32 | void setXScale(int index, float x, bool update = false); 33 | void setYScale(int index, float y, bool update = false); 34 | void setZScale(int index, float z, bool update = false); 35 | void setTexture(int index, int texture, bool update = false); 36 | void setUVScale(float scale, bool update = false); 37 | signals: 38 | void replaceSignal(Model *newModel); 39 | protected: 40 | static ObjConverter objConverter; 41 | static NosModelConverter nosModelConverter; 42 | Model *model; 43 | FileInfo *generateInfos() override; 44 | }; 45 | 46 | #endif // ONEXNSTGDATA_H 47 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNStpData.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexNStpData.h" 2 | #include "OnexNStpMipMap.h" 3 | 4 | OnexNStpData::OnexNStpData(const QString &name, QByteArray content, NosZlibOpener *opener, 5 | 6 | int id, int creationDate, bool compressed) 7 | : OnexTreeImage(name, opener, content, id, creationDate, compressed) { 8 | if (content.isEmpty()) 9 | this->content = QByteArrayLiteral("\x00\x00\x00\x00\x00\x00\x00\x00"); 10 | if (id == -1) 11 | return; 12 | if (getFileAmount() > 0) 13 | generateMipMap(true); 14 | } 15 | 16 | OnexNStpData::OnexNStpData(QJsonObject jo, NosZlibOpener *opener, const QString &directory) : OnexTreeImage(jo["ID"].toString(), opener) { 17 | this->content = QByteArrayLiteral("\x00\x00\x00\x00\x00\x00\x00\x00"); 18 | setId(jo["ID"].toInt(), true); 19 | setCreationDate(jo["Date"].toString(), true); 20 | setCompressed(jo["isCompressed"].toBool(), true); 21 | setFormat(jo["Format"].toInt()); 22 | setSmoothScaling(jo["SmoothScaling"].toBool(), true); 23 | setUnknownValue(jo["Unknown"].toBool(), true); 24 | bool mip = jo["MipMap"].toBool(); 25 | 26 | if (mip) { 27 | int amount = 0; 28 | int x = getResolution().x; 29 | while (x >= 2) { 30 | x /= 2; 31 | amount++; 32 | } 33 | setFileAmount(amount); 34 | } 35 | 36 | if (!jo["path"].toString().isEmpty()) 37 | onReplace(directory + jo["path"].toString()); 38 | 39 | if (getFileAmount() > 0) { 40 | ImageResolution res = getResolution(); 41 | int format = getFormat(); 42 | for (auto &&child : jo["content"].toArray()) { 43 | OnexTreeItem *item = new OnexNStpMipMap(name + "_" + QString::number(res.x) + "x" + QString::number(res.y), 44 | QByteArray(), res.x, res.y, format, (NosZlibOpener *) opener, id, 45 | creationDate, compressed); 46 | this->addChild(item); 47 | item->onReplace(directory + child.toObject()["path"].toString()); 48 | res.x /= 2; 49 | res.y /= 2; 50 | } 51 | if (childCount() == 0) 52 | generateMipMap(true); 53 | } 54 | 55 | } 56 | 57 | OnexNStpData::~OnexNStpData() = default; 58 | 59 | QByteArray OnexNStpData::getContent() { 60 | int amount = childCount(); 61 | if (!hasParent() || amount <= 0) 62 | return content; 63 | QByteArray contentArray = content.mid(0, 8); 64 | for (int i = 0; i < amount; i++) { 65 | auto *currentItem = dynamic_cast(this->child(i)); 66 | contentArray.push_back(currentItem->getContent()); 67 | } 68 | this->content = contentArray; 69 | return content; 70 | } 71 | 72 | QImage OnexNStpData::getImage() { 73 | if (childCount() > 0) 74 | return dynamic_cast(this->child(0))->getImage(); 75 | ImageResolution resolution = this->getResolution(); 76 | int format = this->getFormat(); 77 | if (format == 0) 78 | return imageConverter->convertGBAR4444(content, resolution.x, resolution.y, 8); 79 | else if (format == 1) 80 | return imageConverter->convertARGB555(content, resolution.x, resolution.y, 8); 81 | else if (format == 2) 82 | return imageConverter->convertBGRA8888(content, resolution.x, resolution.y, 8); 83 | else if (format == 3 || format == 4) 84 | return imageConverter->convertGrayscale(content, resolution.x, resolution.y, 8); 85 | else { 86 | qDebug().noquote().nospace() << "Unknown format! (" << format << ")"; 87 | return QImage(resolution.x, resolution.y, QImage::Format_Invalid); 88 | } 89 | } 90 | 91 | ImageResolution OnexNStpData::getResolution() { 92 | int x = opener->getLittleEndianConverter()->fromShort(content.mid(0, 2)); 93 | int y = opener->getLittleEndianConverter()->fromShort(content.mid(2, 2)); 94 | return ImageResolution{x, y}; 95 | } 96 | 97 | int OnexNStpData::getFormat() { 98 | return content.at(4); 99 | } 100 | 101 | bool OnexNStpData::getSmoothScaling() { 102 | return content.at(5); 103 | } 104 | 105 | bool OnexNStpData::getUnknownValue() { 106 | return content.at(6); 107 | } 108 | 109 | int OnexNStpData::getFileAmount() { 110 | return content.at(7); 111 | } 112 | 113 | int OnexNStpData::afterReplace(QImage image) { 114 | if (image.isNull()) { 115 | emit OnexTreeImage::replaceSignal(this->getImage()); 116 | return 0; 117 | } 118 | 119 | qDebug() << getFileAmount(); 120 | int format = this->getFormat(); 121 | if (format < 0 || format > 4) { 122 | QMessageBox::critical(nullptr, "Woops", "Format of " + name + "is not supported!"); 123 | return 0; 124 | } 125 | QByteArray newContent; 126 | newContent.push_back(opener->getLittleEndianConverter()->toShort(image.width())); 127 | newContent.push_back(opener->getLittleEndianConverter()->toShort(image.height())); 128 | newContent.push_back(content.mid(4, 4)); 129 | if (format == 0) 130 | newContent.push_back(imageConverter->toGBAR4444(image)); 131 | else if (format == 1) 132 | newContent.push_back(imageConverter->toARGB555(image)); 133 | else if (format == 2) 134 | newContent.push_back(imageConverter->toBGRA8888(image)); 135 | else if (format == 3 || format == 4) 136 | newContent.push_back(imageConverter->toGrayscale(image)); 137 | setContent(newContent); 138 | setWidth(image.width(), true); 139 | setHeight(image.height(), true); 140 | qDebug() << getFileAmount(); 141 | generateMipMap(getFileAmount() != 0); 142 | 143 | 144 | emit OnexTreeImage::replaceSignal(this->getImage()); 145 | emit replaceInfo(generateInfos()); 146 | return 1; 147 | } 148 | 149 | void OnexNStpData::setWidth(int width, bool update) { 150 | content.replace(0, 2, opener->getLittleEndianConverter()->toShort(width)); 151 | if (update) 152 | emit changeSignal("Width", width); 153 | } 154 | 155 | void OnexNStpData::setHeight(int height, bool update) { 156 | content.replace(2, 2, opener->getLittleEndianConverter()->toShort(height)); 157 | if (update) 158 | emit changeSignal("Height", height); 159 | } 160 | 161 | void OnexNStpData::setFormat(uint8_t format, bool update) { 162 | content[4] = format; 163 | if (update) 164 | emit changeSignal("Format", format); 165 | for (int i = 0; i < childCount(); i++) { 166 | auto *item = static_cast(child(i)); 167 | item->setFormat(format, update); 168 | } 169 | } 170 | 171 | void OnexNStpData::setSmoothScaling(bool smooth, bool update) { 172 | content[5] = smooth; 173 | if (update) 174 | emit changeSignal("SmoothScaling", smooth); 175 | } 176 | 177 | void OnexNStpData::setUnknownValue(bool unkown, bool update) { 178 | content[6] = unkown; 179 | if (update) 180 | emit changeSignal("Unknown", unkown); 181 | } 182 | 183 | void OnexNStpData::setFileAmount(uint8_t value, bool update) { 184 | content[7] = value; 185 | if (update) 186 | emit changeSignal("MipMap", value != 0); 187 | } 188 | 189 | FileInfo *OnexNStpData::generateInfos() { 190 | FileInfo *infos = OnexTreeImage::generateInfos(); 191 | if (hasParent()) { 192 | const QStringList formats = {"ARGB4444", "ARGB1555 ", "ARGB8888", "Grayscale_1", "Grayscale_2"}; 193 | connect(infos->addSelection("Format", formats, getFormat()), QOverload::of(&QComboBox::currentIndexChanged), 194 | [=](const int &value) { setFormat(value); }); 195 | connect(infos->addCheckBox("SmoothScaling", getSmoothScaling()), &QCheckBox::clicked, 196 | [=](const bool value) { setSmoothScaling(value); }); 197 | connect(infos->addCheckBox("Unknown", getUnknownValue()), &QCheckBox::clicked, 198 | [=](const bool value) { setUnknownValue(value); }); 199 | connect(infos->addCheckBox("MipMap", getFileAmount() != 0), &QCheckBox::clicked, 200 | [=](const bool value) { generateMipMap(value); }); 201 | } 202 | return infos; 203 | } 204 | 205 | void OnexNStpData::generateMipMap(bool generate) { 206 | this->takeChildren(); 207 | 208 | if (!generate) { 209 | setFileAmount(0); 210 | return; 211 | } 212 | 213 | int amount = 0; 214 | int x = getResolution().x; 215 | while (x >= 2) { 216 | x /= 2; 217 | amount++; 218 | } 219 | 220 | ImageResolution res = getResolution(); 221 | int format = getFormat(); 222 | int offset = 8; 223 | for (int i = 0; i < amount; i++) { 224 | int nextOffset = offset; 225 | if (format == 0 || format == 1) 226 | nextOffset += res.x * res.y * 2; 227 | else if (format == 2) 228 | nextOffset += res.x * res.y * 4; 229 | else 230 | nextOffset += res.x * res.y; 231 | 232 | if (nextOffset > content.size()) { 233 | QImage subImage = getImage(); 234 | subImage = subImage.scaled(res.x, res.y, Qt::KeepAspectRatio, Qt::SmoothTransformation); 235 | QByteArray subContent; 236 | if (format == 0) 237 | subContent = imageConverter->toGBAR4444(subImage); 238 | else if (format == 1) 239 | subContent = imageConverter->toARGB555(subImage); 240 | else if (format == 2) 241 | subContent = imageConverter->toBGRA8888(subImage); 242 | else if (format == 3 || format == 4) 243 | subContent = imageConverter->toGrayscale(subImage); 244 | 245 | this->addChild(new OnexNStpMipMap(name + "_" + QString::number(res.x) + "x" + QString::number(res.y), 246 | subContent, res.x, res.y, format, (NosZlibOpener *) opener, id, creationDate, compressed)); 247 | } else { 248 | this->addChild(new OnexNStpMipMap(name + "_" + QString::number(res.x) + "x" + QString::number(res.y), 249 | content.mid(offset, nextOffset - offset), res.x, res.y, format, (NosZlibOpener *) opener, id, 250 | creationDate, compressed)); 251 | } 252 | offset = nextOffset; 253 | res.x /= 2; 254 | res.y /= 2; 255 | } 256 | 257 | setFileAmount(amount); 258 | } 259 | 260 | void OnexNStpData::setName(QString name) { 261 | OnexTreeZlibItem::setName(name); 262 | QList childList = takeChildren(); 263 | for (int i = 0; i < childList.size(); i++) { 264 | auto *item = static_cast(childList.at(i)); 265 | item->OnexTreeItem::setName(name + "_" + QString::number(item->getWidth()) + "x" + QString::number(item->getHeight())); 266 | } 267 | addChildren(childList); 268 | } 269 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNStpData.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXNSTPDATA_H 2 | #define ONEXNSTPDATA_H 3 | 4 | #include "OnexTreeImage.h" 5 | 6 | class OnexNStpData : public OnexTreeImage { 7 | Q_OBJECT 8 | public: 9 | OnexNStpData(const QString &name, QByteArray content, NosZlibOpener *opener, 10 | int id = -1, int creationDate = 0, bool compressed = false); 11 | OnexNStpData(QJsonObject jo, NosZlibOpener *opener, const QString &directory); 12 | ~OnexNStpData() override; 13 | QByteArray getContent() override; 14 | QImage getImage() override; 15 | ImageResolution getResolution() override; 16 | int getFormat(); 17 | bool getSmoothScaling(); 18 | bool getUnknownValue(); 19 | int getFileAmount(); 20 | public slots: 21 | void setName(QString name); 22 | int afterReplace(QImage image) override; 23 | void setWidth(int width, bool update = false) override; 24 | void setHeight(int height, bool update = false) override; 25 | void setFormat(uint8_t format, bool update = false); 26 | void setSmoothScaling(bool smooth, bool update = false); 27 | void setUnknownValue(bool unkown, bool update = false); 28 | void setFileAmount(uint8_t format, bool update = false); 29 | protected: 30 | FileInfo *generateInfos() override; 31 | void generateMipMap(bool generate); 32 | }; 33 | 34 | #endif // ONEXNSTPDATA_H 35 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNStpMipMap.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexNStpMipMap.h" 2 | #include "OnexNStpData.h" 3 | 4 | OnexNStpMipMap::OnexNStpMipMap(QString name, QByteArray content, int width, int height, int format, NosZlibOpener *opener, 5 | int id, int creationDate, bool compressed) 6 | : OnexTreeImage(name, opener, content, id, creationDate, compressed), width(width), height(height), format(format) { 7 | setFlags(this->flags() & (~Qt::ItemIsEditable)); 8 | } 9 | 10 | OnexNStpMipMap::~OnexNStpMipMap() = default; 11 | 12 | int OnexNStpMipMap::onReplace(QString directory) { 13 | QString path = getCorrectPath(directory); 14 | QFile file(path); 15 | if (file.open(QIODevice::ReadOnly)) 16 | return OnexTreeImage::afterReplace(file.readAll()); 17 | else { 18 | auto *parent = static_cast(QTreeWidgetItem::parent()); 19 | QString parentPath = path.mid(0, path.lastIndexOf("/") + 1) + parent->getName() + ".png"; 20 | QFile pfile(parentPath); 21 | if (pfile.open(QIODevice::ReadOnly)) 22 | pfile.close(); 23 | else 24 | QMessageBox::critical(nullptr, "Woops", "Couldn't open " + path); 25 | return 0; 26 | } 27 | } 28 | 29 | QImage OnexNStpMipMap::getImage() { 30 | ImageResolution resolution = this->getResolution(); 31 | int format = this->getFormat(); 32 | if (format == 0) 33 | return imageConverter->convertGBAR4444(content, resolution.x, resolution.y); 34 | else if (format == 1) 35 | return imageConverter->convertARGB555(content, resolution.x, resolution.y); 36 | else if (format == 2) 37 | return imageConverter->convertBGRA8888(content, resolution.x, resolution.y); 38 | else if (format == 3 || format == 4) 39 | return imageConverter->convertGrayscale(content, resolution.x, resolution.y); 40 | else { 41 | qDebug().noquote().nospace() << "Unknown format! (" << format << ")"; 42 | return QImage(resolution.x, resolution.y, QImage::Format_Invalid); 43 | } 44 | } 45 | 46 | ImageResolution OnexNStpMipMap::getResolution() { 47 | return ImageResolution{this->width, this->height}; 48 | } 49 | 50 | int OnexNStpMipMap::getWidth() { 51 | return width; 52 | } 53 | 54 | int OnexNStpMipMap::getHeight() { 55 | return height; 56 | } 57 | 58 | int OnexNStpMipMap::getFormat() { 59 | return format; 60 | } 61 | 62 | int OnexNStpMipMap::afterReplace(QImage image) { 63 | int format = this->getFormat(); 64 | if (format < 0 || format > 4) { 65 | QMessageBox::critical(nullptr, "Woops", "Format of " + name + "is not supported!"); 66 | return 0; 67 | } 68 | QByteArray newContent; 69 | if (format == 0) 70 | newContent.push_back(imageConverter->toGBAR4444(image)); 71 | else if (format == 1) 72 | newContent.push_back(imageConverter->toARGB555(image)); 73 | else if (format == 2) 74 | newContent.push_back(imageConverter->toBGRA8888(image)); 75 | else if (format == 3 || format == 4) 76 | newContent.push_back(imageConverter->toGrayscale(image)); 77 | setContent(newContent); 78 | setWidth(image.width(), true); 79 | setHeight(image.height(), true); 80 | 81 | emit OnexTreeImage::replaceSignal(this->getImage()); 82 | emit replaceInfo(generateInfos()); 83 | return 1; 84 | } 85 | 86 | void OnexNStpMipMap::setWidth(int width, bool update) { 87 | this->width = width; 88 | if (update) 89 | emit changeSignal("Width", width); 90 | } 91 | 92 | void OnexNStpMipMap::setHeight(int height, bool update) { 93 | this->height = height; 94 | if (update) 95 | emit changeSignal("Height", height); 96 | } 97 | 98 | void OnexNStpMipMap::setFormat(uint8_t format, bool update) { 99 | this->format = format; 100 | if (update) 101 | emit changeSignal("Format", format); 102 | } 103 | 104 | FileInfo *OnexNStpMipMap::generateInfos() { 105 | return OnexTreeItem::generateInfos(); 106 | } 107 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexNStpMipMap.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXNSTPMAPMAP_H 2 | #define ONEXNSTPMAPMAP_H 3 | 4 | #include "OnexTreeImage.h" 5 | 6 | class OnexNStpMipMap : public OnexTreeImage { 7 | Q_OBJECT 8 | public: 9 | OnexNStpMipMap(QString name, QByteArray content, int width, int height, int format, NosZlibOpener *opener, 10 | int id = -1, int creationDate = 0, bool compressed = false); 11 | OnexNStpMipMap(QJsonObject jo, NosZlibOpener *opener, const QString &directory); 12 | ~OnexNStpMipMap() override; 13 | int onReplace(QString directory) override; 14 | QImage getImage() override; 15 | ImageResolution getResolution() override; 16 | int getWidth(); 17 | int getHeight(); 18 | int getFormat(); 19 | public slots: 20 | int afterReplace(QImage image) override; 21 | void setWidth(int width, bool update = false) override; 22 | void setHeight(int height, bool update = false) override; 23 | void setFormat(uint8_t format, bool update = false); 24 | protected: 25 | int width; 26 | int height; 27 | int format; 28 | FileInfo *generateInfos() override; 29 | }; 30 | 31 | #endif // ONEXNSTPMAPMAP_H 32 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexTreeImage.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexTreeImage.h" 2 | 3 | ImageConverter *OnexTreeImage::imageConverter; 4 | 5 | OnexTreeImage::OnexTreeImage(QString name, NosZlibOpener *opener, QByteArray content, int id, int creationDate, bool compressed) 6 | : OnexTreeZlibItem(name, opener, content, id, creationDate, compressed) { 7 | if (imageConverter == nullptr) 8 | imageConverter = new ImageConverter(opener->getLittleEndianConverter()); 9 | } 10 | 11 | QWidget *OnexTreeImage::getPreview() { 12 | if (!hasParent()) 13 | return nullptr; 14 | auto *imagePreview = new SingleImagePreview(this->getImage()); 15 | connect(this, SIGNAL(replaceSignal(QImage)), imagePreview, SLOT(onReplaced(QImage))); 16 | return imagePreview; 17 | } 18 | 19 | bool OnexTreeImage::isEmpty() { 20 | ImageResolution ir = getResolution(); 21 | return ir.x == 0 && ir.y == 0; 22 | } 23 | 24 | QString OnexTreeImage::getExportExtension() { 25 | return ".png"; 26 | } 27 | 28 | int OnexTreeImage::onReplaceRaw(QString directory) { 29 | int ret = OnexTreeItem::onReplaceRaw(directory); 30 | emit replaceSignal(getImage()); 31 | emit replaceInfo(generateInfos()); 32 | return ret; 33 | } 34 | 35 | int OnexTreeImage::afterReplace(QByteArray content) { 36 | if (content.isEmpty() && childCount() > 0) 37 | return afterReplace(QImage()); 38 | QImage image = QImage::fromData(content); 39 | if (image.isNull() && this->getResolution().x != 0 && this->getResolution().y != 0) { 40 | QMessageBox::critical(nullptr, "Woops", "Couldn't read image " + name); 41 | return 0; 42 | } 43 | if (!hasGoodResolution(image.width(), image.height()) && !(this->getResolution().x == 0 && this->getResolution().y == 0)) { 44 | QMessageBox::StandardButton reply = QMessageBox::question( 45 | nullptr, "Resolution changed", 46 | "The resolution of the image " + name + " doesn't match!\nDo you want to replace it anyway?"); 47 | if (reply == QMessageBox::No) 48 | return 0; 49 | } 50 | return afterReplace(image); 51 | } 52 | 53 | FileInfo *OnexTreeImage::generateInfos() { 54 | FileInfo *infos = OnexTreeZlibItem::generateInfos(); 55 | if (hasParent()) { 56 | ImageResolution ir = getResolution(); 57 | infos->addIntLineEdit("Width", ir.x)->setDisabled(true); 58 | infos->addIntLineEdit("Height", ir.y)->setDisabled(true); 59 | } 60 | return infos; 61 | } 62 | 63 | int OnexTreeImage::saveAsFile(const QString &path, QByteArray content) { 64 | if (this->getImage().save(path, "PNG", 100)) 65 | return 1; 66 | else if (this->getResolution().x == 0 || this->getResolution().y == 0) { 67 | QFile file(path); 68 | if (file.open(QIODevice::WriteOnly)) { 69 | file.close(); 70 | return 1; 71 | } 72 | } 73 | return 0; 74 | } 75 | 76 | bool OnexTreeImage::hasGoodResolution(int x, int y) { 77 | ImageResolution currentResolution = this->getResolution(); 78 | return (x == currentResolution.x && y == currentResolution.y); 79 | } 80 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexTreeImage.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXTREEIMAGE_H 2 | #define ONEXTREEIMAGE_H 3 | 4 | #include "OnexTreeZlibItem.h" 5 | #include "../Previews/SingleImagePreview.h" 6 | #include "../../Converters/ImageConverter.h" 7 | 8 | struct ImageResolution { 9 | int x; 10 | int y; 11 | }; 12 | 13 | class OnexTreeImage : public OnexTreeZlibItem { 14 | Q_OBJECT 15 | public: 16 | OnexTreeImage(QString name, NosZlibOpener *opener, QByteArray content = QByteArray(), int id = -1, int creationDate = 0, bool compressed = false); 17 | QWidget *getPreview() override; 18 | bool isEmpty() override; 19 | QString getExportExtension() override; 20 | virtual QImage getImage() = 0; 21 | virtual ImageResolution getResolution() = 0; 22 | public slots: 23 | int onReplaceRaw(QString directory) override; 24 | int afterReplace(QByteArray content) override; 25 | virtual int afterReplace(QImage image) = 0; 26 | virtual void setWidth(int width, bool update = false) = 0; 27 | virtual void setHeight(int height, bool update = false) = 0; 28 | signals: 29 | void replaceSignal(QImage newImage); 30 | protected: 31 | static ImageConverter *imageConverter; 32 | FileInfo *generateInfos() override; 33 | int saveAsFile(const QString &path, QByteArray content = QByteArray()) override; 34 | virtual bool hasGoodResolution(int x, int y); 35 | }; 36 | 37 | #endif // ONEXTREEIMAGE_H 38 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexTreeText.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexTreeText.h" 2 | #include "../../Openers/NosTextOpener.h" 3 | #include "../Previews/SingleTextFilePreview.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | OnexTreeText::OnexTreeText(const QString &name, NosTextOpener *opener, int fileNumber, int isCompressed, QByteArray content) 10 | : OnexTreeItem(name, opener, content), fileNumber(fileNumber), isCompressed(isCompressed) { 11 | } 12 | 13 | OnexTreeText::OnexTreeText(const QString &name, NosTextOpener *opener, const QString &time) : OnexTreeItem(name, opener, QByteArray()), fileNumber(0), 14 | isCompressed(0), time(time) { 15 | } 16 | 17 | OnexTreeText::OnexTreeText(QJsonObject jo, NosTextOpener *opener, const QString &directory) : OnexTreeItem(jo["Name"].toString(), opener) { 18 | setFileNumber(jo["Filenumber"].toInt(), true); 19 | setIsCompressed(jo["isCompressed"].toBool()); 20 | onReplace(directory + jo["path"].toString()); 21 | } 22 | 23 | OnexTreeText::~OnexTreeText() = default; 24 | 25 | QWidget *OnexTreeText::getPreview() { 26 | if (!hasParent()) 27 | return nullptr; 28 | auto *textPreview = new SingleTextFilePreview(content, getEncoding()); 29 | connect(this, SIGNAL(replaceSignal(QByteArray)), textPreview, SLOT(onReplaced(QByteArray))); 30 | return textPreview; 31 | } 32 | 33 | QString OnexTreeText::getExportExtension() { 34 | QStringList split = name.split(".", QString::SplitBehavior::SkipEmptyParts); 35 | if (split.size() > 1) 36 | return split.at(split.size() - 1); 37 | return ""; 38 | } 39 | 40 | int OnexTreeText::getFileNumber() const { 41 | return fileNumber; 42 | } 43 | 44 | int OnexTreeText::getIsCompressed() const { 45 | return isCompressed; 46 | } 47 | 48 | int OnexTreeText::afterReplace(QByteArray content) { 49 | this->setContent(content); 50 | emit replaceSignal(this->getContent()); 51 | emit replaceInfo(generateInfos()); 52 | return 1; 53 | } 54 | 55 | void OnexTreeText::setFileNumber(int fileNumber, bool update) { 56 | this->fileNumber = fileNumber; 57 | if (update) 58 | emit changeSignal("Filenumber", fileNumber); 59 | } 60 | 61 | void OnexTreeText::setIsCompressed(bool isCompressed, bool update) { 62 | this->isCompressed = isCompressed; 63 | if (update) 64 | emit changeSignal("isCompressed", isCompressed); 65 | } 66 | 67 | void OnexTreeText::setTime(QString time, bool update) { 68 | this->time = time; 69 | if (update) 70 | emit changeSignal("Last Edit", time); 71 | } 72 | 73 | FileInfo *OnexTreeText::generateInfos() { 74 | FileInfo *infos = OnexTreeItem::generateInfos(); 75 | if (!hasParent()) { 76 | infos->addStringLineEdit("Last Edit", time)->setEnabled(false); 77 | } else { 78 | infos = OnexTreeItem::generateInfos(); 79 | connect(infos->addStringLineEdit("Name", text(0)), &QLineEdit::textChanged, 80 | [=](const QString &value) { setText(0, value); }); 81 | connect(infos->addIntLineEdit("Filenumber", getFileNumber()), &QLineEdit::textChanged, 82 | [=](const QString &value) { setFileNumber(value.toInt()); }); 83 | connect(infos->addCheckBox("isCompressed", getIsCompressed()), &QCheckBox::clicked, [=](const bool value) { setIsCompressed(value); }); 84 | } 85 | connect(this, SIGNAL(changeSignal(QString, QString)), infos, SLOT(update(QString, QString))); 86 | connect(this, SIGNAL(changeSignal(QString, int)), infos, SLOT(update(QString, int))); 87 | connect(this, SIGNAL(changeSignal(QString, float)), infos, SLOT(update(QString, float))); 88 | connect(this, SIGNAL(changeSignal(QString, bool)), infos, SLOT(update(QString, bool))); 89 | return infos; 90 | } 91 | 92 | QString OnexTreeText::getEncoding() { 93 | QRegExp rx = QRegExp("^_code_\\w{2}_\\w*\\.txt$"); 94 | int match = rx.indexIn(getName()); 95 | if (match == -1) 96 | return "EUC-KR"; 97 | QString region = getName().mid(6, 2); 98 | if (region == "de" || region == "pl" || region == "it" || region == "cz") 99 | return "Windows-1250"; 100 | else if (region == "ru") 101 | return "Windows-1251"; 102 | else if (region == "uk" || region == "fr" || region == "es") 103 | return "Windows-1252"; 104 | else if (region == "tr") 105 | return "Windows-1254"; 106 | else if (region == "hk" || region == "tw") 107 | return "Big5"; 108 | return "Windows-1250"; 109 | } 110 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexTreeText.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXTREETEXT_H 2 | #define ONEXTREETEXT_H 3 | 4 | #include "../OnexTreeItem.h" 5 | 6 | class NosTextOpener; 7 | 8 | class OnexTreeText : public OnexTreeItem { 9 | Q_OBJECT 10 | public: 11 | OnexTreeText(const QString &name, NosTextOpener *opener, int fileNumber = 0, int isCompressed = 0, 12 | QByteArray content = QByteArray()); 13 | OnexTreeText(const QString &name, NosTextOpener *opener, const QString &time); 14 | OnexTreeText(QJsonObject jo, NosTextOpener *opener, const QString &directory); 15 | ~OnexTreeText() override; 16 | QWidget *getPreview() override; 17 | QString getExportExtension() override; 18 | int getFileNumber() const; 19 | int getIsCompressed() const; 20 | public slots: 21 | int afterReplace(QByteArray content) override; 22 | void setFileNumber(int number, bool update = false); 23 | void setIsCompressed(bool isCompressed, bool update = false); 24 | void setTime(QString time, bool update = false); 25 | signals: 26 | void replaceSignal(QByteArray text); 27 | protected: 28 | int fileNumber; 29 | int isCompressed; 30 | QString time; 31 | FileInfo *generateInfos() override; 32 | QString getEncoding(); 33 | }; 34 | 35 | #endif // ONEXTREETEXT_H 36 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexTreeZlibItem.cpp: -------------------------------------------------------------------------------- 1 | #include "OnexTreeZlibItem.h" 2 | #include 3 | 4 | OnexTreeZlibItem::OnexTreeZlibItem(const QString &name, NosZlibOpener *opener, QByteArray content, int id, int creationDate, bool compressed) 5 | : OnexTreeItem(name, opener, content), id(id), creationDate(creationDate), 6 | compressed(compressed) { 7 | if (creationDate == 0) 8 | setCreationDate(QDate::currentDate().toString("dd/MM/yyyy"), true); 9 | } 10 | 11 | 12 | OnexTreeZlibItem::~OnexTreeZlibItem() = default; 13 | 14 | int OnexTreeZlibItem::getId() { 15 | return id; 16 | } 17 | 18 | int OnexTreeZlibItem::getCreationDate() { 19 | return creationDate; 20 | } 21 | 22 | QString OnexTreeZlibItem::getDateAsString() { 23 | int year = (getCreationDate() & 0xFFFF0000) >> 0x10; 24 | int month = (getCreationDate() & 0xFF00) >> 0x08; 25 | int day = getCreationDate() & 0xFF; 26 | return QString("%1/%2/%3").arg(day, 2, 16, QChar('0')).arg(month, 2, 16, QChar('0')).arg(year, 4, 16, QChar('0')); 27 | } 28 | 29 | bool OnexTreeZlibItem::isCompressed() { 30 | return compressed; 31 | } 32 | 33 | void OnexTreeZlibItem::setName(QString name) { 34 | OnexTreeItem::setName(name); 35 | setId(name.toInt(), true); 36 | } 37 | 38 | void OnexTreeZlibItem::setId(int id, bool update) { 39 | this->id = id; 40 | OnexTreeItem::setName(QString::number(id)); 41 | if (update) 42 | emit changeSignal("ID", id); 43 | } 44 | 45 | void OnexTreeZlibItem::setCreationDate(const QString &date, bool update) { 46 | QStringList parts = date.split("/", QString::SplitBehavior::SkipEmptyParts); 47 | if (parts.size() != 3) 48 | this->creationDate = 0; 49 | else { 50 | int year = parts[2].toInt(nullptr, 16) << 0x10; 51 | int month = parts[1].toInt(nullptr, 16) << 0x08; 52 | int day = parts[0].toInt(nullptr, 16); 53 | this->creationDate = year + month + day; 54 | } 55 | if (update) 56 | emit changeSignal("Date", getDateAsString()); 57 | } 58 | 59 | void OnexTreeZlibItem::setCompressed(bool compressed, bool update) { 60 | this->compressed = compressed; 61 | if (update) 62 | emit changeSignal("isCompressed", compressed); 63 | } 64 | 65 | FileInfo *OnexTreeZlibItem::generateInfos() { 66 | auto *infos = OnexTreeItem::generateInfos(); 67 | if (getId() == -1) { 68 | infos->addStringLineEdit("Header", QString::fromUtf8(getContent(), getContent().size()))->setEnabled(false); 69 | } else { 70 | connect(infos->addIntLineEdit("ID", getId()), &QLineEdit::textChanged, 71 | [=](const QString &value) { setId(value.toInt()); }); 72 | connect(infos->addStringLineEdit("Date", getDateAsString()), &QLineEdit::textChanged, 73 | [=](const QString &value) { setCreationDate(value); }); 74 | connect(infos->addCheckBox("isCompressed", isCompressed()), &QCheckBox::clicked, 75 | [=](const bool value) { setCompressed(value); }); 76 | } 77 | return infos; 78 | } 79 | -------------------------------------------------------------------------------- /Source/Ui/TreeItems/OnexTreeZlibItem.h: -------------------------------------------------------------------------------- 1 | #ifndef ONEXTREEZLIBITEM_H 2 | #define ONEXTREEZLIBITEM_H 3 | 4 | #include "../../Openers/NosZlibOpener.h" 5 | #include "../OnexTreeItem.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class OnexTreeZlibItem : public OnexTreeItem { 12 | Q_OBJECT 13 | public: 14 | OnexTreeZlibItem(const QString &name, NosZlibOpener *opener, QByteArray content = QByteArray(), int id = -1, int creationDate = 0, bool compressed = false); 15 | ~OnexTreeZlibItem() override; 16 | int getId(); 17 | int getCreationDate(); 18 | bool isCompressed(); 19 | public slots: 20 | void setName(QString name) override; 21 | virtual void setId(int id, bool update = false); 22 | void setCreationDate(const QString &date, bool update = false); 23 | void setCompressed(bool compressed, bool update = false); 24 | protected: 25 | int id; 26 | int creationDate; 27 | bool compressed; 28 | FileInfo *generateInfos() override; 29 | QString getDateAsString(); 30 | }; 31 | 32 | #endif // ONEXTREEZLIBITEM_H 33 | -------------------------------------------------------------------------------- /Source/main.cpp: -------------------------------------------------------------------------------- 1 | #include "MainWindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) { 5 | QApplication a(argc, argv); 6 | MainWindow w; 7 | w.show(); 8 | return a.exec(); 9 | } 10 | -------------------------------------------------------------------------------- /Source/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1200 10 | 699 11 | 12 | 13 | 14 | 15 | 500 16 | 300 17 | 18 | 19 | 20 | false 21 | 22 | 23 | OnexExplorer [Beta] 24 | 25 | 26 | 27 | :/resources/oxe_icon_trans.ico 28 | :/resources/oxe_icon_trans.ico:/resources/oxe_icon_trans.ico 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 224 38 | 16777215 39 | 40 | 41 | 42 | Search... example1;example2;example3 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 26 53 | 75 54 | true 55 | 56 | 57 | 58 | + 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 26 67 | 75 68 | true 69 | 70 | 71 | 72 | - 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | <html><head/><body><p><a href="https://discord.gg/t73Ayca"><span 84 | style=" font-size:18pt; font-weight:600; text-decoration: underline; 85 | color:#000000;">Custom Client Modding Discord</span></a></p></body></html> 86 | 87 | 88 | 89 | Qt::AlignCenter 90 | 91 | 92 | true 93 | 94 | 95 | 96 | 97 | 98 | 99 | <html><head/><body><p><a href="https://ko-fi.com/Pumbaa"><img 100 | src=":/resources/kofi4.png" height="36"/></a></p></body></html> 101 | 102 | 103 | 104 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 105 | 106 | 107 | true 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 0 130 | 0 131 | 132 | 133 | 134 | 135 | 224 136 | 16777215 137 | 138 | 139 | 140 | QAbstractItemView::NoEditTriggers 141 | 142 | 143 | QAbstractItemView::ExtendedSelection 144 | 145 | 146 | false 147 | 148 | 149 | true 150 | 151 | 152 | false 153 | 154 | 155 | 156 | 1 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 0 167 | 0 168 | 1200 169 | 21 170 | 171 | 172 | 173 | 174 | File 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | false 190 | 191 | 192 | ? 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | Tools 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | Open 220 | 221 | 222 | Ctrl+O 223 | 224 | 225 | 226 | 227 | true 228 | 229 | 230 | Save as... 231 | 232 | 233 | Ctrl+S 234 | 235 | 236 | 237 | 238 | Close selected item 239 | 240 | 241 | Ctrl+W 242 | 243 | 244 | 245 | 246 | true 247 | 248 | 249 | Export 250 | 251 | 252 | Ctrl+E 253 | 254 | 255 | 256 | 257 | true 258 | 259 | 260 | Help 261 | 262 | 263 | 264 | 265 | Exit 266 | 267 | 268 | Ctrl+Q 269 | 270 | 271 | 272 | 273 | true 274 | 275 | 276 | Close all items 277 | 278 | 279 | Ctrl+Shift+W 280 | 281 | 282 | 283 | 284 | Replace 285 | 286 | 287 | Ctrl+R 288 | 289 | 290 | 291 | 292 | About 293 | 294 | 295 | 296 | 297 | Export to raw 298 | 299 | 300 | Export as raw data 301 | 302 | 303 | Ctrl+Shift+E 304 | 305 | 306 | 307 | 308 | Replace with raw 309 | 310 | 311 | Replace with raw data 312 | 313 | 314 | Ctrl+Shift+R 315 | 316 | 317 | 318 | 319 | true 320 | 321 | 322 | Save 323 | 324 | 325 | Save into original .NOS 326 | 327 | 328 | Ctrl+Shift+S 329 | 330 | 331 | 332 | 333 | Rename 334 | 335 | 336 | Rename an item 337 | 338 | 339 | F2 340 | 341 | 342 | 343 | 344 | Export with config 345 | 346 | 347 | Export content with a config for external usage 348 | 349 | 350 | 351 | 352 | Import from config 353 | 354 | 355 | Import content defined in a Config 356 | 357 | 358 | Ctrl+Shift+O 359 | 360 | 361 | 362 | 363 | Settings 364 | 365 | 366 | 367 | 368 | Add and replace by config 369 | 370 | 371 | Load items that should be added or replaced from a config file 372 | 373 | 374 | 375 | 376 | 377 | treeWidget 378 | 379 | 380 | 381 | 382 | 383 | 384 | -------------------------------------------------------------------------------- /resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | resources/oxe_icon_trans.ico 4 | resources/kofi4.png 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/kofi4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pumba98/OnexExplorer/eaee2aa9f0e71b9960da586f425f79e628013021/resources/kofi4.png -------------------------------------------------------------------------------- /resources/oxe_icon_trans.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pumba98/OnexExplorer/eaee2aa9f0e71b9960da586f425f79e628013021/resources/oxe_icon_trans.ico --------------------------------------------------------------------------------