From 0d475a611b4d278f23cd5134b85f9d9bf7513748 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Thu, 15 Feb 2024 08:00:20 -0500 Subject: [PATCH 001/286] Initial work on a packaging tool for TheXTech --- .gitmodules | 21 + Maintainer/CMakeLists.txt | 2 + Maintainer/XTConvert/3rdparty/3dstools | 1 + Maintainer/XTConvert/3rdparty/gamecube-tools | 1 + Maintainer/XTConvert/3rdparty/libarchive | 1 + Maintainer/XTConvert/3rdparty/libimagequant | 1 + Maintainer/XTConvert/3rdparty/mbediso | 1 + Maintainer/XTConvert/3rdparty/spc2it | 1 + Maintainer/XTConvert/3rdparty/tex3ds | 1 + Maintainer/XTConvert/xt_convert.cpp | 222 ++++++++++ Maintainer/XTConvert/xt_convert.h | 78 ++++ Maintainer/XTConvert/xt_convert.ui | 406 +++++++++++++++++++ Maintainer/main_window.cpp | 11 + Maintainer/main_window.h | 1 + Maintainer/main_window.ui | 18 +- 15 files changed, 765 insertions(+), 1 deletion(-) create mode 160000 Maintainer/XTConvert/3rdparty/3dstools create mode 160000 Maintainer/XTConvert/3rdparty/gamecube-tools create mode 160000 Maintainer/XTConvert/3rdparty/libarchive create mode 160000 Maintainer/XTConvert/3rdparty/libimagequant create mode 160000 Maintainer/XTConvert/3rdparty/mbediso create mode 160000 Maintainer/XTConvert/3rdparty/spc2it create mode 160000 Maintainer/XTConvert/3rdparty/tex3ds create mode 100644 Maintainer/XTConvert/xt_convert.cpp create mode 100644 Maintainer/XTConvert/xt_convert.h create mode 100644 Maintainer/XTConvert/xt_convert.ui diff --git a/.gitmodules b/.gitmodules index dc8f06563e..8569720fe8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -50,3 +50,24 @@ path = _Libs/libtiff url = https://github.com/WohlSoft/libtiff.git branch = master +[submodule "XTConvert/tex3ds"] + path = Maintainer/XTConvert/3rdparty/tex3ds + url = https://github.com/WohlSoft/xtconvert-tex3ds.git +[submodule "XTConvert/3dstools"] + path = Maintainer/XTConvert/3rdparty/3dstools + url = https://github.com/WohlSoft/xtconvert-3dstools.git +[submodule "XTConvert/libimagequant"] + path = Maintainer/XTConvert/3rdparty/libimagequant + url = https://github.com/WohlSoft/xtconvert-libimagequant.git +[submodule "XTConvert/libarchive"] + path = Maintainer/XTConvert/3rdparty/libarchive + url = https://github.com/WohlSoft/xtconvert-libarchive.git +[submodule "XTConvert/gamecube-tools"] + path = Maintainer/XTConvert/3rdparty/gamecube-tools + url = https://github.com/WohlSoft/xtconvert-gamecube-tools.git +[submodule "XTConvert/mbediso"] + path = Maintainer/XTConvert/3rdparty/mbediso + url = https://github.com/ds-sloth/mbediso.git +[submodule "XTConvert/spc2it"] + path = Maintainer/XTConvert/3rdparty/spc2it + url = https://github.com/ds-sloth/spc2it.git diff --git a/Maintainer/CMakeLists.txt b/Maintainer/CMakeLists.txt index d33fba726d..6c37d9420b 100644 --- a/Maintainer/CMakeLists.txt +++ b/Maintainer/CMakeLists.txt @@ -111,6 +111,7 @@ pge_qt_wrap_ui(UIS_HDRS Graphics/png2gifs.ui Graphics/lazyfix.ui Graphics/palette_filter.ui + XTConvert/xt_convert.ui ) set(RESOURCE ${CMAKE_CURRENT_SOURCE_DIR}/_resources/maintainer.qrc) @@ -138,6 +139,7 @@ list(APPEND PGE_MAINTAINER_SRCS Graphics/png2gifs.cpp Graphics/lazyfix.cpp Graphics/palette_filter.cpp + XTConvert/xt_convert.cpp ${UTF8MAIN_SRCS} ${UIS_HDRS} ${RESOURCE_ADDED} diff --git a/Maintainer/XTConvert/3rdparty/3dstools b/Maintainer/XTConvert/3rdparty/3dstools new file mode 160000 index 0000000000..718d6f2330 --- /dev/null +++ b/Maintainer/XTConvert/3rdparty/3dstools @@ -0,0 +1 @@ +Subproject commit 718d6f2330dbef9dd5726573392425dcbd451677 diff --git a/Maintainer/XTConvert/3rdparty/gamecube-tools b/Maintainer/XTConvert/3rdparty/gamecube-tools new file mode 160000 index 0000000000..609dc7884e --- /dev/null +++ b/Maintainer/XTConvert/3rdparty/gamecube-tools @@ -0,0 +1 @@ +Subproject commit 609dc7884eae148ee725c93b3d78d69902f69a0c diff --git a/Maintainer/XTConvert/3rdparty/libarchive b/Maintainer/XTConvert/3rdparty/libarchive new file mode 160000 index 0000000000..8dbb3b4d54 --- /dev/null +++ b/Maintainer/XTConvert/3rdparty/libarchive @@ -0,0 +1 @@ +Subproject commit 8dbb3b4d54213f988fce77c7fa7eaf63249cdbb5 diff --git a/Maintainer/XTConvert/3rdparty/libimagequant b/Maintainer/XTConvert/3rdparty/libimagequant new file mode 160000 index 0000000000..869aa5b859 --- /dev/null +++ b/Maintainer/XTConvert/3rdparty/libimagequant @@ -0,0 +1 @@ +Subproject commit 869aa5b859573df8bf763767b77deb3e3b5d3538 diff --git a/Maintainer/XTConvert/3rdparty/mbediso b/Maintainer/XTConvert/3rdparty/mbediso new file mode 160000 index 0000000000..77c4beeb3d --- /dev/null +++ b/Maintainer/XTConvert/3rdparty/mbediso @@ -0,0 +1 @@ +Subproject commit 77c4beeb3d2593d1e02bf92817f195d03f28e550 diff --git a/Maintainer/XTConvert/3rdparty/spc2it b/Maintainer/XTConvert/3rdparty/spc2it new file mode 160000 index 0000000000..4303c61303 --- /dev/null +++ b/Maintainer/XTConvert/3rdparty/spc2it @@ -0,0 +1 @@ +Subproject commit 4303c6130370077cd761a2344cd692e1e4530b3a diff --git a/Maintainer/XTConvert/3rdparty/tex3ds b/Maintainer/XTConvert/3rdparty/tex3ds new file mode 160000 index 0000000000..37220c696e --- /dev/null +++ b/Maintainer/XTConvert/3rdparty/tex3ds @@ -0,0 +1 @@ +Subproject commit 37220c696e873812df41af62d1db0b20c66661fe diff --git a/Maintainer/XTConvert/xt_convert.cpp b/Maintainer/XTConvert/xt_convert.cpp new file mode 100644 index 0000000000..6e5ab309c3 --- /dev/null +++ b/Maintainer/XTConvert/xt_convert.cpp @@ -0,0 +1,222 @@ +#include "xt_convert.h" +#include "ui_xt_convert.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "qfile_dialogs_default_options.hpp" + + +static QString strTail(QString str, int len) +{ + if(len <= 0) + return ""; + if(str.size() <= len) + return str; + return "..." + str.mid(str.size() - (len + 1), -1); +} + + +XTConvert::XTConvert(QWidget *parent) : + QDialog(parent), + ui(new Ui::XTConvert) +{ + ui->setupUi(this); + + connect(&converter, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(nextStep(int, QProcess::ExitStatus))); + connect(&converter, SIGNAL(readyRead()), this, SLOT(consoleMessage())); + converter.setTextModeEnabled(true); + converter.setProcessChannelMode(QProcess::MergedChannels); + + connect(this, SIGNAL(doNextStep(int, int)), this, SLOT(nextStep_x(int, int))); + connect(this, SIGNAL(gotMax(int)), ui->progress, SLOT(setMaximum(int))); + + inWork = false; + isBackUp = false; + madeJob = 0; +} + +XTConvert::~XTConvert() +{ + delete ui; +} + +void XTConvert::resetStat() +{ + stat_converted_files = 0; + stat_overwritten_levels = 0; + stat_overwritten_worlds = 0; + stat_failed_levels = 0; + stat_failed_files = 0; + fails.clear(); +} + +void XTConvert::setEnableControls() +{ + // TODO: enable controls according to the current engine state +} + +void XTConvert::start() +{ + QMessageBox::warning(this, tr("SoX error"), tr("SoX binary path is wrong.\nPlease set SoX path first")); + return; + + resetStat(); + + bool execFetchEpisodeData = false; + + ui->start->setText(tr("Stop")); + setEnableControls(); + + ui->progress->setMaximum(filesToConvert.size() * 100); + ui->progress->setValue(-1); + lastOutput.clear(); + inWork = true; + madeJob = 0; + + ui->status->setText("Preparing..."); + + if(execFetchEpisodeData) + { + // fetcher = QtConcurrent::run(this, &XTConvert::CollectEpisodeData); + // ui->status->setText("Looking for a musics and level/world files..."); + } + else + nextStep(0, QProcess::NormalExit); +} + +void XTConvert::stop(bool do_abort) +{ + if(fetcher.isRunning()) + fetcher.cancel(); + ui->start->setText(tr("Start")); + ui->progress->setValue(0); + ui->progress->setMaximum(10); + setEnableControls(); + current_musFileNew.clear(); + current_musFileOld.clear(); + filesToConvert.clear(); + inWork = false; + ui->status->setText(""); + + //Restore backUP + if(do_abort) + { + if(converter.state() == QProcess::Running) + converter.terminate(); + + if(isBackUp) + { + if(QFile(current_musFileOld).exists()) + { + if(QFile(current_musFileNew).exists()) + QFile(current_musFileNew).remove(); + QFile(current_musFileOld).rename(current_musFileNew); + } + } + else + { + if(QFile(current_musFileNew).exists()) + QFile(current_musFileNew).remove(); + } + + QMessageBox::warning(this, tr("SoX error"), tr("Operation cancaled")); + } +} + +void XTConvert::nextStep_x(int retStatus, int exitStatus) +{ + nextStep(retStatus, (QProcess::ExitStatus)exitStatus); +} + +void XTConvert::nextStep(int retStatus, QProcess::ExitStatus exitStatus) +{ + if(!inWork) return; +} + +static QString leftBuffer; +void XTConvert::consoleMessage() +{ + QByteArray strdata = converter.readAllStandardOutput(); + int made = 0; + bool found = false; + qDebug() << "OUTPUT: " << strdata; + QStringList acceptedData = QString::fromUtf8(strdata).split('\r'); + foreach(QString entry, acceptedData) + { + entry.remove('\n'); + qDebug() << "ENTRY" << entry; + if(!leftBuffer.isEmpty()) + { + entry = leftBuffer + entry; + leftBuffer.clear(); + } + if(entry.size() < 9) + { + leftBuffer = entry; + continue; + } + if(entry.startsWith("In:")) + { + made = qRound(entry.mid(3, 6).trimmed().remove('%').toDouble()); + found = true; + qDebug() << "PROGRESS: " << made; + break; + } + } + + if(found) + { + qDebug() << "APPLIED: " << ((madeJob - 1) * 100 + made); + ui->progress->setValue((madeJob - 1) * 100 + made); + } + // lastOutput.append(strdata); + // lastOutput.append("\n"); + // //DevConsole::log(strdata, "SoX"); +} + +void XTConvert::on_start_clicked() +{ + if(inWork) + stop(true); + else + start(); +} + +void XTConvert::closeEvent(QCloseEvent *e) +{ + if(inWork) + e->ignore(); + else + e->accept(); +} + +void XTConvert::on_browse_asset_pack_clicked() +{ + QString filter; + filter = "Asset pack archives and info files (gameinfo.ini *.xta)"; + QString file = QFileDialog::getOpenFileName(this, tr("Selecting a level file to convert custom music"), + (ui->asset_pack_path->text().isEmpty() ? ApplicationPath : ui->asset_pack_path->text()), + filter, nullptr, c_fileDialogOptions); + if(file.isEmpty()) return; + + ui->asset_pack_path->setText(file); +} + +void XTConvert::on_browse_episode_clicked() +{ + QString filter; + filter = "Episode archives and world files (*.wld *.wldx *.xte)"; + QString dir = QFileDialog::getExistingDirectory(this, tr("Open episode path to convert entire music set"), + (ui->episode_path->text().isEmpty() ? ApplicationPath : ui->episode_path->text()), c_dirDialogOptions); + if(dir.isEmpty()) return; + + ui->episode_path->setText(dir); +} diff --git a/Maintainer/XTConvert/xt_convert.h b/Maintainer/XTConvert/xt_convert.h new file mode 100644 index 0000000000..ba04bdba22 --- /dev/null +++ b/Maintainer/XTConvert/xt_convert.h @@ -0,0 +1,78 @@ +#ifndef xt_convert_H +#define xt_convert_H + +#include +#include +#include +#include +#include + +namespace Ui { +class XTConvert; +} + +class XTConvert : public QDialog +{ + Q_OBJECT + +public: + explicit XTConvert(QWidget *parent = 0); + ~XTConvert(); + void setEnableControls(); + void start(); + void stop(bool do_abort=false); + +signals: + void doNextStep(int retStatus, int exitStatus); + void gotMax(int max); + +private slots: + void consoleMessage(); + void nextStep_x(int retStatus, int exitStatus); + void nextStep(int retStatus, QProcess::ExitStatus exitStatus); + void on_start_clicked(); + void on_browse_asset_pack_clicked(); + void on_browse_episode_clicked(); + +protected: + void closeEvent( QCloseEvent * e ); + +private: + QFuture fetcher; + + bool inWork; + int madeJob; + QProcess converter; + QQueue filesToConvert; + QString current_musFileOld; + QString current_musFileNew; + Ui::XTConvert *ui; + bool isBackUp; + + enum CVTJOBS{ + CVT_LVL=0, + CVT_EPS, + CVT_CUSTOM + }; + + CVTJOBS job; + + QString curSectionMusic; + QString lastOutput; + QStringList curLevelMusic; + + EpisodeBox episodeBox; + EpisodeBox_level episodeBoxLvl; + EpisodeBox_world episodeBoxWld; + + //statistics + void resetStat(); + long stat_converted_files; + long stat_overwritten_levels; + long stat_overwritten_worlds; + long stat_failed_files; + long stat_failed_levels; + QStringList fails; +}; + +#endif // xt_convert_H diff --git a/Maintainer/XTConvert/xt_convert.ui b/Maintainer/XTConvert/xt_convert.ui new file mode 100644 index 0000000000..03ef918dd9 --- /dev/null +++ b/Maintainer/XTConvert/xt_convert.ui @@ -0,0 +1,406 @@ + + + XTConvert + + + + 0 + 0 + 773 + 403 + + + + Convert Audio (SoX) + + + + :/maintainer.ico:/maintainer.ico + + + + + + Qt::LayoutDirection::LeftToRight + + + + + + Qt::TextFormat::PlainText + + + true + + + + + + + <html><head/><body><p align="center">Here you can package an asset pack or episode for TheXTech to share it with others or play it on low-end consoles.</p></body></html> + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + true + + + + + + + Qt::Orientation::Horizontal + + + + + + + true + + + + 0 + 0 + + + + false + + + 0 + + + false + + + + Load Asset Pack + + + + + 0 + 0 + 451 + 162 + + + + + + + + + Asset pack path: + + + + + + + + + + Browse... + + + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + Package this asset pack + + + + + + + Package an episode + + + + + + + + + + + false + + + Load Episode + + + + + 0 + 0 + 451 + 162 + + + + + + + + + Episode path: + + + + + + + + + + false + + + Browse... + + + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + + + + + + + false + + + Enter Details + + + + + false + + + Choose Target + + + + + 0 + 0 + 451 + 223 + + + + + + + + + Target platform: + + + + + + + + Modern + + + + + 3DS + + + + + Wii + + + + + + + + + + + + Output path: + + + + + + + + + + false + + + Browse... + + + + + + + + + Qt::Orientation::Horizontal + + + + + + + Convert SFX to uncompressed WAV format. This option greatly improves load performance at the cost of package size. + + + Convert SFX to WAV format + + + false + + + + + + + This option converts streamed music to the vorbis format at the native sampling rate of the target device. It will greatly improve audio performance at the possible cost of quality. + + + Convert streamed music to vorbis + + + false + + + + + + + IT format is much more efficient to play but loses some features + + + Convert SPC music to IT format + + + + + + + Bitmask GIFs sometimes have special transparency effects that cannot be emulated with native graphics format. Lossy conversion allows these effects to be lost, but improves loading performance. + + + Lossy conversion for bitmask GIFs + + + + + + + + + false + + + Convert! + + + + + 560 + 200 + 108 + 26 + + + + Start + + + + + false + + + + 20 + 240 + 567 + 26 + + + + 1 + + + 0 + + + true + + + false + + + + + + 30 + 20 + 449 + 109 + + + + + + + + + + + + + diff --git a/Maintainer/main_window.cpp b/Maintainer/main_window.cpp index aa44e29b4d..38c40d84e2 100644 --- a/Maintainer/main_window.cpp +++ b/Maintainer/main_window.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -129,3 +130,13 @@ void MaintainerMain::on_pathcase_fixer_clicked() casefixer.exec(); this->show(); } + +void MaintainerMain::on_xt_convert_clicked() +{ + XTConvert xt_convert(nullptr); + xt_convert.setWindowFlags(g_dialogFlags); + xt_convert.setWindowModality(Qt::NonModal); + this->hide(); + xt_convert.exec(); + this->show(); +} diff --git a/Maintainer/main_window.h b/Maintainer/main_window.h index 8e1a342aba..2723a8d9db 100644 --- a/Maintainer/main_window.h +++ b/Maintainer/main_window.h @@ -32,6 +32,7 @@ private slots: void on_lazyfix_tool_clicked(); void on_palette_filter_clicked(); void on_pathcase_fixer_clicked(); + void on_xt_convert_clicked(); private: Ui::MaintainerMain *ui; diff --git a/Maintainer/main_window.ui b/Maintainer/main_window.ui index da4cf45080..51c0257b80 100644 --- a/Maintainer/main_window.ui +++ b/Maintainer/main_window.ui @@ -7,7 +7,7 @@ 0 0 377 - 475 + 512 @@ -173,6 +173,22 @@ + + + + + 0 + 50 + + + + Convert and package game assets and episodes for TheXTech. + + + TheXTech packager + + + From d62d9d059786167d23523b0c9e472b22ef9c511e Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sun, 18 Aug 2024 15:25:26 -0400 Subject: [PATCH 002/286] Continue work on packaging tool for TheXTech --- Maintainer/CMakeLists.txt | 8 ++++ Maintainer/XTConvert/3rdparty/libimagequant | 2 +- Maintainer/XTConvert/3rdparty/mbediso | 2 +- Maintainer/XTConvert/3rdparty/spc2it | 2 +- .../XTConvert/cmake/build_libarchive.cmake | 46 +++++++++++++++++++ Maintainer/XTConvert/xt_convert.cpp | 20 ++++++++ Maintainer/XTConvert/xt_convert_int.cpp | 23 ++++++++++ 7 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 Maintainer/XTConvert/cmake/build_libarchive.cmake create mode 100644 Maintainer/XTConvert/xt_convert_int.cpp diff --git a/Maintainer/CMakeLists.txt b/Maintainer/CMakeLists.txt index 6c37d9420b..eaf0192de4 100644 --- a/Maintainer/CMakeLists.txt +++ b/Maintainer/CMakeLists.txt @@ -140,6 +140,7 @@ list(APPEND PGE_MAINTAINER_SRCS Graphics/lazyfix.cpp Graphics/palette_filter.cpp XTConvert/xt_convert.cpp + XTConvert/xt_convert_int.cpp ${UTF8MAIN_SRCS} ${UIS_HDRS} ${RESOURCE_ADDED} @@ -291,6 +292,13 @@ if(APPLE) list(APPEND PGE_MAINTAINER_LINK_LIBS ${COREFOUNDATION_LIBRARY}) endif() +# libraries for XTConvert +add_subdirectory(XTConvert/3rdparty/mbediso EXCLUDE_FROM_ALL) +add_subdirectory(XTConvert/3rdparty/libimagequant EXCLUDE_FROM_ALL) +include(XTConvert/cmake/build_libarchive.cmake) +list(APPEND PGE_MAINTAINER_LINK_LIBS archive_static lz4_pack_static imagequant_a) +add_dependencies(pge_maintainer archive_static lz4_pack_static imagequant_a) + target_link_libraries(pge_maintainer ${PGE_MAINTAINER_LINK_LIBS} ) diff --git a/Maintainer/XTConvert/3rdparty/libimagequant b/Maintainer/XTConvert/3rdparty/libimagequant index 869aa5b859..ea735cd9dc 160000 --- a/Maintainer/XTConvert/3rdparty/libimagequant +++ b/Maintainer/XTConvert/3rdparty/libimagequant @@ -1 +1 @@ -Subproject commit 869aa5b859573df8bf763767b77deb3e3b5d3538 +Subproject commit ea735cd9dc3dfc5e151fcfc99f8f937c4708af87 diff --git a/Maintainer/XTConvert/3rdparty/mbediso b/Maintainer/XTConvert/3rdparty/mbediso index 77c4beeb3d..b61918c005 160000 --- a/Maintainer/XTConvert/3rdparty/mbediso +++ b/Maintainer/XTConvert/3rdparty/mbediso @@ -1 +1 @@ -Subproject commit 77c4beeb3d2593d1e02bf92817f195d03f28e550 +Subproject commit b61918c005db2e1d6eef2adaa4cc93e41a8902dd diff --git a/Maintainer/XTConvert/3rdparty/spc2it b/Maintainer/XTConvert/3rdparty/spc2it index 4303c61303..748b8054fe 160000 --- a/Maintainer/XTConvert/3rdparty/spc2it +++ b/Maintainer/XTConvert/3rdparty/spc2it @@ -1 +1 @@ -Subproject commit 4303c6130370077cd761a2344cd692e1e4530b3a +Subproject commit 748b8054fef23ad9f4f0edd36566bbc8082a5ba8 diff --git a/Maintainer/XTConvert/cmake/build_libarchive.cmake b/Maintainer/XTConvert/cmake/build_libarchive.cmake new file mode 100644 index 0000000000..6008c478b7 --- /dev/null +++ b/Maintainer/XTConvert/cmake/build_libarchive.cmake @@ -0,0 +1,46 @@ +function(add_libarchive_target) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + set(ENABLE_MBEDTLS OFF CACHE BOOL "" FORCE) + set(ENABLE_OPENSSL OFF CACHE BOOL "" FORCE) + set(ENABLE_LIBB2 OFF CACHE BOOL "" FORCE) + + # enable LZ4 for extraction + set(ENABLE_LZ4 ON CACHE BOOL "" FORCE) + + set(ENABLE_LZO OFF CACHE BOOL "" FORCE) + set(ENABLE_LZMA OFF CACHE BOOL "" FORCE) + set(ENABLE_ZSTD OFF CACHE BOOL "" FORCE) + + set(ENABLE_ZLIB OFF CACHE BOOL "" FORCE) + set(ENABLE_BZip2 OFF CACHE BOOL "" FORCE) + set(ENABLE_LIBXML2 OFF CACHE BOOL "" FORCE) + set(ENABLE_EXPAT OFF CACHE BOOL "" FORCE) + set(ENABLE_PCREPOSIX OFF CACHE BOOL "" FORCE) + set(ENABLE_PCRE2POSIX OFF CACHE BOOL "" FORCE) + set(ENABLE_LIBGCC OFF CACHE BOOL "" FORCE) + set(ENABLE_CNG OFF CACHE BOOL "" FORCE) + + set(ENABLE_TAR OFF CACHE BOOL "" FORCE) + set(ENABLE_TAR_SHARED OFF CACHE BOOL "" FORCE) + set(ENABLE_CPIO OFF CACHE BOOL "" FORCE) + set(ENABLE_CPIO_SHARED OFF CACHE BOOL "" FORCE) + set(ENABLE_CAT OFF CACHE BOOL "" FORCE) + set(ENABLE_CAT_SHARED OFF CACHE BOOL "" FORCE) + set(ENABLE_UNZIP OFF CACHE BOOL "" FORCE) + set(ENABLE_UNZIP_SHARED OFF CACHE BOOL "" FORCE) + set(ENABLE_XATTR OFF CACHE BOOL "" FORCE) + set(ENABLE_ACL OFF CACHE BOOL "" FORCE) + set(ENABLE_ICONV OFF CACHE BOOL "" FORCE) + set(ENABLE_TEST OFF CACHE BOOL "" FORCE) + set(ENABLE_COVERAGE OFF CACHE BOOL "" FORCE) + set(ENABLE_INSTALL OFF CACHE BOOL "" FORCE) + + get_target_property(LZ4_INCLUDE_DIR lz4_static INTERFACE_INCLUDE_DIRECTORIES) + set(LZ4_LIBRARY "$") + + add_subdirectory(XTConvert/3rdparty/libarchive EXCLUDE_FROM_ALL) + + add_dependencies(archive_static lz4_static) +endfunction() + +add_libarchive_target() diff --git a/Maintainer/XTConvert/xt_convert.cpp b/Maintainer/XTConvert/xt_convert.cpp index 6e5ab309c3..be7cc988a1 100644 --- a/Maintainer/XTConvert/xt_convert.cpp +++ b/Maintainer/XTConvert/xt_convert.cpp @@ -1,3 +1,23 @@ +/* + * XTConvert, a free tool for packaging asset packs and episodes for TheXTech game engine. + * + * This is a part of the Platformer Game Engine by Wohlstand, a free platform for game making + * Copyright (c) 2017-2024 Vitaly Novichkov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "xt_convert.h" #include "ui_xt_convert.h" #include diff --git a/Maintainer/XTConvert/xt_convert_int.cpp b/Maintainer/XTConvert/xt_convert_int.cpp new file mode 100644 index 0000000000..82289dbf0e --- /dev/null +++ b/Maintainer/XTConvert/xt_convert_int.cpp @@ -0,0 +1,23 @@ +/* + * XTConvert, a free tool for packaging asset packs and episodes for TheXTech game engine. + * + * This is a part of the Platformer Game Engine by Wohlstand, a free platform for game making + * Copyright (c) 2017-2024 Vitaly Novichkov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "lz4_pack.h" +#include "archive.h" +#include "libimagequant.h" From 4018af9cfddf3d1284664dfcb04a89c380109a2b Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Fri, 6 Sep 2024 21:48:48 -0400 Subject: [PATCH 003/286] Lots of additional work on XTConvert --- Maintainer/CMakeLists.txt | 10 +- Maintainer/XTConvert/CMakeLists.txt | 24 ++ .../XTConvert/cmake/build_libarchive.cmake | 2 +- Maintainer/XTConvert/graphics_load.cpp | 249 ++++++++++++++++++ Maintainer/XTConvert/graphics_load.h | 28 ++ Maintainer/XTConvert/include/libxtconvert.h | 20 ++ Maintainer/XTConvert/libxtconvert.cpp | 245 +++++++++++++++++ Maintainer/XTConvert/xt_convert_int.cpp | 23 -- Maintainer/XTConvert/xtconvert_cli.cpp | 12 + 9 files changed, 583 insertions(+), 30 deletions(-) create mode 100644 Maintainer/XTConvert/CMakeLists.txt create mode 100644 Maintainer/XTConvert/graphics_load.cpp create mode 100644 Maintainer/XTConvert/graphics_load.h create mode 100644 Maintainer/XTConvert/include/libxtconvert.h create mode 100644 Maintainer/XTConvert/libxtconvert.cpp delete mode 100644 Maintainer/XTConvert/xt_convert_int.cpp create mode 100644 Maintainer/XTConvert/xtconvert_cli.cpp diff --git a/Maintainer/CMakeLists.txt b/Maintainer/CMakeLists.txt index eaf0192de4..3528c53cfc 100644 --- a/Maintainer/CMakeLists.txt +++ b/Maintainer/CMakeLists.txt @@ -140,7 +140,6 @@ list(APPEND PGE_MAINTAINER_SRCS Graphics/lazyfix.cpp Graphics/palette_filter.cpp XTConvert/xt_convert.cpp - XTConvert/xt_convert_int.cpp ${UTF8MAIN_SRCS} ${UIS_HDRS} ${RESOURCE_ADDED} @@ -293,11 +292,10 @@ if(APPLE) endif() # libraries for XTConvert -add_subdirectory(XTConvert/3rdparty/mbediso EXCLUDE_FROM_ALL) -add_subdirectory(XTConvert/3rdparty/libimagequant EXCLUDE_FROM_ALL) -include(XTConvert/cmake/build_libarchive.cmake) -list(APPEND PGE_MAINTAINER_LINK_LIBS archive_static lz4_pack_static imagequant_a) -add_dependencies(pge_maintainer archive_static lz4_pack_static imagequant_a) +add_subdirectory(XTConvert) + +list(APPEND PGE_MAINTAINER_LINK_LIBS libxtconvert) +add_dependencies(pge_maintainer libxtconvert) target_link_libraries(pge_maintainer ${PGE_MAINTAINER_LINK_LIBS} diff --git a/Maintainer/XTConvert/CMakeLists.txt b/Maintainer/XTConvert/CMakeLists.txt new file mode 100644 index 0000000000..7ae2250961 --- /dev/null +++ b/Maintainer/XTConvert/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required (VERSION 3.5) +project(libxtconvert CXX) + +add_subdirectory(3rdparty/mbediso EXCLUDE_FROM_ALL) +add_subdirectory(3rdparty/3dstools EXCLUDE_FROM_ALL) +add_subdirectory(3rdparty/tex3ds EXCLUDE_FROM_ALL) +add_subdirectory(3rdparty/libimagequant EXCLUDE_FROM_ALL) +include(cmake/build_libarchive.cmake) +find_package(Qt6 REQUIRED COMPONENTS Core) + +add_library(libxtconvert libxtconvert.cpp graphics_load.cpp) + +add_dependencies(libxtconvert archive_static lz4_pack_static libromfs3ds libtex3ds FreeImage_Local libpng_Local) + +target_link_libraries(libxtconvert PRIVATE Qt6::Core) +target_link_libraries(libxtconvert PUBLIC archive_static lz4_pack_static libromfs3ds libtex3ds PGE_FreeImage imagequant_a) + +target_include_directories(libxtconvert PUBLIC include) + +project(xtconvert CXX) + +add_executable(xtconvert_cli xtconvert_cli.cpp) +target_link_libraries(xtconvert_cli PRIVATE Qt6::Core) +target_link_libraries(xtconvert_cli PUBLIC libxtconvert) diff --git a/Maintainer/XTConvert/cmake/build_libarchive.cmake b/Maintainer/XTConvert/cmake/build_libarchive.cmake index 6008c478b7..6352b7345d 100644 --- a/Maintainer/XTConvert/cmake/build_libarchive.cmake +++ b/Maintainer/XTConvert/cmake/build_libarchive.cmake @@ -38,7 +38,7 @@ function(add_libarchive_target) get_target_property(LZ4_INCLUDE_DIR lz4_static INTERFACE_INCLUDE_DIRECTORIES) set(LZ4_LIBRARY "$") - add_subdirectory(XTConvert/3rdparty/libarchive EXCLUDE_FROM_ALL) + add_subdirectory(3rdparty/libarchive EXCLUDE_FROM_ALL) add_dependencies(archive_static lz4_static) endfunction() diff --git a/Maintainer/XTConvert/graphics_load.cpp b/Maintainer/XTConvert/graphics_load.cpp new file mode 100644 index 0000000000..23ff7ccd63 --- /dev/null +++ b/Maintainer/XTConvert/graphics_load.cpp @@ -0,0 +1,249 @@ +#include +#include + +#include + +#include "graphics_load.h" + +void GraphicsLoad::initFreeImage() +{ + FreeImage_Initialise(); +} + +void GraphicsLoad::closeFreeImage() +{ + FreeImage_DeInitialise(); +} + +FIBITMAP* GraphicsLoad::loadImage(const QString& path) +{ + QFile file(path); + if(!file.open(QIODevice::ReadOnly)) + return nullptr; + + QByteArray got = file.readAll(); + + FIMEMORY* imgMEM = FreeImage_OpenMemory(reinterpret_cast(got.data()), + static_cast(got.size())); + + FREE_IMAGE_FORMAT formato = FreeImage_GetFileTypeFromMemory(imgMEM); + + if(formato == FIF_UNKNOWN) + { + FreeImage_CloseMemory(imgMEM); + return nullptr; + } + + FIBITMAP* img = FreeImage_LoadFromMemory(formato, imgMEM, 0); + FreeImage_CloseMemory(imgMEM); + + if(!img) + return nullptr; + + if(FreeImage_GetBPP(img) == 32) + return img; + + FIBITMAP* img_rgba = FreeImage_ConvertTo32Bits(img); + + FreeImage_Unload(img); + + return img_rgba; +} + +FIBITMAP* GraphicsLoad::loadPNGAsMask(const QString& path) +{ + FIBITMAP* mask = loadImage(path); + + if(!mask) + return nullptr; + + // convert alpha channel to luminance + unsigned int img_w = FreeImage_GetWidth(mask); + unsigned int img_h = FreeImage_GetHeight(mask); + + uint8_t *pixel_data = reinterpret_cast(FreeImage_GetBits(mask)); + auto dest_px_stride = static_cast(FreeImage_GetPitch(mask)); + + for(unsigned int y = 0; (y < img_h); y++) + { + for(unsigned int x = 0; (x < img_w); x++) + { + RGBQUAD pix; + FreeImage_GetPixelColor(mask, x, y, &pix); + + uint8_t* pixel = &pixel_data[dest_px_stride * y + 4 * x]; + + uint8_t grey = 0xFF - pixel[3]; + pixel[0] = grey; + pixel[1] = grey; + pixel[2] = grey; + pixel[3] = 0xFF; + } + } + + return mask; +} + +static constexpr RGBQUAD s_bitblitBG = {0, 0, 0, 0xFF}; + +void GraphicsLoad::mergeWithMask(FIBITMAP *image, FIBITMAP *mask) +{ + unsigned int img_w = FreeImage_GetWidth(image); + unsigned int img_h = FreeImage_GetHeight(image); + unsigned int img_pitch = FreeImage_GetPitch(image); + unsigned int mask_w = FreeImage_GetWidth(mask); + unsigned int mask_h = FreeImage_GetHeight(mask); + unsigned int mask_pitch = FreeImage_GetPitch(mask); + BYTE *img_bits = FreeImage_GetBits(image); + BYTE *mask_bits = FreeImage_GetBits(mask); + BYTE *FPixP = nullptr; + BYTE *SPixP = mask_bits; + RGBQUAD Npix = {0x00, 0x00, 0x00, 0xFF}; //Destination pixel color + BYTE Wpix[] = {0xFF, 0xFF, 0xFF, 0xFF}; //Dummy white pixel + unsigned short newAlpha = 0xFF; //Calculated destination alpha-value + + bool endOfY = false; + unsigned int ym = mask_h - 1; + unsigned int y = img_h - 1; + + while(1) + { + FPixP = img_bits + (img_pitch * y); + if(!endOfY) + SPixP = mask_bits + (mask_pitch * ym); + + for(unsigned int x = 0; (x < img_w); x++) + { + Npix.rgbBlue = ((SPixP[FI_RGBA_BLUE] & s_bitblitBG.rgbBlue) | FPixP[FI_RGBA_BLUE]); + Npix.rgbGreen = ((SPixP[FI_RGBA_GREEN] & s_bitblitBG.rgbGreen) | FPixP[FI_RGBA_GREEN]); + Npix.rgbRed = ((SPixP[FI_RGBA_RED] & s_bitblitBG.rgbRed) | FPixP[FI_RGBA_RED]); + newAlpha = 255 - ((static_cast(SPixP[FI_RGBA_RED]) + + static_cast(SPixP[FI_RGBA_GREEN]) + + static_cast(SPixP[FI_RGBA_BLUE])) / 3); + + if((SPixP[FI_RGBA_RED] > 240u) //is almost White + && (SPixP[FI_RGBA_GREEN] > 240u) + && (SPixP[FI_RGBA_BLUE] > 240u)) + newAlpha = 0; + + newAlpha += ((static_cast(FPixP[FI_RGBA_RED]) + + static_cast(FPixP[FI_RGBA_GREEN]) + + static_cast(FPixP[FI_RGBA_BLUE])) / 3); + + if(newAlpha > 255) newAlpha = 255; + + FPixP[FI_RGBA_BLUE] = Npix.rgbBlue; + FPixP[FI_RGBA_GREEN] = Npix.rgbGreen; + FPixP[FI_RGBA_RED] = Npix.rgbRed; + FPixP[FI_RGBA_ALPHA] = static_cast(newAlpha); + FPixP += 4; + + if(x >= mask_w - 1 || endOfY) + SPixP = Wpix; + else + SPixP += 4; + } + + if(ym == 0) + { + endOfY = true; + SPixP = Wpix; + } + else + ym--; + + if(y == 0) + break; + y--; + } +} + +FIBITMAP *GraphicsLoad::fast2xScaleDown(FIBITMAP *image) +{ + if(!image) + return nullptr; + + if(FreeImage_GetBPP(image) != 32) + return nullptr; + + auto src_w = static_cast(FreeImage_GetWidth(image)); + auto src_h = static_cast(FreeImage_GetHeight(image)); + const uint32_t *src_pixels = reinterpret_cast(FreeImage_GetBits(image)); + auto src_pitch_px = static_cast(FreeImage_GetPitch(image)) / 4; + + FIBITMAP *dest = FreeImage_Allocate(src_w / 2, src_h / 2, 32); + + if(!dest) + return nullptr; + + uint32_t *dest_pixels = reinterpret_cast(FreeImage_GetBits(dest)); + auto dest_pitch_px = static_cast(FreeImage_GetPitch(dest)) / 4; + + for(uint32_t src_y = 0, dest_y = 0; dest_y < src_h / 2; src_y += 2, dest_y += 1) + { + for(uint32_t src_x = 0, dest_x = 0; dest_x < src_w / 2; src_x += 2, dest_x += 1) + { + dest_pixels[dest_y * dest_pitch_px + dest_x] = src_pixels[src_y * src_pitch_px + src_x]; + } + } + + return dest; +} + +bool GraphicsLoad::validateBitmaskRequired(FIBITMAP *image, FIBITMAP *mask) +{ + if(!image || !mask) + return false; + + auto fw = static_cast(FreeImage_GetWidth(image)); + auto fh = static_cast(FreeImage_GetHeight(image)); + auto fpitch = static_cast(FreeImage_GetPitch(image)); + BYTE *fimg_bits = FreeImage_GetBits(image); + + auto bw = static_cast(FreeImage_GetWidth(mask)); + auto bh = static_cast(FreeImage_GetHeight(mask)); + auto bpitch = static_cast(FreeImage_GetPitch(mask)); + BYTE *bimg_bits = FreeImage_GetBits(mask); + + BYTE *line1 = fimg_bits; + BYTE *line2 = bimg_bits; + + for(uint32_t y = 0; y < fh || y < bh; ++y) + { + for(uint32_t x = 0; x < fw || x < bw; ++x) + { + bool bp_present = y < bh && x < bw; + bool fp_present = y < fh && x < fw; + + BYTE *fp = line1 + (y * fpitch) + (x * 4); + BYTE *bp = line2 + (y * bpitch) + (x * 4); + + // accept vanilla GIFs that target 16-bit color depth + // note that missing back pixels are white and absent front pixels are black + bool bp_is_white = !bp_present || (bp[0] >= 0xf8 && bp[1] >= 0xf8 && bp[2] >= 0xf8); + bool fp_is_white = fp_present && (fp[0] >= 0xf8 && fp[1] >= 0xf8 && fp[2] >= 0xf8); + bool bp_is_black = bp_present && (bp[0] < 0x08 && bp[1] < 0x08 && bp[2] < 0x08); + bool fp_is_black = !fp_present || (fp[0] < 0x08 && fp[1] < 0x08 && fp[2] < 0x08); + + // mask pixel is black: buffer replaced with front pixel + if(bp_is_black) + continue; + + // front pixel is white: buffer replaced with front pixel + if(fp_is_white) + continue; + + // back pixel is white and front pixel is black: buffer preserved + if(bp_is_white && fp_is_black) + continue; + + // pixel is matching with the front (i.e. is not an example of the lazily-made sprite) + if(bp_present && fp_present && bp[0] == fp[0] && bp[1] == fp[1] && bp[2] == fp[2]) + continue; + + return true; + } + } + + return false; +} diff --git a/Maintainer/XTConvert/graphics_load.h b/Maintainer/XTConvert/graphics_load.h new file mode 100644 index 0000000000..b26edfb15a --- /dev/null +++ b/Maintainer/XTConvert/graphics_load.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +struct FIBITMAP; +class GraphicsLoad +{ +public: + /*! + * \brief Initializes FreeImage + */ + static void initFreeImage(); + + /*! + * \brief DeInitializes FreeImage + */ + static void closeFreeImage(); + + static FIBITMAP* loadImage(const QString& path); + + static FIBITMAP* loadPNGAsMask(const QString& path); + + static void mergeWithMask(FIBITMAP *image, FIBITMAP *mask); + + static FIBITMAP *fast2xScaleDown(FIBITMAP *image); + + static bool validateBitmaskRequired(FIBITMAP *image, FIBITMAP *mask); +}; diff --git a/Maintainer/XTConvert/include/libxtconvert.h b/Maintainer/XTConvert/include/libxtconvert.h new file mode 100644 index 0000000000..a6b9642263 --- /dev/null +++ b/Maintainer/XTConvert/include/libxtconvert.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace XTConvert +{ + +struct Spec +{ + bool target_big_endian = false; + bool target_3ds = false; + + QString input_dir; + QString graphics_dir; + QString destination; +}; + +bool Convert(const Spec& spec); + +} // namespace XTConvert diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp new file mode 100644 index 0000000000..a62ce3adbb --- /dev/null +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -0,0 +1,245 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "archive.h" +#include "archive_entry.h" + +#include "lz4_pack.h" +#include "libromfs3ds.h" +#include "libtex3ds.h" + +#include "libxtconvert.h" + +namespace XTConvert +{ + +class Converter +{ +public: + QDir m_input_dir; + QTemporaryDir m_temp_dir_owner; + QDir m_temp_dir; + + Spec m_spec; + + QString m_error; + + bool convert_file(const QString& filename, const QString& in_path, const QString& out_path) + { + if(filename.endsWith("m.gif")) + return true; + else + { + qInfo() << "copying" << out_path; + return QFile::copy(in_path, out_path); + } + } + + bool build_temp_dir() + { + m_input_dir.makeAbsolute(); + + if(!m_temp_dir_owner.isValid()) + { + m_error = "Failed to create temporary directory"; + return false; + } + + m_temp_dir.setPath(m_temp_dir_owner.path()); + + m_input_dir.setFilter(QDir::NoDotAndDotDot | QDir::AllEntries); + + QDirIterator it(m_input_dir, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + + while(it.hasNext()) + { + QString abs_path = it.next(); + QString rel_path = m_input_dir.relativeFilePath(abs_path); + QString temp_path = m_temp_dir.filePath(rel_path); + + QString filename = it.fileName().toLower(); + + if(it.fileInfo().isDir()) + { + qInfo() << "making" << temp_path; + if(!m_temp_dir.mkdir(temp_path)) + { + m_error = "Failed to create directory "; + m_error += temp_path; + return false; + } + + continue; + } + + if(!convert_file(filename, abs_path, temp_path)) + { + m_error = "Failed to convert file "; + m_error += temp_path; + return false; + } + } + + return true; + } + + bool build_graphics_lists() + { + return true; + } + + bool build_soundbank() + { + return true; + } + + bool create_package_iso_lz4() + { + if(m_spec.destination.size() == 0) + return false; + + QTemporaryFile temp_iso = QTemporaryFile(); + if(!temp_iso.open()) + return false; + + QString iso_path = temp_iso.fileName(); + temp_iso.close(); + + if(iso_path.isEmpty()) + return false; + + bool success = false; + int r; + + struct archive* package = nullptr; + struct archive_entry* entry = nullptr; + + package = archive_write_new(); + if(!package) + goto cleanup; + + archive_write_set_format_iso9660(package); + archive_write_set_format_option(package, "iso9660", "rockridge", nullptr); + archive_write_set_format_option(package, "iso9660", "pad", nullptr); + archive_write_set_format_option(package, "iso9660", "iso-level", "1"); + archive_write_set_format_option(package, "iso9660", "limit-depth", nullptr); + archive_write_open_filename(package, iso_path.toUtf8().data()); + + { + m_temp_dir.setFilter(QDir::NoDotAndDotDot | QDir::Files); + + QDirIterator it(m_temp_dir, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + + while(it.hasNext()) + { + QString abs_path = it.next(); + QString rel_path = m_temp_dir.relativeFilePath(abs_path); + + QFile file(abs_path); + if(!file.open(QIODevice::ReadOnly)) + goto cleanup; + + QByteArray got = file.readAll(); + + entry = archive_entry_new(); + archive_entry_set_pathname(entry, rel_path.toUtf8().data()); + archive_entry_set_size(entry, got.size()); + archive_entry_set_filetype(entry, AE_IFREG); + + r = archive_write_header(package, entry); + if(r < ARCHIVE_OK) + { + // think about whether to do anything here + } + + if(r == ARCHIVE_FATAL) + goto cleanup; + + if(r > ARCHIVE_FAILED) + archive_write_data(package, got.data(), got.size()); + + archive_entry_free(entry); + entry = nullptr; + } + } + + success = true; + +cleanup: + if(entry) + archive_entry_free(entry); + + if(package) + { + archive_write_close(package); + archive_write_free(package); + } + + if(success) + { + FILE* inf = fopen(iso_path.toUtf8().data(), "rb"); + FILE* outf = fopen(m_spec.destination.toUtf8().data(), "wb"); + + success = LZ4Pack::compress(outf, inf, 4096, m_spec.target_big_endian); + + if(inf) + fclose(inf); + + if(outf) + fclose(outf); + + return success; + } + + return false; + } + + bool create_package_romfs3ds() + { + if(m_spec.destination.size() == 0) + return false; + + if(LibRomFS3DS::MkRomfs(m_spec.destination.toUtf8().data(), m_temp_dir.path().toUtf8().data()) != 0) + return false; + + return true; + } + + bool create_package() + { + if(m_spec.target_3ds) + return create_package_romfs3ds(); + else + return create_package_iso_lz4(); + } + + bool process() + { + m_input_dir.setPath(m_spec.input_dir); + + if(!build_temp_dir()) + return false; + + if(!create_package()) + return false; + + return true; + } +}; + +bool Convert(const Spec& spec) +{ + Converter c; + c.m_spec = spec; + return c.process(); +} + +} // namespace XTConvert diff --git a/Maintainer/XTConvert/xt_convert_int.cpp b/Maintainer/XTConvert/xt_convert_int.cpp deleted file mode 100644 index 82289dbf0e..0000000000 --- a/Maintainer/XTConvert/xt_convert_int.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * XTConvert, a free tool for packaging asset packs and episodes for TheXTech game engine. - * - * This is a part of the Platformer Game Engine by Wohlstand, a free platform for game making - * Copyright (c) 2017-2024 Vitaly Novichkov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "lz4_pack.h" -#include "archive.h" -#include "libimagequant.h" diff --git a/Maintainer/XTConvert/xtconvert_cli.cpp b/Maintainer/XTConvert/xtconvert_cli.cpp new file mode 100644 index 0000000000..f0f3cd5976 --- /dev/null +++ b/Maintainer/XTConvert/xtconvert_cli.cpp @@ -0,0 +1,12 @@ +#include "libxtconvert.h" + +int main(int argc, char** argv) +{ + XTConvert::Spec s; + // c.m_spec.target_3ds = true; + + s.input_dir = argv[1]; + s.destination = argv[2]; + + return !XTConvert::Convert(s); +} From 00b1175c0e70b51790d4dbe04c2ac759b0988df5 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Fri, 6 Sep 2024 21:50:40 -0400 Subject: [PATCH 004/286] Update submodules for XTConvert --- Maintainer/XTConvert/3rdparty/3dstools | 2 +- Maintainer/XTConvert/3rdparty/libarchive | 2 +- Maintainer/XTConvert/3rdparty/mbediso | 2 +- Maintainer/XTConvert/3rdparty/tex3ds | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Maintainer/XTConvert/3rdparty/3dstools b/Maintainer/XTConvert/3rdparty/3dstools index 718d6f2330..0664a1033f 160000 --- a/Maintainer/XTConvert/3rdparty/3dstools +++ b/Maintainer/XTConvert/3rdparty/3dstools @@ -1 +1 @@ -Subproject commit 718d6f2330dbef9dd5726573392425dcbd451677 +Subproject commit 0664a1033f55f8f63c5e199146417333c92539d2 diff --git a/Maintainer/XTConvert/3rdparty/libarchive b/Maintainer/XTConvert/3rdparty/libarchive index 8dbb3b4d54..e5c642e9c7 160000 --- a/Maintainer/XTConvert/3rdparty/libarchive +++ b/Maintainer/XTConvert/3rdparty/libarchive @@ -1 +1 @@ -Subproject commit 8dbb3b4d54213f988fce77c7fa7eaf63249cdbb5 +Subproject commit e5c642e9c76d3d0d88e4ae4fedc4cdac82ba3f30 diff --git a/Maintainer/XTConvert/3rdparty/mbediso b/Maintainer/XTConvert/3rdparty/mbediso index b61918c005..cd6c6dd624 160000 --- a/Maintainer/XTConvert/3rdparty/mbediso +++ b/Maintainer/XTConvert/3rdparty/mbediso @@ -1 +1 @@ -Subproject commit b61918c005db2e1d6eef2adaa4cc93e41a8902dd +Subproject commit cd6c6dd62406ddc2fb2b57467dd288ea408b5f16 diff --git a/Maintainer/XTConvert/3rdparty/tex3ds b/Maintainer/XTConvert/3rdparty/tex3ds index 37220c696e..24d27422b5 160000 --- a/Maintainer/XTConvert/3rdparty/tex3ds +++ b/Maintainer/XTConvert/3rdparty/tex3ds @@ -1 +1 @@ -Subproject commit 37220c696e873812df41af62d1db0b20c66661fe +Subproject commit 24d27422b531862cbcf2480b2fda30e9c6aac9e7 From f7247db17aceaf19171ae0ce666e35d75dead42e Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sat, 7 Sep 2024 18:51:15 -0400 Subject: [PATCH 005/286] XTConvert: improve 3DS support --- Maintainer/XTConvert/3rdparty/tex3ds | 2 +- Maintainer/XTConvert/graphics_load.cpp | 8 +- Maintainer/XTConvert/graphics_load.h | 2 +- Maintainer/XTConvert/include/libxtconvert.h | 13 +- Maintainer/XTConvert/libxtconvert.cpp | 213 +++++++++++++++++++- Maintainer/XTConvert/xtconvert_cli.cpp | 2 +- 6 files changed, 227 insertions(+), 13 deletions(-) diff --git a/Maintainer/XTConvert/3rdparty/tex3ds b/Maintainer/XTConvert/3rdparty/tex3ds index 24d27422b5..47a08cc8c1 160000 --- a/Maintainer/XTConvert/3rdparty/tex3ds +++ b/Maintainer/XTConvert/3rdparty/tex3ds @@ -1 +1 @@ -Subproject commit 24d27422b531862cbcf2480b2fda30e9c6aac9e7 +Subproject commit 47a08cc8c1ff396dc93c4962870f51adf790e012 diff --git a/Maintainer/XTConvert/graphics_load.cpp b/Maintainer/XTConvert/graphics_load.cpp index 23ff7ccd63..fbf48a4339 100644 --- a/Maintainer/XTConvert/graphics_load.cpp +++ b/Maintainer/XTConvert/graphics_load.cpp @@ -50,12 +50,10 @@ FIBITMAP* GraphicsLoad::loadImage(const QString& path) return img_rgba; } -FIBITMAP* GraphicsLoad::loadPNGAsMask(const QString& path) +void GraphicsLoad::PNGToMask(FIBITMAP* mask) { - FIBITMAP* mask = loadImage(path); - if(!mask) - return nullptr; + return; // convert alpha channel to luminance unsigned int img_w = FreeImage_GetWidth(mask); @@ -80,8 +78,6 @@ FIBITMAP* GraphicsLoad::loadPNGAsMask(const QString& path) pixel[3] = 0xFF; } } - - return mask; } static constexpr RGBQUAD s_bitblitBG = {0, 0, 0, 0xFF}; diff --git a/Maintainer/XTConvert/graphics_load.h b/Maintainer/XTConvert/graphics_load.h index b26edfb15a..bf21e6f54a 100644 --- a/Maintainer/XTConvert/graphics_load.h +++ b/Maintainer/XTConvert/graphics_load.h @@ -18,7 +18,7 @@ class GraphicsLoad static FIBITMAP* loadImage(const QString& path); - static FIBITMAP* loadPNGAsMask(const QString& path); + static void PNGToMask(FIBITMAP* mask); static void mergeWithMask(FIBITMAP *image, FIBITMAP *mask); diff --git a/Maintainer/XTConvert/include/libxtconvert.h b/Maintainer/XTConvert/include/libxtconvert.h index a6b9642263..5fddca4f4e 100644 --- a/Maintainer/XTConvert/include/libxtconvert.h +++ b/Maintainer/XTConvert/include/libxtconvert.h @@ -5,10 +5,19 @@ namespace XTConvert { +enum class TargetPlatform +{ + Desktop, + TPL, + T3X, + DSG, +}; + struct Spec { - bool target_big_endian = false; - bool target_3ds = false; + TargetPlatform target_platform = TargetPlatform::Desktop; + + bool preserve_bitmask_appearance = true; QString input_dir; QString graphics_dir; diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index a62ce3adbb..3b6662e943 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -9,6 +9,8 @@ #include +#include + #include "archive.h" #include "archive_entry.h" @@ -16,6 +18,8 @@ #include "libromfs3ds.h" #include "libtex3ds.h" +#include "graphics_load.h" + #include "libxtconvert.h" namespace XTConvert @@ -23,8 +27,52 @@ namespace XTConvert class Converter { + struct DirInfo + { + QDir dir; + + QSet textures_1x; + + std::unique_ptr own_graphics_list; // open file to the directory's own graphics list (if any) + QFile* graphics_list = nullptr; // pointer to the correct graphics list to write to (if any) + + enum MakeSizeFiles_t + { + SIZEFILES_NONE = 0, + SIZEFILES_PREVIEW_EPISODE, + SIZEFILES_PREVIEW_ASSETS, + SIZEFILES_ALL, + }; + + MakeSizeFiles_t make_size_files = SIZEFILES_NONE; + bool make_mask_gifs = false; + bool copy_mask_gifs = false; + }; + + DirInfo m_cur_dir; + + void sync_cur_dir(const QString& in_file) + { + QFileInfo fi(in_file); + QDir parent = fi.dir(); + + if(parent.path() == m_cur_dir.dir.path()) + return; + + m_cur_dir = DirInfo(); + m_cur_dir.dir = std::move(parent); + + QStringList path_parts = QDir::toNativeSeparators(m_cur_dir.dir.path()).toLower().split(QDir::separator()); + + // TODO: check for 1x textures + + m_cur_dir.make_size_files = DirInfo::SIZEFILES_ALL; + } + public: QDir m_input_dir; + QDir m_assets_dir; + QTemporaryDir m_temp_dir_owner; QDir m_temp_dir; @@ -32,10 +80,169 @@ class Converter QString m_error; + bool convert_image(const QString& filename, const QString& in_path, const QString& out_path) + { + FIBITMAP* image = GraphicsLoad::loadImage(in_path); + if(!image) + return false; + + FIBITMAP* mask = nullptr; + QString mask_path; + if(filename.endsWith(".gif")) + { + // find the mask!! + QString filename_stem = filename.chopped(4); + auto found = m_cur_dir.dir.entryInfoList({filename_stem + "m.gif"}); + if(!found.isEmpty()) + { + mask_path = found[0].absoluteFilePath(); + mask = GraphicsLoad::loadImage(mask_path); + } + + if(!mask) + mask_path.clear(); + + // keep trying to find mask, in fallback dir and in appropriate graphics dir + for(int i = 0; i < 2; i++) + { + if(mask) + break; + + QString dir_piece = (i == 0) ? "fallback" : filename.split('-')[0]; + auto s = QDir::separator(); + QString dir_to_check = m_assets_dir.path() + s + "graphics" + s + dir_piece + s; + + // look for mask GIF + mask = GraphicsLoad::loadImage(dir_to_check + filename_stem + "m.gif"); + + if(mask) + break; + + // look for PNG + mask = GraphicsLoad::loadImage(dir_to_check + filename_stem + ".png"); + + // convert PNG to mask + if(mask) + GraphicsLoad::PNGToMask(mask); + } + + // Okay, finding a mask failed. Imagine that image is fully opaque in that case, and ignore the mask logic. + } + + TargetPlatform output_format = m_spec.target_platform; + + // find out whether mask is actually needed + if(m_spec.preserve_bitmask_appearance && GraphicsLoad::validateBitmaskRequired(image, mask)) + output_format = TargetPlatform::Desktop; + else if(mask) + GraphicsLoad::mergeWithMask(image, mask); + + // either way we can free the mask bitmap now + if(mask) + { + FreeImage_Unload(mask); + mask = nullptr; + } + + QString used_out_path = out_path; + + // scale the image as needed + int orig_w = FreeImage_GetWidth(image); + int orig_h = FreeImage_GetHeight(image); + + // 2x downscale by default + if(!m_cur_dir.textures_1x.contains(filename)) + { + FIBITMAP* scaled = GraphicsLoad::fast2xScaleDown(image); + FreeImage_Unload(image); + if(!scaled) + return false; + + image = scaled; + } + + // save the image! + bool save_success = false; + if(output_format == TargetPlatform::Desktop) + { + qInfo() << "mask required for" << in_path; + save_success = QFile::copy(in_path, out_path); + + if(save_success && !mask_path.isEmpty()) + save_success = QFile::copy(mask_path, out_path.chopped(4) + "m.gif"); + } + else if(output_format == TargetPlatform::T3X) + { + used_out_path = out_path.chopped(4) + ".t3x"; + + FreeImage_FlipVertical(image); + + Tex3DS::Params params; + params.compression_format = Tex3DS::COMPRESSION_LZ11; + + // image parts + save_success = true; + for(int i = 0; i < 3; i++) + { + int start_y = i * 1024; + int h = FreeImage_GetHeight(image) - start_y; + if(h <= 0) + break; + if(h > 1024) + h = 1024; + + int w = FreeImage_GetWidth(image); + if(w > 1024) + w = 1024; + + int stride = FreeImage_GetPitch(image); + + params.input_img = Tex3DS::Image(w, h); + + if(w * 4 == stride) + memcpy(params.input_img.pixels.data(), FreeImage_GetBits(image) + stride * 1024 * i, params.input_img.pixels.size() * sizeof(Tex3DS::RGBA)); + else + { + for(int row = 0; row < h; row++) + memcpy(params.input_img.pixels.data() + params.input_img.stride * row, FreeImage_GetBits(image) + stride * (1024 * i + row), params.input_img.w * sizeof(Tex3DS::RGBA)); + } + + params.output_path = used_out_path.toUtf8(); + if(i > 0) + params.output_path.push_back('0' + i); + + save_success &= Tex3DS::Process(params); + } + } + + // cleanup the image + FreeImage_Unload(image); + + // finish up by constructing the size information and writing it as needed + QString size_text = QString("%1\n%2\n").arg(orig_w, 4).arg(orig_h, 4); + + // write to graphics list NOW, write to actual size file after deciding output format + if(m_cur_dir.make_size_files && save_success && output_format != TargetPlatform::Desktop) + { + QFile file(used_out_path + ".size"); + if(!file.open(QIODevice::WriteOnly)) + save_success = false; + + file.write(size_text.toUtf8()); + file.close(); + } + + return save_success; + } + bool convert_file(const QString& filename, const QString& in_path, const QString& out_path) { + sync_cur_dir(in_path); + if(filename.endsWith("m.gif")) return true; + else if(m_spec.target_platform != TargetPlatform::Desktop && (filename.endsWith(".png") || filename.endsWith(".gif"))) + return convert_image(filename, in_path, out_path); else { qInfo() << "copying" << out_path; @@ -188,7 +395,7 @@ class Converter FILE* inf = fopen(iso_path.toUtf8().data(), "rb"); FILE* outf = fopen(m_spec.destination.toUtf8().data(), "wb"); - success = LZ4Pack::compress(outf, inf, 4096, m_spec.target_big_endian); + success = LZ4Pack::compress(outf, inf, 4096, m_spec.target_platform == TargetPlatform::TPL); if(inf) fclose(inf); @@ -215,7 +422,7 @@ class Converter bool create_package() { - if(m_spec.target_3ds) + if(m_spec.target_platform == TargetPlatform::T3X) return create_package_romfs3ds(); else return create_package_iso_lz4(); @@ -223,6 +430,8 @@ class Converter bool process() { + GraphicsLoad::initFreeImage(); + m_input_dir.setPath(m_spec.input_dir); if(!build_temp_dir()) diff --git a/Maintainer/XTConvert/xtconvert_cli.cpp b/Maintainer/XTConvert/xtconvert_cli.cpp index f0f3cd5976..9065928c1d 100644 --- a/Maintainer/XTConvert/xtconvert_cli.cpp +++ b/Maintainer/XTConvert/xtconvert_cli.cpp @@ -3,7 +3,7 @@ int main(int argc, char** argv) { XTConvert::Spec s; - // c.m_spec.target_3ds = true; + s.target_platform = XTConvert::TargetPlatform::T3X; s.input_dir = argv[1]; s.destination = argv[2]; From 01655548b99d0da63437b7151edb609329521e2c Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sun, 8 Sep 2024 17:50:57 -0400 Subject: [PATCH 006/286] libxtconvert.cpp: improve fonts support --- Maintainer/XTConvert/libxtconvert.cpp | 82 +++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 3b6662e943..01a17a9dc6 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -47,6 +48,7 @@ class Converter MakeSizeFiles_t make_size_files = SIZEFILES_NONE; bool make_mask_gifs = false; bool copy_mask_gifs = false; + bool convert_font_inis = false; }; DirInfo m_cur_dir; @@ -64,7 +66,31 @@ class Converter QStringList path_parts = QDir::toNativeSeparators(m_cur_dir.dir.path()).toLower().split(QDir::separator()); - // TODO: check for 1x textures + // check for 1x textures: fonts + if(path_parts[path_parts.size() - 1] == "fonts") + { + for(QFileInfo f : m_cur_dir.dir.entryInfoList({"*.ini"})) + { + qInfo() << "Scanning" << f.absoluteFilePath(); + QSettings ini(f.absoluteFilePath(), QSettings::IniFormat); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + ini.setIniCodec("UTF-8"); +#endif + + ini.beginGroup("font-map"); + int texture_scale = ini.value("texture-scale", 1).toInt(); + QString texture_filename = ini.value("texture", "").toString().toLower(); + + qInfo() << "scale" << texture_scale << "fn" << texture_filename; + + if(texture_scale != 1 && !texture_filename.isEmpty()) + m_cur_dir.textures_1x.insert(texture_filename); + } + + // trigger font INI conversion + if(m_spec.target_platform != TargetPlatform::Desktop) + m_cur_dir.convert_font_inis = true; + } m_cur_dir.make_size_files = DirInfo::SIZEFILES_ALL; } @@ -146,9 +172,12 @@ class Converter QString used_out_path = out_path; + // scale the image as needed - int orig_w = FreeImage_GetWidth(image); - int orig_h = FreeImage_GetHeight(image); + + // save these if it becomes necessary + // int orig_w = FreeImage_GetWidth(image); + // int orig_h = FreeImage_GetHeight(image); // 2x downscale by default if(!m_cur_dir.textures_1x.contains(filename)) @@ -161,6 +190,9 @@ class Converter image = scaled; } + int image_h = FreeImage_GetHeight(image); + int image_w = FreeImage_GetWidth(image); + // save the image! bool save_success = false; if(output_format == TargetPlatform::Desktop) @@ -185,13 +217,13 @@ class Converter for(int i = 0; i < 3; i++) { int start_y = i * 1024; - int h = FreeImage_GetHeight(image) - start_y; + int h = image_h - start_y; if(h <= 0) break; if(h > 1024) h = 1024; - int w = FreeImage_GetWidth(image); + int w = image_w; if(w > 1024) w = 1024; @@ -219,7 +251,7 @@ class Converter FreeImage_Unload(image); // finish up by constructing the size information and writing it as needed - QString size_text = QString("%1\n%2\n").arg(orig_w, 4).arg(orig_h, 4); + QString size_text = QString("%1\n%2\n").arg(image_w * 2, 4).arg(image_h * 2, 4); // write to graphics list NOW, write to actual size file after deciding output format if(m_cur_dir.make_size_files && save_success && output_format != TargetPlatform::Desktop) @@ -235,6 +267,42 @@ class Converter return save_success; } + bool convert_font_ini(const QString&, const QString& in_path, const QString& out_path) + { + if(!QFile::copy(in_path, out_path)) + return false; + + QSettings ini(out_path, QSettings::IniFormat); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + ini.setIniCodec("UTF-8"); +#endif + + ini.beginGroup("font-map"); + QString texture_filename = ini.value("texture", "").toString(); + + if(!texture_filename.isEmpty() && (texture_filename.toLower().endsWith(".png") || texture_filename.toLower().endsWith(".gif"))) + { + texture_filename.chop(4); + if(m_spec.target_platform == TargetPlatform::T3X) + texture_filename += ".t3x"; + else if(m_spec.target_platform == TargetPlatform::TPL) + texture_filename += ".tpl"; + else if(m_spec.target_platform == TargetPlatform::DSG) + texture_filename += ".dsg"; + + ini.setValue("texture", texture_filename); + } + + ini.endGroup(); + + ini.sync(); + + if(ini.status() != QSettings::NoError) + return false; + + return true; + } + bool convert_file(const QString& filename, const QString& in_path, const QString& out_path) { sync_cur_dir(in_path); @@ -243,6 +311,8 @@ class Converter return true; else if(m_spec.target_platform != TargetPlatform::Desktop && (filename.endsWith(".png") || filename.endsWith(".gif"))) return convert_image(filename, in_path, out_path); + else if(filename.endsWith(".ini") && m_cur_dir.convert_font_inis) + return convert_font_ini(filename, in_path, out_path); else { qInfo() << "copying" << out_path; From 317aca5ad99d57877993ab2f6ebb6496824cfad7 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:11:02 -0400 Subject: [PATCH 007/286] Rename PNGToMask RGBAToMask for clarity --- Maintainer/XTConvert/graphics_load.cpp | 5 +---- Maintainer/XTConvert/graphics_load.h | 2 +- Maintainer/XTConvert/libxtconvert.cpp | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Maintainer/XTConvert/graphics_load.cpp b/Maintainer/XTConvert/graphics_load.cpp index fbf48a4339..b31c5fdca7 100644 --- a/Maintainer/XTConvert/graphics_load.cpp +++ b/Maintainer/XTConvert/graphics_load.cpp @@ -50,7 +50,7 @@ FIBITMAP* GraphicsLoad::loadImage(const QString& path) return img_rgba; } -void GraphicsLoad::PNGToMask(FIBITMAP* mask) +void GraphicsLoad::RGBAToMask(FIBITMAP* mask) { if(!mask) return; @@ -66,9 +66,6 @@ void GraphicsLoad::PNGToMask(FIBITMAP* mask) { for(unsigned int x = 0; (x < img_w); x++) { - RGBQUAD pix; - FreeImage_GetPixelColor(mask, x, y, &pix); - uint8_t* pixel = &pixel_data[dest_px_stride * y + 4 * x]; uint8_t grey = 0xFF - pixel[3]; diff --git a/Maintainer/XTConvert/graphics_load.h b/Maintainer/XTConvert/graphics_load.h index bf21e6f54a..797fcfc3fd 100644 --- a/Maintainer/XTConvert/graphics_load.h +++ b/Maintainer/XTConvert/graphics_load.h @@ -18,7 +18,7 @@ class GraphicsLoad static FIBITMAP* loadImage(const QString& path); - static void PNGToMask(FIBITMAP* mask); + static void RGBAToMask(FIBITMAP* mask); static void mergeWithMask(FIBITMAP *image, FIBITMAP *mask); diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 01a17a9dc6..7faca1e2a9 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -149,7 +149,7 @@ class Converter // convert PNG to mask if(mask) - GraphicsLoad::PNGToMask(mask); + GraphicsLoad::RGBAToMask(mask); } // Okay, finding a mask failed. Imagine that image is fully opaque in that case, and ignore the mask logic. From e66c7277ace18378f9b7414130203d110f571228 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Mon, 16 Sep 2024 09:17:27 -0400 Subject: [PATCH 008/286] Partial work on DSG graphics conversion --- Maintainer/XTConvert/3rdparty/libimagequant | 2 +- Maintainer/XTConvert/3rdparty/tex3ds | 2 +- Maintainer/XTConvert/CMakeLists.txt | 3 +- Maintainer/XTConvert/libxtconvert.cpp | 124 ++++++++++++++- Maintainer/XTConvert/src/texconv.cpp | 167 ++++++++++++++++++++ Maintainer/XTConvert/src/texconv.h | 73 +++++++++ Maintainer/XTConvert/xtconvert_cli.cpp | 2 +- 7 files changed, 364 insertions(+), 9 deletions(-) create mode 100644 Maintainer/XTConvert/src/texconv.cpp create mode 100644 Maintainer/XTConvert/src/texconv.h diff --git a/Maintainer/XTConvert/3rdparty/libimagequant b/Maintainer/XTConvert/3rdparty/libimagequant index ea735cd9dc..95520cc259 160000 --- a/Maintainer/XTConvert/3rdparty/libimagequant +++ b/Maintainer/XTConvert/3rdparty/libimagequant @@ -1 +1 @@ -Subproject commit ea735cd9dc3dfc5e151fcfc99f8f937c4708af87 +Subproject commit 95520cc2597b1b36265aeb9486060619727852ee diff --git a/Maintainer/XTConvert/3rdparty/tex3ds b/Maintainer/XTConvert/3rdparty/tex3ds index 47a08cc8c1..7f4db105e9 160000 --- a/Maintainer/XTConvert/3rdparty/tex3ds +++ b/Maintainer/XTConvert/3rdparty/tex3ds @@ -1 +1 @@ -Subproject commit 47a08cc8c1ff396dc93c4962870f51adf790e012 +Subproject commit 7f4db105e9f6fdaa187330bb73851f954dda9d59 diff --git a/Maintainer/XTConvert/CMakeLists.txt b/Maintainer/XTConvert/CMakeLists.txt index 7ae2250961..d9cbab7d1d 100644 --- a/Maintainer/XTConvert/CMakeLists.txt +++ b/Maintainer/XTConvert/CMakeLists.txt @@ -8,7 +8,7 @@ add_subdirectory(3rdparty/libimagequant EXCLUDE_FROM_ALL) include(cmake/build_libarchive.cmake) find_package(Qt6 REQUIRED COMPONENTS Core) -add_library(libxtconvert libxtconvert.cpp graphics_load.cpp) +add_library(libxtconvert libxtconvert.cpp graphics_load.cpp src/texconv.cpp) add_dependencies(libxtconvert archive_static lz4_pack_static libromfs3ds libtex3ds FreeImage_Local libpng_Local) @@ -16,6 +16,7 @@ target_link_libraries(libxtconvert PRIVATE Qt6::Core) target_link_libraries(libxtconvert PUBLIC archive_static lz4_pack_static libromfs3ds libtex3ds PGE_FreeImage imagequant_a) target_include_directories(libxtconvert PUBLIC include) +target_include_directories(libxtconvert PRIVATE src) project(xtconvert CXX) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 7faca1e2a9..6e557cc1b6 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -20,6 +20,7 @@ #include "libtex3ds.h" #include "graphics_load.h" +#include "texconv.h" #include "libxtconvert.h" @@ -173,6 +174,9 @@ class Converter QString used_out_path = out_path; + // FIXME: save mask fallback here if we are in the global graphics dir + + // scale the image as needed // save these if it becomes necessary @@ -195,6 +199,7 @@ class Converter // save the image! bool save_success = false; + QString size_text; if(output_format == TargetPlatform::Desktop) { qInfo() << "mask required for" << in_path; @@ -205,6 +210,8 @@ class Converter } else if(output_format == TargetPlatform::T3X) { + // break these into their own functions + used_out_path = out_path.chopped(4) + ".t3x"; FreeImage_FlipVertical(image); @@ -239,22 +246,127 @@ class Converter memcpy(params.input_img.pixels.data() + params.input_img.stride * row, FreeImage_GetBits(image) + stride * (1024 * i + row), params.input_img.w * sizeof(Tex3DS::RGBA)); } + // FIXME: check if possible to do 5551 here, or possibly just do 5551 by force (maybe unless there's intermediate opacity?) + params.output_path = used_out_path.toUtf8(); if(i > 0) params.output_path.push_back('0' + i); save_success &= Tex3DS::Process(params); } + + size_text = QString("%1\n%2\n").arg(image_w * 2, 4).arg(image_h * 2, 4); + } + else if(output_format == TargetPlatform::TPL) + { + } + else if(output_format == TargetPlatform::DSG) + { + used_out_path = out_path.chopped(4) + ".dsg"; + + int flags = 0; + + // FIXME: move this out of the DSG-specific code + if(filename.startsWith("mario-") || filename.startsWith("luigi-") || filename.startsWith("peach-") || filename.startsWith("toad-") || filename.startsWith("link-")) + { + if(shrink_player_texture(&image, filename.startsWith("link-"))) + { + image_w = FreeImage_GetWidth(image); + image_h = FreeImage_GetHeight(image); + } + } + + // downscale image further, and threshold alpha, here. + int container_w = next_power_of_2(image_w); + int container_h = next_power_of_2(image_h); + + while((container_w * container_h) > 65536) + { + // log a warning here + flags++; + container_w /= 2; + container_h /= 2; + } + + if(flags) + { + FIBITMAP* scaled = FreeImage_Rescale(image, image_w / (1 << flags), image_h / (1 << flags), FILTER_BILINEAR); + + FreeImage_Unload(image); + + if(!scaled) + return false; + + image = scaled; + } + + // palettize texture + int input_w = FreeImage_GetWidth(image); + int input_h = FreeImage_GetHeight(image); + PaletteTex p(FreeImage_GetBits(image), input_w, input_h); + if(!p.convert(PaletteTex::HALF, 0)) + { + FreeImage_Unload(image); + return false; + } + + // save the image here + QByteArray dsg_data; + dsg_data.resize(32 + container_w * container_h / 2); + + // save palette + for(int i = 0; i < 16; ++i) + { + liq_color c = p.palette()[i]; + uint8_t* dest = reinterpret_cast(&dsg_data[i * 2]); + + write_uint16_le(dest, color_to_rgb5a1(c)); + } + + bool fully_opaque = (p.palette()[0].a == 255); + if(fully_opaque) + flags |= 16; + + // save image indexes + const uint8_t* src = p.indexes(); + uint8_t* out_base = reinterpret_cast(&dsg_data[32]); + int out_stride = container_w / 2; + for(int row = 0; row < input_h; row++) + { + uint8_t* dest = out_base + (input_h - 1 - row) * out_stride; + + int col; + for(col = 0; col < input_w - 1; col += 2) + { + *(dest++) = (src[0] << 0) | (src[1] << 4); + src += 2; + } + + if(col < input_w) + { + *(dest++) = (src[0] << 4); + src += 1; + } + } + + QFile file(used_out_path); + if(!file.open(QIODevice::WriteOnly)) + save_success = false; + else + { + file.write(dsg_data); + file.close(); + save_success = true; + + size_text = QString("%1\n%2\n%3\n").arg(image_w * 2, 4).arg(image_h * 2, 4).arg(flags); + } } // cleanup the image FreeImage_Unload(image); - // finish up by constructing the size information and writing it as needed - QString size_text = QString("%1\n%2\n").arg(image_w * 2, 4).arg(image_h * 2, 4); - - // write to graphics list NOW, write to actual size file after deciding output format - if(m_cur_dir.make_size_files && save_success && output_format != TargetPlatform::Desktop) + // write to size file if needed + if(m_cur_dir.make_size_files && save_success && !size_text.isEmpty()) { QFile file(used_out_path + ".size"); if(!file.open(QIODevice::WriteOnly)) @@ -264,6 +376,8 @@ class Converter file.close(); } + // FIXME: write to graphics list NOW + return save_success; } diff --git a/Maintainer/XTConvert/src/texconv.cpp b/Maintainer/XTConvert/src/texconv.cpp new file mode 100644 index 0000000000..74633e613b --- /dev/null +++ b/Maintainer/XTConvert/src/texconv.cpp @@ -0,0 +1,167 @@ +#include + +#include "texconv.h" + +namespace XTConvert +{ + +void write_uint16_le(uint8_t* dest, uint16_t src) +{ + dest[0] = uint8_t(src >> 0); + dest[1] = uint8_t(src >> 8); +} + +uint16_t color_to_rgb5a1(liq_color color) +{ + return (uint16_t(color.a >= 128) << 15) + | (uint16_t(color.r >> 3) << 10) + | (uint16_t(color.g >> 3) << 5) + | (uint16_t(color.b >> 3) << 0); +} + +static bool s_blit(FIBITMAP* dest, FIBITMAP* src, int sx, int sy, int dx, int dy, int w, int h) +{ + FIBITMAP* view = FreeImage_CreateView(src, sx, sy, sx + w, sy + h); + if(!view) + return false; + + bool succ = FreeImage_Paste(dest, view, dx, dy, 255); + FreeImage_Unload(view); + + return succ; +} + +bool shrink_player_texture(FIBITMAP** image, bool char5) +{ + const int orig_frame_w = 100; + const int orig_frame_h = 100; + const int orig_cols = 10; + const int orig_rows = 10; + + const int new_cols = 0; +// def do_sheet(fn, frames, out, w, h, cols, rows, make_flippable=False): +// ow = 100 +// oh = 100 +// ocols = 10 +// orows = 10 + +// cmd = f'convert -size {w*cols}x{h*rows*2} xc:none -compose Replace -gravity NorthWest -define trim:edges=east ' + +// for df, sf in enumerate(frames): +// for dir in [-1, 1]: +// o_coord = dir * sf + 49 +// sx = ow * (o_coord // orows) +// sy = oh * (o_coord % orows) + +// dx = w * (df // rows) +// dy = h * (df % rows) + +// flip = False + +// dw = w + +// if dir == -1: +// dx = (cols - 1) * w - dx +// dy += rows * h + +// if make_flippable: +// flip = (sf == 0) + +// if not flip: +// trim = check_trim(fn, sx, sy, w, h) +// print(f'lo-{sf} = {trim}') +// dx += trim +// dw -= trim + +// if sf == 25 or sf == 26: +// flip = True +// elif sf == 0: +// continue + +// cmd += blit(fn, sx, sy, dx, dy, dw, h, flip) + + return true; +} + +PaletteTex::PaletteTex(const uint8_t* pixels_rgba, int width, int height) +{ + m_attr = liq_attr_create(); + if(!m_attr) + return; + + liq_set_min_posterization(m_attr, 3); + + if(!pixels_rgba) + return; + + m_image = liq_image_create_rgba(m_attr, pixels_rgba, width, height, 0); +} + +PaletteTex::~PaletteTex() +{ + if(m_result) + { + liq_result_destroy(m_result); + m_result = nullptr; + } + + if(m_image) + { + liq_image_destroy(m_image); + m_image = nullptr; + } + + if(m_attr) + { + liq_attr_destroy(m_attr); + m_attr = nullptr; + } +} + +bool PaletteTex::convert(PaletteSize size, int min_quality) +{ + if(!m_attr || !m_image || min_quality < 0 || min_quality > 100) + return false; + + if(m_result) + { + liq_result_destroy(m_result); + m_result = nullptr; + } + + if(size == HALF) + liq_set_max_colors(m_attr, 16); + else if(size == BYTE) + liq_set_max_colors(m_attr, 256); + + liq_set_quality(m_attr, min_quality, 100); + + if(liq_image_quantize(m_image, m_attr, &m_result) != LIQ_OK) + return false; + + liq_set_dithering_level(m_result, 0); + + m_indexes.resize(liq_image_get_height(m_image) * liq_image_get_width(m_image)); + + if(liq_write_remapped_image(m_result, m_image, m_indexes.data(), m_indexes.size()) != LIQ_OK) + { + m_indexes.clear(); + return false; + } + + const liq_palette* palette = liq_get_palette(m_result); + if(size == HALF) + m_palette.resize(16); + else + m_palette.resize(256); + + if(palette->count > m_palette.size()) + return false; + + for(size_t i = 0; i < palette->count; i++) + m_palette[i] = palette->entries[i]; + + return true; +} + +} // namespace XTConvert diff --git a/Maintainer/XTConvert/src/texconv.h b/Maintainer/XTConvert/src/texconv.h new file mode 100644 index 0000000000..604c74f37a --- /dev/null +++ b/Maintainer/XTConvert/src/texconv.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include + +#include "libimagequant.h" + +struct FIBITMAP; + +namespace XTConvert +{ + +enum TPLConversion +{ + RGBA8_TO_RGB5A3, + CI8_TO_CI4, + CI8_TO_CI8, +}; + +static inline int next_power_of_2(int val) +{ + int power = 8; + while(power < val) + power *= 2; + return power; +} + +void write_uint16_le(uint8_t* dest, uint16_t src); +uint16_t color_to_rgb5a1(liq_color color); +bool swizzle_tpl(std::vector& out, const uint8_t* in, int in_width, int in_height, TPLConversion format); + +bool shrink_player_texture(FIBITMAP** image, bool char5); + +struct PaletteTex +{ +private: + liq_attr* m_attr = nullptr; + liq_image* m_image = nullptr; + liq_result* m_result = nullptr; + + std::vector m_palette; + std::vector m_indexes; + +public: + enum PaletteSize + { + HALF, + BYTE, + }; + + PaletteTex(const uint8_t* pixels_rgba, int width, int height); + ~PaletteTex(); + + bool convert(PaletteSize size, int min_quality); + + const liq_color* palette() const + { + if(m_palette.empty()) + return nullptr; + + return m_palette.data(); + } + + const uint8_t* indexes() const + { + if(m_indexes.empty()) + return nullptr; + + return m_indexes.data(); + } +}; + +} // namespace XTConvert diff --git a/Maintainer/XTConvert/xtconvert_cli.cpp b/Maintainer/XTConvert/xtconvert_cli.cpp index 9065928c1d..8d2a6ea984 100644 --- a/Maintainer/XTConvert/xtconvert_cli.cpp +++ b/Maintainer/XTConvert/xtconvert_cli.cpp @@ -3,7 +3,7 @@ int main(int argc, char** argv) { XTConvert::Spec s; - s.target_platform = XTConvert::TargetPlatform::T3X; + s.target_platform = XTConvert::TargetPlatform::DSG; s.input_dir = argv[1]; s.destination = argv[2]; From 58988014e88f6f563a5f08fca26a299e69a085bf Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sun, 22 Sep 2024 22:28:19 -0400 Subject: [PATCH 009/286] texconv.cpp: implement player texture shrink logic --- Maintainer/XTConvert/src/texconv.cpp | 75 +++++++++++++++------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/Maintainer/XTConvert/src/texconv.cpp b/Maintainer/XTConvert/src/texconv.cpp index 74633e613b..3723f0b8e0 100644 --- a/Maintainer/XTConvert/src/texconv.cpp +++ b/Maintainer/XTConvert/src/texconv.cpp @@ -33,52 +33,57 @@ static bool s_blit(FIBITMAP* dest, FIBITMAP* src, int sx, int sy, int dx, int dy bool shrink_player_texture(FIBITMAP** image, bool char5) { - const int orig_frame_w = 100; - const int orig_frame_h = 100; - const int orig_cols = 10; - const int orig_rows = 10; + const int source_frame_w = 50; + const int source_frame_h = 50; + const int source_cols = 10; + const int source_rows = 10; - const int new_cols = 0; -// def do_sheet(fn, frames, out, w, h, cols, rows, make_flippable=False): -// ow = 100 -// oh = 100 -// ocols = 10 -// orows = 10 + const int dest_frame_w = (char5) ? 32 : 24; + const int dest_frame_h = 32; + const int dest_cols = (char5) ? 4 : 10; + const int dest_rows_per_dir = 4; + const int dest_rows = dest_rows_per_dir * 2; -// cmd = f'convert -size {w*cols}x{h*rows*2} xc:none -compose Replace -gravity NorthWest -define trim:edges=east ' + FIBITMAP* source_image = *image; -// for df, sf in enumerate(frames): -// for dir in [-1, 1]: -// o_coord = dir * sf + 49 -// sx = ow * (o_coord // orows) -// sy = oh * (o_coord % orows) + if(FreeImage_GetWidth(source_image) != source_frame_w * source_cols || FreeImage_GetHeight(source_image) != source_frame_h * source_rows) + return false; + + FIBITMAP* dest_image = FreeImage_AllocateT(FIT_BITMAP, dest_frame_w * dest_cols, dest_frame_h * dest_rows, 32); + + if(!dest_image) + return false; -// dx = w * (df // rows) -// dy = h * (df % rows) + int copy_frame_count = (char5) ? 16 : 38; -// flip = False + for(int dest_frame = 0; dest_frame < copy_frame_count; dest_frame++) + { + int source_frame = (dest_frame < 33) ? dest_frame : dest_frame + 7; -// dw = w + if(char5) + source_frame += 1; -// if dir == -1: -// dx = (cols - 1) * w - dx -// dy += rows * h + for(int dir = -1; dir <= 1; dir += 2) + { + int source_frame_res = dir * source_frame + 49; + int source_x = source_frame_w * (source_frame_res / source_rows); + int source_y = source_frame_h * (source_frame_res % source_rows); -// if make_flippable: -// flip = (sf == 0) + int dest_x = dest_frame_w * (dest_frame / dest_rows_per_dir); + int dest_y = dest_frame_h * (dest_frame % dest_rows_per_dir); -// if not flip: -// trim = check_trim(fn, sx, sy, w, h) -// print(f'lo-{sf} = {trim}') -// dx += trim -// dw -= trim + if(dir == -1) + { + dest_x = (dest_cols - 1) * dest_frame_w - dest_x; + dest_y += dest_rows_per_dir * dest_frame_h; + } -// if sf == 25 or sf == 26: -// flip = True -// elif sf == 0: -// continue + s_blit(dest_image, source_image, source_x, source_y, dest_x, dest_y, dest_frame_w, dest_frame_h); + } + } -// cmd += blit(fn, sx, sy, dx, dy, dw, h, flip) + FreeImage_Unload(source_image); + *image = dest_image; return true; } From b5658b1365ebea36503bbc6b9c4bcc556cbb2614 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Mon, 16 Sep 2024 09:17:27 -0400 Subject: [PATCH 010/286] Continued work on DSG graphics conversion --- Maintainer/XTConvert/libxtconvert.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 6e557cc1b6..7f934e335c 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -280,12 +280,12 @@ class Converter int container_w = next_power_of_2(image_w); int container_h = next_power_of_2(image_h); - while((container_w * container_h) > 65536) + while((container_w * container_h) / 2 > 131072) { // log a warning here flags++; - container_w /= 2; - container_h /= 2; + container_w = next_power_of_2(image_w / (1 << flags)); + container_h = next_power_of_2(image_h / (1 << flags)); } if(flags) From bac34d53aaccc2369dc37252c996d9b30a6fc154 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:45:56 -0400 Subject: [PATCH 011/286] Initial support for TPL graphics conversion --- Maintainer/XTConvert/CMakeLists.txt | 7 +- Maintainer/XTConvert/libxtconvert.cpp | 53 +++++++ Maintainer/XTConvert/src/export_tpl.cpp | 178 ++++++++++++++++++++++++ Maintainer/XTConvert/src/export_tpl.h | 20 +++ Maintainer/XTConvert/src/texconv.h | 8 -- Maintainer/XTConvert/xtconvert_cli.cpp | 2 +- 6 files changed, 258 insertions(+), 10 deletions(-) create mode 100644 Maintainer/XTConvert/src/export_tpl.cpp create mode 100644 Maintainer/XTConvert/src/export_tpl.h diff --git a/Maintainer/XTConvert/CMakeLists.txt b/Maintainer/XTConvert/CMakeLists.txt index d9cbab7d1d..d7d4e2e5f5 100644 --- a/Maintainer/XTConvert/CMakeLists.txt +++ b/Maintainer/XTConvert/CMakeLists.txt @@ -8,7 +8,12 @@ add_subdirectory(3rdparty/libimagequant EXCLUDE_FROM_ALL) include(cmake/build_libarchive.cmake) find_package(Qt6 REQUIRED COMPONENTS Core) -add_library(libxtconvert libxtconvert.cpp graphics_load.cpp src/texconv.cpp) +add_library(libxtconvert + libxtconvert.cpp + graphics_load.cpp + src/texconv.cpp + src/export_tpl.cpp +) add_dependencies(libxtconvert archive_static lz4_pack_static libromfs3ds libtex3ds FreeImage_Local libpng_Local) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 7f934e335c..de8f9b12f4 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -21,6 +21,7 @@ #include "graphics_load.h" #include "texconv.h" +#include "export_tpl.h" #include "libxtconvert.h" @@ -259,6 +260,58 @@ class Converter } else if(output_format == TargetPlatform::TPL) { + used_out_path = out_path.chopped(4) + ".tpl"; + + FreeImage_FlipVertical(image); + + // palettize texture + int input_w = FreeImage_GetWidth(image); + int input_h_full = FreeImage_GetHeight(image); + int stride = FreeImage_GetPitch(image); + + save_success = false; + + for(int i = 0; i < 3; i++) + { + int start_y = i * 1024; + int input_h = input_h_full - start_y; + if(input_h <= 0) + break; + if(input_h > 1024) + input_h = 1024; + + PaletteTex p(FreeImage_GetBits(image) + stride * start_y, input_w, input_h); + + PaletteTex::PaletteSize used_size = PaletteTex::HALF; + if(!p.convert(used_size, 90)) + { + used_size = PaletteTex::BYTE; + + if(!p.convert(used_size, 0)) + break; + } + + QByteArray tpl_data; + tpl_data.resize(ExportTPL::data_size(used_size, input_w, input_h)); + ExportTPL::fill_data(reinterpret_cast(&tpl_data[0]), used_size, input_w, input_h, p.palette(), p.indexes()); + + auto tpl_path = used_out_path; + if(i > 0) + tpl_path += char('0' + i); + + QFile file(tpl_path); + if(!file.open(QIODevice::WriteOnly)) + break; + else + { + file.write(tpl_data); + file.close(); + save_success = true; + } + } + + if(save_success) + size_text = QString("%1\n%2\n").arg(image_w * 2, 4).arg(image_h * 2, 4); } else if(output_format == TargetPlatform::DSG) { diff --git a/Maintainer/XTConvert/src/export_tpl.cpp b/Maintainer/XTConvert/src/export_tpl.cpp new file mode 100644 index 0000000000..faf81c2271 --- /dev/null +++ b/Maintainer/XTConvert/src/export_tpl.cpp @@ -0,0 +1,178 @@ +#include + +#include "export_tpl.h" + +namespace XTConvert +{ + +namespace ExportTPL +{ + +void write_uint16_be(uint8_t* dest, uint16_t src) +{ + dest[0] = uint8_t(src >> 8); + dest[1] = uint8_t(src >> 0); +} + +uint16_t color_to_rgb5a3(liq_color color) +{ + if(color.a < 0xf8) + { + return (uint16_t(color.a >> 5) << 12) + | (uint16_t(color.b >> 4) << 8) + | (uint16_t(color.g >> 4) << 4) + | (uint16_t(color.r >> 4) << 0); + } + else + { + return (uint16_t(1) << 15) + | (uint16_t(color.b >> 3) << 10) + | (uint16_t(color.g >> 3) << 5) + | (uint16_t(color.r >> 3) << 0); + } +} + +size_t data_size(PaletteTex::PaletteSize index_size, int input_w, int input_h) +{ + size_t indexes_per_byte = (index_size == PaletteTex::HALF) ? 2 : 1; + + size_t img_height = next_power_of_2(input_h); + size_t img_width = next_power_of_2(input_w); + + if(img_width > 1024) + img_width = 1024; + + size_t file_hdr = 0x60; + size_t img_data = img_height * img_width / indexes_per_byte; + size_t pal_data = (index_size == PaletteTex::HALF) ? 0x20 : 0x200; + + return file_hdr + img_data + pal_data; +} + +void fill_data(uint8_t* out, PaletteTex::PaletteSize index_size, int input_w, int input_h, const liq_color* palette, const uint8_t* indexes) +{ + size_t indexes_per_byte = (index_size == PaletteTex::HALF) ? 2 : 1; + + size_t img_height = (input_h + 7) / 8 * 8; + size_t img_width = (input_w + 7) / 8 * 8; + + if(img_width > 1024) + img_width = 1024; + + size_t file_hdr = 0x60; + size_t img_data = img_height * img_width / indexes_per_byte; + size_t pal_data = (index_size == PaletteTex::HALF) ? 0x20 : 0x200; + + // calculations for headers + size_t pal_dat_off = file_hdr + img_data; + uint8_t pdo1 = uint8_t(pal_dat_off >> 24); + uint8_t pdo2 = uint8_t(pal_dat_off >> 16); + uint8_t pdo3 = uint8_t(pal_dat_off >> 8); + uint8_t pdo4 = uint8_t(pal_dat_off >> 0); + + size_t palette_entry_count = (index_size == PaletteTex::HALF) ? 16 : 256; + uint8_t pec1 = palette_entry_count / 0x100; + uint8_t pec2 = palette_entry_count & 0xff; + + uint8_t ihe1 = img_height / 0x100; + uint8_t ihe2 = img_height & 0xff; + + uint8_t iwi1 = img_width / 0x100; + uint8_t iwi2 = img_width & 0xff; + + uint8_t ifmt = (index_size == PaletteTex::HALF) ? 0x08 : 0x09; + + // file header template + const uint8_t file_header[0x60] = { + 0x00, 0x20, 0xaf, 0x30, // magic number + 0x00, 0x00, 0x00, 0x01, // image count + 0x00, 0x00, 0x00, 0x0c, // image offset table offset + 0x00, 0x00, 0x00, 0x14, // image header offset + + 0x00, 0x00, 0x00, 0x40, // palette header offset + ihe1, ihe2, iwi1, iwi2, // [[image header]] image dimensions + 0x00, 0x00, 0x00, ifmt, // image format + 0x00, 0x00, 0x00, 0x60, // image data offset + + 0x00, 0x00, 0x00, 0x00, // wrap s + 0x00, 0x00, 0x00, 0x00, // wrap t + 0x00, 0x00, 0x00, 0x01, // min filter + 0x00, 0x00, 0x00, 0x01, // mag filter + + 0x00, 0x00, 0x00, 0x00, // LOD bias + 0x00, 0x00, 0x00, 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // padding + 0x00, 0x00, 0x00, 0x00, // padding + + pec1, pec2, 0x00, 0x00, // palette header (pt 1) + 0x00, 0x00, 0x00, 0x02, // palette format + pdo1, pdo2, pdo3, pdo4, // palette data offset + + // PADDING + }; + + memcpy(out, file_header, 0x60); + + // palette data + { + uint8_t* pal_out = out + pal_dat_off; + + for(unsigned pal_i = 0; pal_i < palette_entry_count; pal_i++) + write_uint16_be(pal_out + pal_i * 2, color_to_rgb5a3(palette[pal_i])); + } + + // image data + { + uint8_t* img_out = out + 0x60; + + uint8_t* dest = img_out; + + int blk_width = 8; + int blk_stride = (index_size == PaletteTex::HALF) ? 4 : 8; + int blk_height = (index_size == PaletteTex::HALF) ? 8 : 4; + + for(int start_y = 0; start_y < (int)img_height; start_y += blk_height) + { + for(int start_x = 0; start_x < (int)img_width; start_x += blk_width) + { + for(int dy = 0; dy < blk_height; dy++) + { + if(start_y + dy >= input_h) + { + dest += blk_stride; + continue; + } + + const uint8_t* src = indexes + (start_y + dy) * input_w + start_x; + + if(index_size == PaletteTex::HALF) + { + for(int dx = 0; dx < blk_width; dx += 2) + { + if(start_x + dx + 1 > input_w) + dest++; + else if(start_x + dx + 1 == input_w) + *(dest++) = (src[dx] << 4); + else + *(dest++) = (src[dx + 0] << 4) | (src[dx + 1] << 0); + } + } + else + { + for(int dx = 0; dx < blk_width; dx++) + { + if(start_x + dx >= input_w) + dest++; + else + *(dest++) = src[dx]; + } + } + } + } + } + } +} + +} // namespace ExportTPL + +} // namespace XTConvert diff --git a/Maintainer/XTConvert/src/export_tpl.h b/Maintainer/XTConvert/src/export_tpl.h new file mode 100644 index 0000000000..63baace93f --- /dev/null +++ b/Maintainer/XTConvert/src/export_tpl.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include "texconv.h" + +namespace XTConvert +{ + +namespace ExportTPL +{ + +size_t data_size(PaletteTex::PaletteSize index_size, int input_w, int input_h); + +void fill_data(uint8_t* out, PaletteTex::PaletteSize index_size, int input_w, int input_h, const liq_color* palette, const uint8_t* indexes); + +} // namespace ExportTPL + +} // namespace XTConvert diff --git a/Maintainer/XTConvert/src/texconv.h b/Maintainer/XTConvert/src/texconv.h index 604c74f37a..f1105a7a1f 100644 --- a/Maintainer/XTConvert/src/texconv.h +++ b/Maintainer/XTConvert/src/texconv.h @@ -10,13 +10,6 @@ struct FIBITMAP; namespace XTConvert { -enum TPLConversion -{ - RGBA8_TO_RGB5A3, - CI8_TO_CI4, - CI8_TO_CI8, -}; - static inline int next_power_of_2(int val) { int power = 8; @@ -27,7 +20,6 @@ static inline int next_power_of_2(int val) void write_uint16_le(uint8_t* dest, uint16_t src); uint16_t color_to_rgb5a1(liq_color color); -bool swizzle_tpl(std::vector& out, const uint8_t* in, int in_width, int in_height, TPLConversion format); bool shrink_player_texture(FIBITMAP** image, bool char5); diff --git a/Maintainer/XTConvert/xtconvert_cli.cpp b/Maintainer/XTConvert/xtconvert_cli.cpp index 8d2a6ea984..929d8bb29b 100644 --- a/Maintainer/XTConvert/xtconvert_cli.cpp +++ b/Maintainer/XTConvert/xtconvert_cli.cpp @@ -3,7 +3,7 @@ int main(int argc, char** argv) { XTConvert::Spec s; - s.target_platform = XTConvert::TargetPlatform::DSG; + s.target_platform = XTConvert::TargetPlatform::TPL; s.input_dir = argv[1]; s.destination = argv[2]; From 3a86e3a6d0af9de4de66bee9858d1a47c7ef3e71 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:14:15 -0400 Subject: [PATCH 012/286] Remove unused gamecube-tools submodule --- .gitmodules | 3 --- Maintainer/XTConvert/3rdparty/gamecube-tools | 1 - 2 files changed, 4 deletions(-) delete mode 160000 Maintainer/XTConvert/3rdparty/gamecube-tools diff --git a/.gitmodules b/.gitmodules index 8569720fe8..63469006ab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -62,9 +62,6 @@ [submodule "XTConvert/libarchive"] path = Maintainer/XTConvert/3rdparty/libarchive url = https://github.com/WohlSoft/xtconvert-libarchive.git -[submodule "XTConvert/gamecube-tools"] - path = Maintainer/XTConvert/3rdparty/gamecube-tools - url = https://github.com/WohlSoft/xtconvert-gamecube-tools.git [submodule "XTConvert/mbediso"] path = Maintainer/XTConvert/3rdparty/mbediso url = https://github.com/ds-sloth/mbediso.git diff --git a/Maintainer/XTConvert/3rdparty/gamecube-tools b/Maintainer/XTConvert/3rdparty/gamecube-tools deleted file mode 160000 index 609dc7884e..0000000000 --- a/Maintainer/XTConvert/3rdparty/gamecube-tools +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 609dc7884eae148ee725c93b3d78d69902f69a0c From 416b33beea3602a43dccba0315335b3c9824ebea Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:32:52 -0400 Subject: [PATCH 013/286] Use spc2it library for 3DS --- Maintainer/XTConvert/3rdparty/spc2it | 2 +- Maintainer/XTConvert/CMakeLists.txt | 3 ++- Maintainer/XTConvert/libxtconvert.cpp | 6 ++++++ Maintainer/XTConvert/xtconvert_cli.cpp | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Maintainer/XTConvert/3rdparty/spc2it b/Maintainer/XTConvert/3rdparty/spc2it index 748b8054fe..b70316eaab 160000 --- a/Maintainer/XTConvert/3rdparty/spc2it +++ b/Maintainer/XTConvert/3rdparty/spc2it @@ -1 +1 @@ -Subproject commit 748b8054fef23ad9f4f0edd36566bbc8082a5ba8 +Subproject commit b70316eaab76100aa8e392910a04017976087f15 diff --git a/Maintainer/XTConvert/CMakeLists.txt b/Maintainer/XTConvert/CMakeLists.txt index d7d4e2e5f5..b0fde3eacf 100644 --- a/Maintainer/XTConvert/CMakeLists.txt +++ b/Maintainer/XTConvert/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(3rdparty/mbediso EXCLUDE_FROM_ALL) add_subdirectory(3rdparty/3dstools EXCLUDE_FROM_ALL) add_subdirectory(3rdparty/tex3ds EXCLUDE_FROM_ALL) add_subdirectory(3rdparty/libimagequant EXCLUDE_FROM_ALL) +add_subdirectory(3rdparty/spc2it EXCLUDE_FROM_ALL) include(cmake/build_libarchive.cmake) find_package(Qt6 REQUIRED COMPONENTS Core) @@ -18,7 +19,7 @@ add_library(libxtconvert add_dependencies(libxtconvert archive_static lz4_pack_static libromfs3ds libtex3ds FreeImage_Local libpng_Local) target_link_libraries(libxtconvert PRIVATE Qt6::Core) -target_link_libraries(libxtconvert PUBLIC archive_static lz4_pack_static libromfs3ds libtex3ds PGE_FreeImage imagequant_a) +target_link_libraries(libxtconvert PUBLIC archive_static lz4_pack_static libromfs3ds libtex3ds PGE_FreeImage imagequant_a libspc2it) target_include_directories(libxtconvert PUBLIC include) target_include_directories(libxtconvert PRIVATE src) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index de8f9b12f4..10f2797779 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -18,6 +18,7 @@ #include "lz4_pack.h" #include "libromfs3ds.h" #include "libtex3ds.h" +#include "spc2it.h" #include "graphics_load.h" #include "texconv.h" @@ -480,6 +481,11 @@ class Converter return convert_image(filename, in_path, out_path); else if(filename.endsWith(".ini") && m_cur_dir.convert_font_inis) return convert_font_ini(filename, in_path, out_path); + else if(filename.endsWith(".spc") && m_spec.target_platform == TargetPlatform::T3X) + { + qInfo() << "spc2it" << out_path; + return spc2it_convert(in_path.toUtf8().data(), out_path.toUtf8().data()) == 0; + } else { qInfo() << "copying" << out_path; diff --git a/Maintainer/XTConvert/xtconvert_cli.cpp b/Maintainer/XTConvert/xtconvert_cli.cpp index 929d8bb29b..9065928c1d 100644 --- a/Maintainer/XTConvert/xtconvert_cli.cpp +++ b/Maintainer/XTConvert/xtconvert_cli.cpp @@ -3,7 +3,7 @@ int main(int argc, char** argv) { XTConvert::Spec s; - s.target_platform = XTConvert::TargetPlatform::TPL; + s.target_platform = XTConvert::TargetPlatform::T3X; s.input_dir = argv[1]; s.destination = argv[2]; From 7195151fe22f931bb7c9c649d1e8502c11b2de71 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Fri, 8 Nov 2024 22:11:35 -0500 Subject: [PATCH 014/286] XTConvert: initial work on ogg2wav support --- Maintainer/XTConvert/CMakeLists.txt | 9 +- Maintainer/XTConvert/graphics_load.cpp | 4 +- Maintainer/XTConvert/graphics_load.h | 15 ++-- Maintainer/XTConvert/libxtconvert.cpp | 107 +++++++++++++++++++++++- Maintainer/XTConvert/src/export_wav.cpp | 51 +++++++++++ Maintainer/XTConvert/src/export_wav.h | 17 ++++ 6 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 Maintainer/XTConvert/src/export_wav.cpp create mode 100644 Maintainer/XTConvert/src/export_wav.h diff --git a/Maintainer/XTConvert/CMakeLists.txt b/Maintainer/XTConvert/CMakeLists.txt index b0fde3eacf..6850263775 100644 --- a/Maintainer/XTConvert/CMakeLists.txt +++ b/Maintainer/XTConvert/CMakeLists.txt @@ -14,13 +14,20 @@ add_library(libxtconvert graphics_load.cpp src/texconv.cpp src/export_tpl.cpp + src/export_wav.cpp ) -add_dependencies(libxtconvert archive_static lz4_pack_static libromfs3ds libtex3ds FreeImage_Local libpng_Local) +add_dependencies(libxtconvert archive_static lz4_pack_static libromfs3ds libtex3ds FreeImage_Local libpng_Local SDLMixerX_Local SDL2_Local) target_link_libraries(libxtconvert PRIVATE Qt6::Core) target_link_libraries(libxtconvert PUBLIC archive_static lz4_pack_static libromfs3ds libtex3ds PGE_FreeImage imagequant_a libspc2it) +if(PGE_STATIC_SDLMIXER) + target_link_libraries(libxtconvert PUBLIC PGE_SDLMixerX_static PGE_SDL2_static) +else() + target_link_libraries(libxtconvert PUBLIC PGE_SDLMixerX PGE_SDL2) +endif() + target_include_directories(libxtconvert PUBLIC include) target_include_directories(libxtconvert PRIVATE src) diff --git a/Maintainer/XTConvert/graphics_load.cpp b/Maintainer/XTConvert/graphics_load.cpp index b31c5fdca7..e535c3bd40 100644 --- a/Maintainer/XTConvert/graphics_load.cpp +++ b/Maintainer/XTConvert/graphics_load.cpp @@ -5,12 +5,12 @@ #include "graphics_load.h" -void GraphicsLoad::initFreeImage() +GraphicsLoad::FreeImage_Sentinel::FreeImage_Sentinel() { FreeImage_Initialise(); } -void GraphicsLoad::closeFreeImage() +GraphicsLoad::FreeImage_Sentinel::~FreeImage_Sentinel() { FreeImage_DeInitialise(); } diff --git a/Maintainer/XTConvert/graphics_load.h b/Maintainer/XTConvert/graphics_load.h index 797fcfc3fd..8a3e78fd45 100644 --- a/Maintainer/XTConvert/graphics_load.h +++ b/Maintainer/XTConvert/graphics_load.h @@ -6,15 +6,12 @@ struct FIBITMAP; class GraphicsLoad { public: - /*! - * \brief Initializes FreeImage - */ - static void initFreeImage(); - - /*! - * \brief DeInitializes FreeImage - */ - static void closeFreeImage(); + // ensures that FreeImage is inited while alive + struct FreeImage_Sentinel + { + FreeImage_Sentinel(); + ~FreeImage_Sentinel(); + }; static FIBITMAP* loadImage(const QString& path); diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 10f2797779..3a27f64ad1 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -12,6 +12,8 @@ #include +#include + #include "archive.h" #include "archive_entry.h" @@ -23,12 +25,78 @@ #include "graphics_load.h" #include "texconv.h" #include "export_tpl.h" +#include "export_wav.h" #include "libxtconvert.h" namespace XTConvert { +struct MixerX_Sentinel +{ + bool valid = false; + + MixerX_Sentinel(const Spec& spec) + { + { + int freq; + Uint16 fmt; + int chn; + + if(Mix_QuerySpec(&freq, &fmt, &chn)) + return; + } + + if(Mix_Init(MIX_INIT_FLAC | MIX_INIT_MOD | MIX_INIT_MP3 | MIX_INIT_OGG | MIX_INIT_MID) < 0) + return; + + SDL_AudioSpec as; + + as.channels = 2; + as.format = AUDIO_S16LSB; + as.freq = 44100; + as.silence = 0; + as.samples = 8192; + as.size = as.samples * 4; + as.callback = nullptr; + as.userdata = nullptr; + + if(spec.target_platform == TargetPlatform::TPL) + as.freq = 32000; + else if(spec.target_platform == TargetPlatform::DSG) + as.freq = 24000; + + Mix_InitMixer(&as, SDL_FALSE); + + { + int freq; + Uint16 fmt; + int chn; + + if(!Mix_QuerySpec(&freq, &fmt, &chn) || freq != as.freq || chn != as.channels || fmt != as.format) + { + Mix_FreeMixer(); + Mix_Quit(); + + return; + } + } + + valid = true; + } + + ~MixerX_Sentinel() + { + if(valid) + { + Mix_FreeMixer(); + Mix_Quit(); + } + + valid = true; + } +}; + class Converter { struct DirInfo @@ -220,6 +288,7 @@ class Converter Tex3DS::Params params; params.compression_format = Tex3DS::COMPRESSION_LZ11; + params.process_format = Tex3DS::RGBA8888; // image parts save_success = true; @@ -471,6 +540,33 @@ class Converter return true; } + bool convert_sfx(const QString&, const QString& in_path, const QString& out_path) + { + qInfo() << "ogg2wav" << out_path; + + Mix_Chunk* ch = Mix_LoadWAV(in_path.toUtf8().data()); + if(!ch) + return false; + + qInfo() << "length" << ch->alen; + + QByteArray wav_data; + + ExportWAV::export_wav(wav_data, ch); + + Mix_FreeChunk(ch); + + QFile file(out_path); + if(!file.open(QIODevice::WriteOnly)) + return false; + else + { + file.write(wav_data); + file.close(); + return true; + } + } + bool convert_file(const QString& filename, const QString& in_path, const QString& out_path) { sync_cur_dir(in_path); @@ -481,6 +577,8 @@ class Converter return convert_image(filename, in_path, out_path); else if(filename.endsWith(".ini") && m_cur_dir.convert_font_inis) return convert_font_ini(filename, in_path, out_path); + else if(filename.endsWith(".ogg") && in_path.contains("/sound/")) + return convert_sfx(filename, in_path, out_path); else if(filename.endsWith(".spc") && m_spec.target_platform == TargetPlatform::T3X) { qInfo() << "spc2it" << out_path; @@ -673,7 +771,14 @@ class Converter bool process() { - GraphicsLoad::initFreeImage(); + GraphicsLoad::FreeImage_Sentinel fs; + MixerX_Sentinel ms(m_spec); + + if(!ms.valid) + { + qInfo() << "Error: MixerX could not be (re)initialized"; + return false; + } m_input_dir.setPath(m_spec.input_dir); diff --git a/Maintainer/XTConvert/src/export_wav.cpp b/Maintainer/XTConvert/src/export_wav.cpp new file mode 100644 index 0000000000..8707d497fa --- /dev/null +++ b/Maintainer/XTConvert/src/export_wav.cpp @@ -0,0 +1,51 @@ +#include "export_wav.h" + +#include + +#include + +namespace XTConvert +{ + +namespace ExportWAV +{ + +// partly based off of ymhm.h by Aaron Giles (BSD-3 clause) +void export_wav(QByteArray& out, const Mix_Chunk* chunk) +{ + int samplerate; + + { + Uint16 fmt; + int chn; + + if(Mix_QuerySpec(&samplerate, &fmt, &chn) == 0 || chn != 2 || fmt != AUDIO_S16LSB) + { + out.clear(); + return; + } + } + + out.resize(44 + chunk->alen); + + // THIS STRONGLY ASSUMES LITTLE ENDIAN BUILD + memcpy(&out[0], "RIFF", 4); + *(uint32_t *)&out[4] = out.size() - 8; + memcpy(&out[8], "WAVE", 4); + memcpy(&out[12], "fmt ", 4); + *(uint32_t *)&out[16] = 16; + *(uint16_t *)&out[20] = 1; + *(uint16_t *)&out[22] = 2; + *(uint32_t *)&out[24] = samplerate; + *(uint32_t *)&out[28] = samplerate * 4; + *(uint16_t *)&out[32] = 4; + *(uint16_t *)&out[34] = 16; + memcpy(&out[36], "data", 4); + *(uint32_t *)&out[40] = out.size() - 44; + + memcpy(&out[44], chunk->abuf, chunk->alen); +} + +} // namespace ExportWAV + +} // namespace XTConvert diff --git a/Maintainer/XTConvert/src/export_wav.h b/Maintainer/XTConvert/src/export_wav.h new file mode 100644 index 0000000000..0fda2c098b --- /dev/null +++ b/Maintainer/XTConvert/src/export_wav.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +struct Mix_Chunk; + +namespace XTConvert +{ + +namespace ExportWAV +{ + +void export_wav(QByteArray& out, const Mix_Chunk* chunk); + +} // namespace ExportWAV + +} // namespace XTConvert From 13b8dfcc2e096b8c3979bad7d7413676b5b642ed Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sat, 9 Nov 2024 09:43:05 -0500 Subject: [PATCH 015/286] XTConvert: only use RGBA8888 when necessary on 3DS --- Maintainer/XTConvert/graphics_load.cpp | 30 ++++++++++++++++++++++++++ Maintainer/XTConvert/graphics_load.h | 2 ++ Maintainer/XTConvert/libxtconvert.cpp | 9 +++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Maintainer/XTConvert/graphics_load.cpp b/Maintainer/XTConvert/graphics_load.cpp index e535c3bd40..741c1cafa4 100644 --- a/Maintainer/XTConvert/graphics_load.cpp +++ b/Maintainer/XTConvert/graphics_load.cpp @@ -1,5 +1,6 @@ #include #include +#include #include @@ -240,3 +241,32 @@ bool GraphicsLoad::validateBitmaskRequired(FIBITMAP *image, FIBITMAP *mask) return false; } + +bool GraphicsLoad::validateForDepthTest(FIBITMAP *image) +{ + if(!image) + return false; + + auto w = static_cast(FreeImage_GetWidth(image)); + auto h = static_cast(FreeImage_GetHeight(image)); + auto pitch = static_cast(FreeImage_GetPitch(image)); + BYTE *img_bits = FreeImage_GetBits(image); + + for(uint32_t y = 0; y < h; ++y) + { + for(uint32_t x = 0; x < w; ++x) + { + BYTE *alpha = img_bits + (y * pitch) + (x * 4) + 3; + + // vanilla game used 5 bits per channel, so we set the alpha test as >= 0x08 + if(*alpha < 0x08 || *alpha >= 0xf8) + continue; + + // qInfo() << "alpha " << *alpha << " at " << x << ", " << y; + + return false; + } + } + + return true; +} diff --git a/Maintainer/XTConvert/graphics_load.h b/Maintainer/XTConvert/graphics_load.h index 8a3e78fd45..ec2e21affc 100644 --- a/Maintainer/XTConvert/graphics_load.h +++ b/Maintainer/XTConvert/graphics_load.h @@ -22,4 +22,6 @@ class GraphicsLoad static FIBITMAP *fast2xScaleDown(FIBITMAP *image); static bool validateBitmaskRequired(FIBITMAP *image, FIBITMAP *mask); + + static bool validateForDepthTest(FIBITMAP *image); }; diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 3a27f64ad1..f4e80fe65d 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -288,7 +288,14 @@ class Converter Tex3DS::Params params; params.compression_format = Tex3DS::COMPRESSION_LZ11; - params.process_format = Tex3DS::RGBA8888; + params.process_format = Tex3DS::RGBA5551; + + // check for transparency + if(!GraphicsLoad::validateForDepthTest(image)) + { + qInfo() << "Using RGBA8888 for " << in_path; + params.process_format = Tex3DS::RGBA8888; + } // image parts save_success = true; From 36dc4a43307de400c207fed3173ce2aaab79e711 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sat, 9 Nov 2024 16:54:30 -0500 Subject: [PATCH 016/286] Preliminary code to write graphics lists --- Maintainer/XTConvert/libxtconvert.cpp | 160 +++++++++++++++++++++++--- 1 file changed, 147 insertions(+), 13 deletions(-) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index f4e80fe65d..be36916d4e 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -105,8 +105,8 @@ class Converter QSet textures_1x; - std::unique_ptr own_graphics_list; // open file to the directory's own graphics list (if any) - QFile* graphics_list = nullptr; // pointer to the correct graphics list to write to (if any) + std::unique_ptr graphics_list; // open file to the directory's own graphics list (if any) + bool graphics_list_empty = true; enum MakeSizeFiles_t { @@ -122,7 +122,14 @@ class Converter bool convert_font_inis = false; }; + QDir m_input_dir; + QDir m_assets_dir; + + QTemporaryDir m_temp_dir_owner; + QDir m_temp_dir; + DirInfo m_cur_dir; + QFile m_main_graphics_list; void sync_cur_dir(const QString& in_file) { @@ -132,6 +139,11 @@ class Converter if(parent.path() == m_cur_dir.dir.path()) return; + // remove old graphics list if empty + if(m_cur_dir.graphics_list_empty && m_cur_dir.graphics_list) + m_cur_dir.graphics_list->remove(); + + // reset m_cur_dir m_cur_dir = DirInfo(); m_cur_dir.dir = std::move(parent); @@ -163,16 +175,31 @@ class Converter m_cur_dir.convert_font_inis = true; } - m_cur_dir.make_size_files = DirInfo::SIZEFILES_ALL; - } + // prepare size file policy and graphics list + if(m_spec.target_platform == TargetPlatform::Desktop) + m_cur_dir.make_size_files = DirInfo::SIZEFILES_NONE; + else if(m_cur_dir.dir.path().contains("/graphics/")) + m_cur_dir.make_size_files = DirInfo::SIZEFILES_PREVIEW_ASSETS; + else + { + // find path of temp dir + auto s = QDir::separator(); + QString rel_path = m_input_dir.relativeFilePath(m_cur_dir.dir.path()); + QString graphics_list_path = m_temp_dir.filePath(rel_path) + s + "graphics.list"; -public: - QDir m_input_dir; - QDir m_assets_dir; + m_cur_dir.graphics_list.reset(new QFile(graphics_list_path)); - QTemporaryDir m_temp_dir_owner; - QDir m_temp_dir; + if(m_cur_dir.graphics_list->open(QIODevice::WriteOnly | QIODevice::Append)) + { + m_cur_dir.make_size_files = DirInfo::SIZEFILES_PREVIEW_EPISODE; + m_cur_dir.graphics_list_empty = (m_cur_dir.graphics_list->pos() == 0); + } + else + m_cur_dir.make_size_files = DirInfo::SIZEFILES_ALL; + } + } +public: Spec m_spec; QString m_error; @@ -495,8 +522,107 @@ class Converter // cleanup the image FreeImage_Unload(image); - // write to size file if needed - if(m_cur_dir.make_size_files && save_success && !size_text.isEmpty()) + // check if graphics list present + QFile* write_graphics_list = nullptr; + + if(m_cur_dir.graphics_list.get()) + write_graphics_list = m_cur_dir.graphics_list.get(); + else if(m_main_graphics_list.isOpen()) + write_graphics_list = &m_main_graphics_list; + + // write graphics list + if(write_graphics_list) + { + QString basename = filename.section(".", 0, 0); + QString type = basename.section("-", 0, 0); + QString index = basename.section("-", 1); + + bool index_valid = false; + index.toUInt(&index_valid); + + static const std::array valid_types = { + "background", "background2", "block", "effect", "level", + "link", "luigi", "mario", "npc", "path", + "peach", "player", "scene", "tile", "toad", + "yoshib", "yoshit" + }; + + bool type_valid = false; + for(const char* valid_type : valid_types) + { + if(type == valid_type) + { + // only allow valid subdirectories for main graphics list + if(write_graphics_list == &m_main_graphics_list) + { + // special case: yoshib and yoshit stored in yoshi + if(valid_type == valid_types[15] || valid_type == valid_types[16]) + { + if(!m_cur_dir.dir.path().endsWith("yoshi")) + break; + } + else if(!m_cur_dir.dir.path().endsWith(valid_type)) + break; + } + + type_valid = true; + break; + } + } + + if(index_valid && type_valid) + { + // make path relative to graphics list + QFileInfo fi(write_graphics_list->fileName()); + QDir graphics_list_parent = fi.dir(); + QString relative_out_path = graphics_list_parent.relativeFilePath(used_out_path); + + write_graphics_list->write(type.toUtf8()); + write_graphics_list->write(" "); + write_graphics_list->write(index.toUtf8()); + write_graphics_list->write("\n"); + write_graphics_list->write(relative_out_path.toUtf8()); + write_graphics_list->write("\n"); + write_graphics_list->write(size_text.toUtf8()); + write_graphics_list->write("\n"); + + if(write_graphics_list == m_cur_dir.graphics_list.get()) + m_cur_dir.graphics_list_empty = false; + } + else + write_graphics_list = nullptr; + } + + // check if size file needed + bool make_size_file = false; + + if(m_cur_dir.make_size_files == DirInfo::SIZEFILES_ALL) + make_size_file = true; + else if(m_cur_dir.make_size_files != DirInfo::SIZEFILES_NONE && !write_graphics_list) + { + // always write if filename doesn't follow graphics.list format + make_size_file = true; + } + else if(m_cur_dir.make_size_files == DirInfo::SIZEFILES_PREVIEW_ASSETS) + { + // UI and background sprites + make_size_file = in_path.contains("/ui/") + || filename.contains("/background2/"); + } + else if(m_cur_dir.make_size_files == DirInfo::SIZEFILES_PREVIEW_EPISODE) + { + // player and mount sprites + make_size_file = filename.contains("mario") + || filename.contains("luigi") + || filename.contains("peach") + || filename.contains("toad") + || filename.contains("link") + || filename.contains("yoshi") + || filename.contains("mount"); + } + + // write to size file + if(make_size_file && save_success && !size_text.isEmpty()) { QFile file(used_out_path + ".size"); if(!file.open(QIODevice::WriteOnly)) @@ -506,8 +632,6 @@ class Converter file.close(); } - // FIXME: write to graphics list NOW - return save_success; } @@ -632,6 +756,14 @@ class Converter return false; } + if(rel_path == "graphics") + { + qInfo() << "Initializing primary graphics.list file"; + + m_main_graphics_list.setFileName(temp_path + QDir::separator() + "graphics.list"); + m_main_graphics_list.open(QIODevice::WriteOnly); + } + continue; } @@ -643,6 +775,8 @@ class Converter } } + m_main_graphics_list.close(); + return true; } From 0615ca03d1f174b5e3ef723bcbe6319cd627063a Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sat, 9 Nov 2024 17:43:28 -0500 Subject: [PATCH 017/286] libxtconvert.cpp: temporarily disable verbose logging --- Maintainer/XTConvert/libxtconvert.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index be36916d4e..308d7f06e2 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -154,7 +154,8 @@ class Converter { for(QFileInfo f : m_cur_dir.dir.entryInfoList({"*.ini"})) { - qInfo() << "Scanning" << f.absoluteFilePath(); + // qInfo() << "Scanning" << f.absoluteFilePath(); + QSettings ini(f.absoluteFilePath(), QSettings::IniFormat); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) ini.setIniCodec("UTF-8"); @@ -164,7 +165,7 @@ class Converter int texture_scale = ini.value("texture-scale", 1).toInt(); QString texture_filename = ini.value("texture", "").toString().toLower(); - qInfo() << "scale" << texture_scale << "fn" << texture_filename; + // qInfo() << "scale" << texture_scale << "fn" << texture_filename; if(texture_scale != 1 && !texture_filename.isEmpty()) m_cur_dir.textures_1x.insert(texture_filename); From ba6423d16a6ef9f89b0b1a2d583abc03a10c3252 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sun, 10 Nov 2024 19:29:20 -0500 Subject: [PATCH 018/286] XTConvert: automatically make fallback mask GIFs --- Maintainer/XTConvert/include/libxtconvert.h | 2 +- Maintainer/XTConvert/libxtconvert.cpp | 157 ++++++++++++++------ Maintainer/XTConvert/xtconvert_cli.cpp | 2 +- 3 files changed, 117 insertions(+), 44 deletions(-) diff --git a/Maintainer/XTConvert/include/libxtconvert.h b/Maintainer/XTConvert/include/libxtconvert.h index 5fddca4f4e..f3d464bf12 100644 --- a/Maintainer/XTConvert/include/libxtconvert.h +++ b/Maintainer/XTConvert/include/libxtconvert.h @@ -20,7 +20,7 @@ struct Spec bool preserve_bitmask_appearance = true; QString input_dir; - QString graphics_dir; + QString use_assets_dir; QString destination; }; diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 308d7f06e2..4620f09c64 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -32,6 +32,34 @@ namespace XTConvert { +static bool validate_image_filename(const QString& filename, QString& type, QString& index) +{ + QString basename = filename.section(".", 0, 0); + type = basename.section("-", 0, 0); + index = basename.section("-", 1); + + bool index_valid = false; + index.toUInt(&index_valid); + + if(!index_valid) + return false; + + static const std::array valid_types = { + "background", "background2", "block", "effect", "level", + "link", "luigi", "mario", "npc", "path", + "peach", "player", "scene", "tile", "toad", + "yoshib", "yoshit" + }; + + for(const char* valid_type : valid_types) + { + if(type == valid_type) + return true; + } + + return false; +} + struct MixerX_Sentinel { bool valid = false; @@ -117,8 +145,7 @@ class Converter }; MakeSizeFiles_t make_size_files = SIZEFILES_NONE; - bool make_mask_gifs = false; - bool copy_mask_gifs = false; + bool make_fallback_masks = false; bool convert_font_inis = false; }; @@ -147,6 +174,7 @@ class Converter m_cur_dir = DirInfo(); m_cur_dir.dir = std::move(parent); + QString rel_path = m_input_dir.relativeFilePath(m_cur_dir.dir.path()); QStringList path_parts = QDir::toNativeSeparators(m_cur_dir.dir.path()).toLower().split(QDir::separator()); // check for 1x textures: fonts @@ -179,13 +207,15 @@ class Converter // prepare size file policy and graphics list if(m_spec.target_platform == TargetPlatform::Desktop) m_cur_dir.make_size_files = DirInfo::SIZEFILES_NONE; - else if(m_cur_dir.dir.path().contains("/graphics/")) + else if(m_spec.use_assets_dir.isEmpty() && rel_path.startsWith("graphics/")) + { m_cur_dir.make_size_files = DirInfo::SIZEFILES_PREVIEW_ASSETS; + m_cur_dir.make_fallback_masks = (m_spec.target_platform != TargetPlatform::TPL); + } else { - // find path of temp dir + // find path of temp dir's graphics list auto s = QDir::separator(); - QString rel_path = m_input_dir.relativeFilePath(m_cur_dir.dir.path()); QString graphics_list_path = m_temp_dir.filePath(rel_path) + s + "graphics.list"; m_cur_dir.graphics_list.reset(new QFile(graphics_list_path)); @@ -271,8 +301,56 @@ class Converter QString used_out_path = out_path; + // save mask fallback here if we are in the global graphics dir + if(m_cur_dir.make_fallback_masks && output_format != TargetPlatform::Desktop) + { + QString type, index; + + FIBITMAP* fallback_mask = nullptr; + - // FIXME: save mask fallback here if we are in the global graphics dir + // make sure that the texture is valid and should have transparency + if(validate_image_filename(filename, type, index) && type != "background2" && type != "tile") + fallback_mask = FreeImage_Clone(image); + + // convert to 8-bit paletted mask + if(fallback_mask) + { + GraphicsLoad::RGBAToMask(fallback_mask); + + // should not fail (there are only 256 shades of gray) + FIBITMAP* fallback_mask_2 = FreeImage_ColorQuantizeEx(fallback_mask, FIQ_LFPQUANT, 2); + if(fallback_mask_2) + { + FreeImage_Unload(fallback_mask_2); + fallback_mask_2 = FreeImage_Threshold(fallback_mask, 127); + } + else + fallback_mask_2 = FreeImage_ConvertTo8Bits(fallback_mask); + + FreeImage_Unload(fallback_mask); + + fallback_mask = fallback_mask_2; + } + + if(fallback_mask) + { + QString fallback_mask_path = m_temp_dir.filePath("graphics/fallback/"); + + // make fallback folder if it isn't present + if(!QFileInfo::exists(fallback_mask_path)) + m_temp_dir.mkdir(fallback_mask_path); + + // append filename + fallback_mask_path += filename.chopped(4) + "m.gif"; + + // don't clobber a version copied from the original fallback folder + if(!QFileInfo::exists(fallback_mask_path)) + FreeImage_Save(FIF_GIF, fallback_mask, fallback_mask_path.toUtf8()); + + FreeImage_Unload(fallback_mask); + } + } // scale the image as needed @@ -534,44 +612,24 @@ class Converter // write graphics list if(write_graphics_list) { - QString basename = filename.section(".", 0, 0); - QString type = basename.section("-", 0, 0); - QString index = basename.section("-", 1); - - bool index_valid = false; - index.toUInt(&index_valid); - - static const std::array valid_types = { - "background", "background2", "block", "effect", "level", - "link", "luigi", "mario", "npc", "path", - "peach", "player", "scene", "tile", "toad", - "yoshib", "yoshit" - }; - - bool type_valid = false; - for(const char* valid_type : valid_types) + QString type, index; + + bool valid = validate_image_filename(filename, type, index); + + // only allow valid subdirectories for main graphics list + if(valid && write_graphics_list == &m_main_graphics_list) { - if(type == valid_type) + // special case: yoshib and yoshit stored in yoshi + if(type.startsWith('y')) { - // only allow valid subdirectories for main graphics list - if(write_graphics_list == &m_main_graphics_list) - { - // special case: yoshib and yoshit stored in yoshi - if(valid_type == valid_types[15] || valid_type == valid_types[16]) - { - if(!m_cur_dir.dir.path().endsWith("yoshi")) - break; - } - else if(!m_cur_dir.dir.path().endsWith(valid_type)) - break; - } - - type_valid = true; - break; + if(!m_cur_dir.dir.path().endsWith("yoshi")) + valid = false; } + else if(!m_cur_dir.dir.path().endsWith(type)) + valid = false; } - if(index_valid && type_valid) + if(valid) { // make path relative to graphics list QFileInfo fi(write_graphics_list->fileName()); @@ -703,9 +761,19 @@ class Converter { sync_cur_dir(in_path); - if(filename.endsWith("m.gif")) + if(filename.endsWith("m.gif") && in_path.contains("/graphics/fallback/")) + { + // want to clobber auto-generated masks + if(QFile::exists(out_path)) + QFile::remove(out_path); + + return QFile::copy(in_path, out_path); + } + else if(filename.endsWith("m.gif")) + return true; + else if(filename.endsWith(".xcf") || filename.endsWith(".odt") || filename.endsWith(".pdf")) return true; - else if(m_spec.target_platform != TargetPlatform::Desktop && (filename.endsWith(".png") || filename.endsWith(".gif"))) + else if(m_spec.target_platform != TargetPlatform::Desktop && (filename.endsWith(".png") || filename.endsWith(".gif")) && !in_path.contains("/graphics/fallback/")) return convert_image(filename, in_path, out_path); else if(filename.endsWith(".ini") && m_cur_dir.convert_font_inis) return convert_font_ini(filename, in_path, out_path); @@ -750,7 +818,7 @@ class Converter if(it.fileInfo().isDir()) { qInfo() << "making" << temp_path; - if(!m_temp_dir.mkdir(temp_path)) + if(!m_temp_dir.exists(temp_path) && !m_temp_dir.mkdir(temp_path)) { m_error = "Failed to create directory "; m_error += temp_path; @@ -924,6 +992,11 @@ class Converter m_input_dir.setPath(m_spec.input_dir); + if(!m_spec.use_assets_dir.isEmpty()) + m_assets_dir.setPath(m_spec.use_assets_dir); + else + m_assets_dir.setPath(m_spec.input_dir); + if(!build_temp_dir()) return false; diff --git a/Maintainer/XTConvert/xtconvert_cli.cpp b/Maintainer/XTConvert/xtconvert_cli.cpp index 9065928c1d..929d8bb29b 100644 --- a/Maintainer/XTConvert/xtconvert_cli.cpp +++ b/Maintainer/XTConvert/xtconvert_cli.cpp @@ -3,7 +3,7 @@ int main(int argc, char** argv) { XTConvert::Spec s; - s.target_platform = XTConvert::TargetPlatform::T3X; + s.target_platform = XTConvert::TargetPlatform::TPL; s.input_dir = argv[1]; s.destination = argv[2]; From 7aa157ba5637b7636791a6ec13bbd24df43bd783 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Wed, 27 Nov 2024 16:21:46 -0500 Subject: [PATCH 019/286] XTConvert: refactor parameters --- Maintainer/XTConvert/include/libxtconvert.h | 17 +++++++++-- Maintainer/XTConvert/libxtconvert.cpp | 33 +++++++++++++++++---- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Maintainer/XTConvert/include/libxtconvert.h b/Maintainer/XTConvert/include/libxtconvert.h index f3d464bf12..76767e497e 100644 --- a/Maintainer/XTConvert/include/libxtconvert.h +++ b/Maintainer/XTConvert/include/libxtconvert.h @@ -13,11 +13,24 @@ enum class TargetPlatform DSG, }; +enum class PackageType +{ + Episode, + AssetPack, +}; + +enum class ConvertGIFs +{ + Safe, + None, // ban this for now + All, +}; + struct Spec { TargetPlatform target_platform = TargetPlatform::Desktop; - - bool preserve_bitmask_appearance = true; + PackageType package_type = PackageType::AssetPack; + ConvertGIFs convert_gifs = ConvertGIFs::Safe; QString input_dir; QString use_assets_dir; diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 4620f09c64..90b8f84739 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -207,7 +207,7 @@ class Converter // prepare size file policy and graphics list if(m_spec.target_platform == TargetPlatform::Desktop) m_cur_dir.make_size_files = DirInfo::SIZEFILES_NONE; - else if(m_spec.use_assets_dir.isEmpty() && rel_path.startsWith("graphics/")) + else if(m_spec.package_type == PackageType::AssetPack && rel_path.startsWith("graphics/")) { m_cur_dir.make_size_files = DirInfo::SIZEFILES_PREVIEW_ASSETS; m_cur_dir.make_fallback_masks = (m_spec.target_platform != TargetPlatform::TPL); @@ -282,12 +282,13 @@ class Converter } // Okay, finding a mask failed. Imagine that image is fully opaque in that case, and ignore the mask logic. + // qInfo() << "Failed to find mask for GIF " << in_path; } TargetPlatform output_format = m_spec.target_platform; // find out whether mask is actually needed - if(m_spec.preserve_bitmask_appearance && GraphicsLoad::validateBitmaskRequired(image, mask)) + if(m_spec.convert_gifs != ConvertGIFs::All && GraphicsLoad::validateBitmaskRequired(image, mask)) output_format = TargetPlatform::Desktop; else if(mask) GraphicsLoad::mergeWithMask(image, mask); @@ -383,6 +384,8 @@ class Converter if(save_success && !mask_path.isEmpty()) save_success = QFile::copy(mask_path, out_path.chopped(4) + "m.gif"); + + // set the size text here...! } else if(output_format == TargetPlatform::T3X) { @@ -784,6 +787,18 @@ class Converter qInfo() << "spc2it" << out_path; return spc2it_convert(in_path.toUtf8().data(), out_path.toUtf8().data()) == 0; } + else if(m_spec.package_type == PackageType::Episode && (filename.endsWith(".wld") || filename.endsWith(".wldx"))) + { + // load the episode information, or complain if there are multiple WLD(X) files + qInfo() << "copying" << out_path; + return QFile::copy(in_path, out_path); + } + else if(m_spec.package_type == PackageType::Episode && filename.startsWith("translation_") && filename.endsWith(".json")) + { + // load the translated episode name + qInfo() << "copying" << out_path; + return QFile::copy(in_path, out_path); + } else { qInfo() << "copying" << out_path; @@ -846,12 +861,20 @@ class Converter m_main_graphics_list.close(); + if(m_spec.package_type == PackageType::Episode) + update_meta_episode(); + else + update_meta_asset_pack(); + return true; } - bool build_graphics_lists() + void update_meta_episode() + { + } + + void update_meta_asset_pack() { - return true; } bool build_soundbank() @@ -992,7 +1015,7 @@ class Converter m_input_dir.setPath(m_spec.input_dir); - if(!m_spec.use_assets_dir.isEmpty()) + if(m_spec.package_type == PackageType::Episode) m_assets_dir.setPath(m_spec.use_assets_dir); else m_assets_dir.setPath(m_spec.input_dir); From 74507688308b91a45b95ac714fb0b055f2f28d70 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:12:09 -0500 Subject: [PATCH 020/286] libxtconvert.cpp: graphics list fixes --- Maintainer/XTConvert/libxtconvert.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 90b8f84739..bdf1c9ce5e 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -607,7 +607,7 @@ class Converter // check if graphics list present QFile* write_graphics_list = nullptr; - if(m_cur_dir.graphics_list.get()) + if(m_cur_dir.graphics_list) write_graphics_list = m_cur_dir.graphics_list.get(); else if(m_main_graphics_list.isOpen()) write_graphics_list = &m_main_graphics_list; @@ -861,6 +861,9 @@ class Converter m_main_graphics_list.close(); + if(m_cur_dir.graphics_list) + m_cur_dir.graphics_list->close(); + if(m_spec.package_type == PackageType::Episode) update_meta_episode(); else @@ -1013,6 +1016,12 @@ class Converter return false; } + if(m_spec.target_platform == TargetPlatform::DSG && m_spec.convert_gifs != ConvertGIFs::All) + { + qInfo() << "Note: converting all GIFs to DSG"; + m_spec.convert_gifs = ConvertGIFs::All; + } + m_input_dir.setPath(m_spec.input_dir); if(m_spec.package_type == PackageType::Episode) From 904007112f95b6c5d17ba2c4a88347966de7e7f5 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sat, 30 Nov 2024 13:21:55 -0500 Subject: [PATCH 021/286] XTConvert: add mus2qoa audio conversion --- Maintainer/XTConvert/CMakeLists.txt | 2 +- Maintainer/XTConvert/libxtconvert.cpp | 39 +- Maintainer/XTConvert/src/export_audio.cpp | 119 +++ .../src/{export_wav.h => export_audio.h} | 6 +- Maintainer/XTConvert/src/export_wav.cpp | 51 -- Maintainer/XTConvert/src/qoa.h | 742 ++++++++++++++++++ Maintainer/XTConvert/xtconvert_cli.cpp | 7 +- 7 files changed, 904 insertions(+), 62 deletions(-) create mode 100644 Maintainer/XTConvert/src/export_audio.cpp rename Maintainer/XTConvert/src/{export_wav.h => export_audio.h} (60%) delete mode 100644 Maintainer/XTConvert/src/export_wav.cpp create mode 100644 Maintainer/XTConvert/src/qoa.h diff --git a/Maintainer/XTConvert/CMakeLists.txt b/Maintainer/XTConvert/CMakeLists.txt index 6850263775..b5e7f45cbc 100644 --- a/Maintainer/XTConvert/CMakeLists.txt +++ b/Maintainer/XTConvert/CMakeLists.txt @@ -14,7 +14,7 @@ add_library(libxtconvert graphics_load.cpp src/texconv.cpp src/export_tpl.cpp - src/export_wav.cpp + src/export_audio.cpp ) add_dependencies(libxtconvert archive_static lz4_pack_static libromfs3ds libtex3ds FreeImage_Local libpng_Local SDLMixerX_Local SDL2_Local) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index bdf1c9ce5e..c8461bd4b6 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -25,7 +25,7 @@ #include "graphics_load.h" #include "texconv.h" #include "export_tpl.h" -#include "export_wav.h" +#include "export_audio.h" #include "libxtconvert.h" @@ -89,10 +89,10 @@ struct MixerX_Sentinel as.callback = nullptr; as.userdata = nullptr; - if(spec.target_platform == TargetPlatform::TPL) + if(spec.target_platform == TargetPlatform::TPL || spec.target_platform == TargetPlatform::DSG) as.freq = 32000; - else if(spec.target_platform == TargetPlatform::DSG) - as.freq = 24000; + // else if(spec.target_platform == TargetPlatform::DSG) + // as.freq = 24000; Mix_InitMixer(&as, SDL_FALSE); @@ -745,7 +745,7 @@ class Converter QByteArray wav_data; - ExportWAV::export_wav(wav_data, ch); + ExportAudio::export_wav(wav_data, ch); Mix_FreeChunk(ch); @@ -760,6 +760,31 @@ class Converter } } + bool convert_music_16m(const QString&, const QString& in_path, const QString& out_path) + { + qInfo() << "mus2qoa" << out_path; + + Mix_Chunk* ch = Mix_LoadWAV(in_path.toUtf8().data()); + if(!ch) + return false; + + QByteArray qoa_data; + + ExportAudio::export_qoa(qoa_data, ch); + + Mix_FreeChunk(ch); + + QFile file(out_path + ".qoa"); + if(!file.open(QIODevice::WriteOnly)) + return false; + else + { + file.write(qoa_data); + file.close(); + return true; + } + } + bool convert_file(const QString& filename, const QString& in_path, const QString& out_path) { sync_cur_dir(in_path); @@ -780,8 +805,10 @@ class Converter return convert_image(filename, in_path, out_path); else if(filename.endsWith(".ini") && m_cur_dir.convert_font_inis) return convert_font_ini(filename, in_path, out_path); - else if(filename.endsWith(".ogg") && in_path.contains("/sound/")) + else if(m_spec.target_platform != TargetPlatform::Desktop && filename.endsWith(".ogg") && in_path.contains("/sound/")) return convert_sfx(filename, in_path, out_path); + else if(m_spec.target_platform == TargetPlatform::DSG && (filename.endsWith(".mp3") || filename.endsWith(".ogg"))) + return convert_music_16m(filename, in_path, out_path); else if(filename.endsWith(".spc") && m_spec.target_platform == TargetPlatform::T3X) { qInfo() << "spc2it" << out_path; diff --git a/Maintainer/XTConvert/src/export_audio.cpp b/Maintainer/XTConvert/src/export_audio.cpp new file mode 100644 index 0000000000..28fbcebb45 --- /dev/null +++ b/Maintainer/XTConvert/src/export_audio.cpp @@ -0,0 +1,119 @@ +#include "export_audio.h" + +#include + +#include + +#define QOA_NO_STDIO +#define QOA_IMPLEMENTATION +#include "qoa.h" + +namespace XTConvert +{ + +namespace ExportAudio +{ + +// partly based off of ymhm.h by Aaron Giles (BSD-3 clause) +void export_wav(QByteArray& out, const Mix_Chunk* chunk) +{ + int samplerate; + + { + Uint16 fmt; + int chn; + + if(Mix_QuerySpec(&samplerate, &fmt, &chn) == 0 || chn != 2 || fmt != AUDIO_S16LSB) + { + out.clear(); + return; + } + } + + out.resize(44 + chunk->alen); + + // THIS STRONGLY ASSUMES LITTLE ENDIAN BUILD + memcpy(&out[0], "RIFF", 4); + *(uint32_t *)&out[4] = out.size() - 8; + memcpy(&out[8], "WAVE", 4); + memcpy(&out[12], "fmt ", 4); + *(uint32_t *)&out[16] = 16; + *(uint16_t *)&out[20] = 1; + *(uint16_t *)&out[22] = 2; + *(uint32_t *)&out[24] = samplerate; + *(uint32_t *)&out[28] = samplerate * 4; + *(uint16_t *)&out[32] = 4; + *(uint16_t *)&out[34] = 16; + memcpy(&out[36], "data", 4); + *(uint32_t *)&out[40] = out.size() - 44; + + memcpy(&out[44], chunk->abuf, chunk->alen); +} + +void export_qoa(QByteArray& out, const Mix_Chunk* chunk) +{ + int samplerate, chn; + + { + Uint16 fmt; + + if(Mix_QuerySpec(&samplerate, &fmt, &chn) == 0 || chn > 2 || fmt != AUDIO_S16LSB) + { + out.clear(); + return; + } + } + + int sample_count = chunk->alen / (2 * chn); + int16_t* abuf_in = (int16_t*)chunk->abuf; + int16_t* use_abuf = abuf_in; + + // convert stereo to mono destructively + if(chn == 2) + { + int16_t* abuf_out = (int16_t*)malloc(sample_count * sizeof(int16_t)); + + if(!abuf_out) + { + out.clear(); + return; + } + + for(int sample = 0; sample < sample_count; sample++) + { + int32_t left_sample = abuf_in[sample * 2 + 0]; + int32_t right_sample = abuf_in[sample * 2 + 1]; + + abuf_out[sample] = static_cast((left_sample + right_sample) / 2); + } + + use_abuf = abuf_out; + chn = 1; + } + + qoa_desc desc; + desc.channels = chn; + desc.samplerate = samplerate; + desc.samples = sample_count; + + unsigned int out_len; + uint8_t* encoded = (uint8_t*)qoa_encode(use_abuf, &desc, &out_len); + + if(use_abuf != abuf_in) + free(use_abuf); + + if(!encoded) + { + out.clear(); + return; + } + + out.resize(out_len); + memcpy(&out[0], encoded, out_len); + + QOA_FREE(encoded); +} + +} // namespace ExportAudio + +} // namespace XTConvert diff --git a/Maintainer/XTConvert/src/export_wav.h b/Maintainer/XTConvert/src/export_audio.h similarity index 60% rename from Maintainer/XTConvert/src/export_wav.h rename to Maintainer/XTConvert/src/export_audio.h index 0fda2c098b..1d1e732b7b 100644 --- a/Maintainer/XTConvert/src/export_wav.h +++ b/Maintainer/XTConvert/src/export_audio.h @@ -7,11 +7,13 @@ struct Mix_Chunk; namespace XTConvert { -namespace ExportWAV +namespace ExportAudio { void export_wav(QByteArray& out, const Mix_Chunk* chunk); -} // namespace ExportWAV +void export_qoa(QByteArray& out, const Mix_Chunk* chunk); + +} // namespace ExportAudio } // namespace XTConvert diff --git a/Maintainer/XTConvert/src/export_wav.cpp b/Maintainer/XTConvert/src/export_wav.cpp deleted file mode 100644 index 8707d497fa..0000000000 --- a/Maintainer/XTConvert/src/export_wav.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "export_wav.h" - -#include - -#include - -namespace XTConvert -{ - -namespace ExportWAV -{ - -// partly based off of ymhm.h by Aaron Giles (BSD-3 clause) -void export_wav(QByteArray& out, const Mix_Chunk* chunk) -{ - int samplerate; - - { - Uint16 fmt; - int chn; - - if(Mix_QuerySpec(&samplerate, &fmt, &chn) == 0 || chn != 2 || fmt != AUDIO_S16LSB) - { - out.clear(); - return; - } - } - - out.resize(44 + chunk->alen); - - // THIS STRONGLY ASSUMES LITTLE ENDIAN BUILD - memcpy(&out[0], "RIFF", 4); - *(uint32_t *)&out[4] = out.size() - 8; - memcpy(&out[8], "WAVE", 4); - memcpy(&out[12], "fmt ", 4); - *(uint32_t *)&out[16] = 16; - *(uint16_t *)&out[20] = 1; - *(uint16_t *)&out[22] = 2; - *(uint32_t *)&out[24] = samplerate; - *(uint32_t *)&out[28] = samplerate * 4; - *(uint16_t *)&out[32] = 4; - *(uint16_t *)&out[34] = 16; - memcpy(&out[36], "data", 4); - *(uint32_t *)&out[40] = out.size() - 44; - - memcpy(&out[44], chunk->abuf, chunk->alen); -} - -} // namespace ExportWAV - -} // namespace XTConvert diff --git a/Maintainer/XTConvert/src/qoa.h b/Maintainer/XTConvert/src/qoa.h new file mode 100644 index 0000000000..96a884c42a --- /dev/null +++ b/Maintainer/XTConvert/src/qoa.h @@ -0,0 +1,742 @@ +/* + +Copyright (c) 2023, Dominic Szablewski - https://phoboslab.org +SPDX-License-Identifier: MIT + +QOA - The "Quite OK Audio" format for fast, lossy audio compression + + +-- Data Format + +QOA encodes pulse-code modulated (PCM) audio data with up to 255 channels, +sample rates from 1 up to 16777215 hertz and a bit depth of 16 bits. + +The compression method employed in QOA is lossy; it discards some information +from the uncompressed PCM data. For many types of audio signals this compression +is "transparent", i.e. the difference from the original file is often not +audible. + +QOA encodes 20 samples of 16 bit PCM data into slices of 64 bits. A single +sample therefore requires 3.2 bits of storage space, resulting in a 5x +compression (16 / 3.2). + +A QOA file consists of an 8 byte file header, followed by a number of frames. +Each frame contains an 8 byte frame header, the current 16 byte en-/decoder +state per channel and 256 slices per channel. Each slice is 8 bytes wide and +encodes 20 samples of audio data. + +All values, including the slices, are big endian. The file layout is as follows: + +struct { + struct { + char magic[4]; // magic bytes "qoaf" + uint32_t samples; // samples per channel in this file + } file_header; + + struct { + struct { + uint8_t num_channels; // no. of channels + uint24_t samplerate; // samplerate in hz + uint16_t fsamples; // samples per channel in this frame + uint16_t fsize; // frame size (includes this header) + } frame_header; + + struct { + int16_t history[4]; // most recent last + int16_t weights[4]; // most recent last + } lms_state[num_channels]; + + qoa_slice_t slices[256][num_channels]; + + } frames[ceil(samples / (256 * 20))]; +} qoa_file_t; + +Each `qoa_slice_t` contains a quantized scalefactor `sf_quant` and 20 quantized +residuals `qrNN`: + +.- QOA_SLICE -- 64 bits, 20 samples --------------------------/ /------------. +| Byte[0] | Byte[1] | Byte[2] \ \ Byte[7] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | 7 6 5 / / 2 1 0 | +|------------+--------+--------+--------+---------+---------+-\ \--+---------| +| sf_quant | qr00 | qr01 | qr02 | qr03 | qr04 | / / | qr19 | +`-------------------------------------------------------------\ \------------` + +Each frame except the last must contain exactly 256 slices per channel. The last +frame may contain between 1 .. 256 (inclusive) slices per channel. The last +slice (for each channel) in the last frame may contain less than 20 samples; the +slice still must be 8 bytes wide, with the unused samples zeroed out. + +Channels are interleaved per slice. E.g. for 2 channel stereo: +slice[0] = L, slice[1] = R, slice[2] = L, slice[3] = R ... + +A valid QOA file or stream must have at least one frame. Each frame must contain +at least one channel and one sample with a samplerate between 1 .. 16777215 +(inclusive). + +If the total number of samples is not known by the encoder, the samples in the +file header may be set to 0x00000000 to indicate that the encoder is +"streaming". In a streaming context, the samplerate and number of channels may +differ from frame to frame. For static files (those with samples set to a +non-zero value), each frame must have the same number of channels and same +samplerate. + +Note that this implementation of QOA only handles files with a known total +number of samples. + +A decoder should support at least 8 channels. The channel layout for channel +counts 1 .. 8 is: + + 1. Mono + 2. L, R + 3. L, R, C + 4. FL, FR, B/SL, B/SR + 5. FL, FR, C, B/SL, B/SR + 6. FL, FR, C, LFE, B/SL, B/SR + 7. FL, FR, C, LFE, B, SL, SR + 8. FL, FR, C, LFE, BL, BR, SL, SR + +QOA predicts each audio sample based on the previously decoded ones using a +"Sign-Sign Least Mean Squares Filter" (LMS). This prediction plus the +dequantized residual forms the final output sample. + +*/ + + + +/* ----------------------------------------------------------------------------- + Header - Public functions */ + +#ifndef QOA_H +#define QOA_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define QOA_MIN_FILESIZE 16 +#define QOA_MAX_CHANNELS 8 + +#define QOA_SLICE_LEN 20 +#define QOA_SLICES_PER_FRAME 256 +#define QOA_FRAME_LEN (QOA_SLICES_PER_FRAME * QOA_SLICE_LEN) +#define QOA_LMS_LEN 4 +#define QOA_MAGIC 0x716f6166 /* 'qoaf' */ + +#define QOA_FRAME_SIZE(channels, slices) \ + (8 + QOA_LMS_LEN * 4 * channels + 8 * slices * channels) + +typedef struct { + int history[QOA_LMS_LEN]; + int weights[QOA_LMS_LEN]; +} qoa_lms_t; + +typedef struct { + unsigned int channels; + unsigned int samplerate; + unsigned int samples; + qoa_lms_t lms[QOA_MAX_CHANNELS]; + #ifdef QOA_RECORD_TOTAL_ERROR + double error; + #endif +} qoa_desc; + +unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes); +unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes); +void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len); + +unsigned int qoa_max_frame_size(qoa_desc *qoa); +unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa); +unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len); +short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *file); + +#ifndef QOA_NO_STDIO + +int qoa_write(const char *filename, const short *sample_data, qoa_desc *qoa); +void *qoa_read(const char *filename, qoa_desc *qoa); + +#endif /* QOA_NO_STDIO */ + + +#ifdef __cplusplus +} +#endif +#endif /* QOA_H */ + + +/* ----------------------------------------------------------------------------- + Implementation */ + +#ifdef QOA_IMPLEMENTATION +#include + +#ifndef QOA_MALLOC + #define QOA_MALLOC(sz) malloc(sz) + #define QOA_FREE(p) free(p) +#endif + +typedef unsigned long long qoa_uint64_t; + + +/* The quant_tab provides an index into the dequant_tab for residuals in the +range of -8 .. 8. It maps this range to just 3bits and becomes less accurate at +the higher end. Note that the residual zero is identical to the lowest positive +value. This is mostly fine, since the qoa_div() function always rounds away +from zero. */ + +static const int qoa_quant_tab[17] = { + 7, 7, 7, 5, 5, 3, 3, 1, /* -8..-1 */ + 0, /* 0 */ + 0, 2, 2, 4, 4, 6, 6, 6 /* 1.. 8 */ +}; + + +/* We have 16 different scalefactors. Like the quantized residuals these become +less accurate at the higher end. In theory, the highest scalefactor that we +would need to encode the highest 16bit residual is (2**16)/8 = 8192. However we +rely on the LMS filter to predict samples accurately enough that a maximum +residual of one quarter of the 16 bit range is sufficient. I.e. with the +scalefactor 2048 times the quant range of 8 we can encode residuals up to 2**14. + +The scalefactor values are computed as: +scalefactor_tab[s] <- round(pow(s + 1, 2.75)) */ + +static const int qoa_scalefactor_tab[16] = { + 1, 7, 21, 45, 84, 138, 211, 304, 421, 562, 731, 928, 1157, 1419, 1715, 2048 +}; + + +/* The reciprocal_tab maps each of the 16 scalefactors to their rounded +reciprocals 1/scalefactor. This allows us to calculate the scaled residuals in +the encoder with just one multiplication instead of an expensive division. We +do this in .16 fixed point with integers, instead of floats. + +The reciprocal_tab is computed as: +reciprocal_tab[s] <- ((1<<16) + scalefactor_tab[s] - 1) / scalefactor_tab[s] */ + +static const int qoa_reciprocal_tab[16] = { + 65536, 9363, 3121, 1457, 781, 475, 311, 216, 156, 117, 90, 71, 57, 47, 39, 32 +}; + + +/* The dequant_tab maps each of the scalefactors and quantized residuals to +their unscaled & dequantized version. + +Since qoa_div rounds away from the zero, the smallest entries are mapped to 3/4 +instead of 1. The dequant_tab assumes the following dequantized values for each +of the quant_tab indices and is computed as: +float dqt[8] = {0.75, -0.75, 2.5, -2.5, 4.5, -4.5, 7, -7}; +dequant_tab[s][q] <- round_ties_away_from_zero(scalefactor_tab[s] * dqt[q]) + +The rounding employed here is "to nearest, ties away from zero", i.e. positive +and negative values are treated symmetrically. +*/ + +static const int qoa_dequant_tab[16][8] = { + { 1, -1, 3, -3, 5, -5, 7, -7}, + { 5, -5, 18, -18, 32, -32, 49, -49}, + { 16, -16, 53, -53, 95, -95, 147, -147}, + { 34, -34, 113, -113, 203, -203, 315, -315}, + { 63, -63, 210, -210, 378, -378, 588, -588}, + { 104, -104, 345, -345, 621, -621, 966, -966}, + { 158, -158, 528, -528, 950, -950, 1477, -1477}, + { 228, -228, 760, -760, 1368, -1368, 2128, -2128}, + { 316, -316, 1053, -1053, 1895, -1895, 2947, -2947}, + { 422, -422, 1405, -1405, 2529, -2529, 3934, -3934}, + { 548, -548, 1828, -1828, 3290, -3290, 5117, -5117}, + { 696, -696, 2320, -2320, 4176, -4176, 6496, -6496}, + { 868, -868, 2893, -2893, 5207, -5207, 8099, -8099}, + {1064, -1064, 3548, -3548, 6386, -6386, 9933, -9933}, + {1286, -1286, 4288, -4288, 7718, -7718, 12005, -12005}, + {1536, -1536, 5120, -5120, 9216, -9216, 14336, -14336}, +}; + + +/* The Least Mean Squares Filter is the heart of QOA. It predicts the next +sample based on the previous 4 reconstructed samples. It does so by continuously +adjusting 4 weights based on the residual of the previous prediction. + +The next sample is predicted as the sum of (weight[i] * history[i]). + +The adjustment of the weights is done with a "Sign-Sign-LMS" that adds or +subtracts the residual to each weight, based on the corresponding sample from +the history. This, surprisingly, is sufficient to get worthwhile predictions. + +This is all done with fixed point integers. Hence the right-shifts when updating +the weights and calculating the prediction. */ + +static int qoa_lms_predict(qoa_lms_t *lms) { + int prediction = 0; + for (int i = 0; i < QOA_LMS_LEN; i++) { + prediction += lms->weights[i] * lms->history[i]; + } + return prediction >> 13; +} + +static void qoa_lms_update(qoa_lms_t *lms, int sample, int residual) { + int delta = residual >> 4; + for (int i = 0; i < QOA_LMS_LEN; i++) { + lms->weights[i] += lms->history[i] < 0 ? -delta : delta; + } + + for (int i = 0; i < QOA_LMS_LEN-1; i++) { + lms->history[i] = lms->history[i+1]; + } + lms->history[QOA_LMS_LEN-1] = sample; +} + + +/* qoa_div() implements a rounding division, but avoids rounding to zero for +small numbers. E.g. 0.1 will be rounded to 1. Note that 0 itself still +returns as 0, which is handled in the qoa_quant_tab[]. +qoa_div() takes an index into the .16 fixed point qoa_reciprocal_tab as an +argument, so it can do the division with a cheaper integer multiplication. */ + +static inline int qoa_div(int v, int scalefactor) { + int reciprocal = qoa_reciprocal_tab[scalefactor]; + int n = (v * reciprocal + (1 << 15)) >> 16; + n = n + ((v > 0) - (v < 0)) - ((n > 0) - (n < 0)); /* round away from 0 */ + return n; +} + +static inline int qoa_clamp(int v, int min, int max) { + if (v < min) { return min; } + if (v > max) { return max; } + return v; +} + +/* This specialized clamp function for the signed 16 bit range improves decode +performance quite a bit. The extra if() statement works nicely with the CPUs +branch prediction as this branch is rarely taken. */ + +static inline int qoa_clamp_s16(int v) { + if ((unsigned int)(v + 32768) > 65535) { + if (v < -32768) { return -32768; } + if (v > 32767) { return 32767; } + } + return v; +} + +static inline qoa_uint64_t qoa_read_u64(const unsigned char *bytes, unsigned int *p) { + bytes += *p; + *p += 8; + return + ((qoa_uint64_t)(bytes[0]) << 56) | ((qoa_uint64_t)(bytes[1]) << 48) | + ((qoa_uint64_t)(bytes[2]) << 40) | ((qoa_uint64_t)(bytes[3]) << 32) | + ((qoa_uint64_t)(bytes[4]) << 24) | ((qoa_uint64_t)(bytes[5]) << 16) | + ((qoa_uint64_t)(bytes[6]) << 8) | ((qoa_uint64_t)(bytes[7]) << 0); +} + +static inline void qoa_write_u64(qoa_uint64_t v, unsigned char *bytes, unsigned int *p) { + bytes += *p; + *p += 8; + bytes[0] = (v >> 56) & 0xff; + bytes[1] = (v >> 48) & 0xff; + bytes[2] = (v >> 40) & 0xff; + bytes[3] = (v >> 32) & 0xff; + bytes[4] = (v >> 24) & 0xff; + bytes[5] = (v >> 16) & 0xff; + bytes[6] = (v >> 8) & 0xff; + bytes[7] = (v >> 0) & 0xff; +} + + +/* ----------------------------------------------------------------------------- + Encoder */ + +unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes) { + unsigned int p = 0; + qoa_write_u64(((qoa_uint64_t)QOA_MAGIC << 32) | qoa->samples, bytes, &p); + return p; +} + +unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes) { + unsigned int channels = qoa->channels; + + unsigned int p = 0; + unsigned int slices = (frame_len + QOA_SLICE_LEN - 1) / QOA_SLICE_LEN; + unsigned int frame_size = QOA_FRAME_SIZE(channels, slices); + int prev_scalefactor[QOA_MAX_CHANNELS] = {0}; + + /* Write the frame header */ + qoa_write_u64(( + (qoa_uint64_t)qoa->channels << 56 | + (qoa_uint64_t)qoa->samplerate << 32 | + (qoa_uint64_t)frame_len << 16 | + (qoa_uint64_t)frame_size + ), bytes, &p); + + + for (unsigned int c = 0; c < channels; c++) { + /* Write the current LMS state */ + qoa_uint64_t weights = 0; + qoa_uint64_t history = 0; + for (int i = 0; i < QOA_LMS_LEN; i++) { + history = (history << 16) | (qoa->lms[c].history[i] & 0xffff); + weights = (weights << 16) | (qoa->lms[c].weights[i] & 0xffff); + } + qoa_write_u64(history, bytes, &p); + qoa_write_u64(weights, bytes, &p); + } + + /* We encode all samples with the channels interleaved on a slice level. + E.g. for stereo: (ch-0, slice 0), (ch 1, slice 0), (ch 0, slice 1), ...*/ + for (unsigned int sample_index = 0; sample_index < frame_len; sample_index += QOA_SLICE_LEN) { + + for (unsigned int c = 0; c < channels; c++) { + int slice_len = qoa_clamp(QOA_SLICE_LEN, 0, frame_len - sample_index); + int slice_start = sample_index * channels + c; + int slice_end = (sample_index + slice_len) * channels + c; + + /* Brute for search for the best scalefactor. Just go through all + 16 scalefactors, encode all samples for the current slice and + meassure the total squared error. */ + qoa_uint64_t best_rank = -1; + #ifdef QOA_RECORD_TOTAL_ERROR + qoa_uint64_t best_error = -1; + #endif + qoa_uint64_t best_slice = 0; + qoa_lms_t best_lms; + int best_scalefactor = 0; + + for (int sfi = 0; sfi < 16; sfi++) { + /* There is a strong correlation between the scalefactors of + neighboring slices. As an optimization, start testing + the best scalefactor of the previous slice first. */ + int scalefactor = (sfi + prev_scalefactor[c]) % 16; + + /* We have to reset the LMS state to the last known good one + before trying each scalefactor, as each pass updates the LMS + state when encoding. */ + qoa_lms_t lms = qoa->lms[c]; + qoa_uint64_t slice = scalefactor; + qoa_uint64_t current_rank = 0; + #ifdef QOA_RECORD_TOTAL_ERROR + qoa_uint64_t current_error = 0; + #endif + + for (int si = slice_start; si < slice_end; si += channels) { + int sample = sample_data[si]; + int predicted = qoa_lms_predict(&lms); + + int residual = sample - predicted; + int scaled = qoa_div(residual, scalefactor); + int clamped = qoa_clamp(scaled, -8, 8); + int quantized = qoa_quant_tab[clamped + 8]; + int dequantized = qoa_dequant_tab[scalefactor][quantized]; + int reconstructed = qoa_clamp_s16(predicted + dequantized); + + + /* If the weights have grown too large, we introduce a penalty + here. This prevents pops/clicks in certain problem cases */ + int weights_penalty = (( + lms.weights[0] * lms.weights[0] + + lms.weights[1] * lms.weights[1] + + lms.weights[2] * lms.weights[2] + + lms.weights[3] * lms.weights[3] + ) >> 18) - 0x8ff; + if (weights_penalty < 0) { + weights_penalty = 0; + } + + long long error = (sample - reconstructed); + qoa_uint64_t error_sq = error * error; + + current_rank += error_sq + weights_penalty * weights_penalty; + #ifdef QOA_RECORD_TOTAL_ERROR + current_error += error_sq; + #endif + if (current_rank > best_rank) { + break; + } + + qoa_lms_update(&lms, reconstructed, dequantized); + slice = (slice << 3) | quantized; + } + + if (current_rank < best_rank) { + best_rank = current_rank; + #ifdef QOA_RECORD_TOTAL_ERROR + best_error = current_error; + #endif + best_slice = slice; + best_lms = lms; + best_scalefactor = scalefactor; + } + } + + prev_scalefactor[c] = best_scalefactor; + + qoa->lms[c] = best_lms; + #ifdef QOA_RECORD_TOTAL_ERROR + qoa->error += best_error; + #endif + + /* If this slice was shorter than QOA_SLICE_LEN, we have to left- + shift all encoded data, to ensure the rightmost bits are the empty + ones. This should only happen in the last frame of a file as all + slices are completely filled otherwise. */ + best_slice <<= (QOA_SLICE_LEN - slice_len) * 3; + qoa_write_u64(best_slice, bytes, &p); + } + } + + return p; +} + +void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len) { + if ( + qoa->samples == 0 || + qoa->samplerate == 0 || qoa->samplerate > 0xffffff || + qoa->channels == 0 || qoa->channels > QOA_MAX_CHANNELS + ) { + return NULL; + } + + /* Calculate the encoded size and allocate */ + unsigned int num_frames = (qoa->samples + QOA_FRAME_LEN-1) / QOA_FRAME_LEN; + unsigned int num_slices = (qoa->samples + QOA_SLICE_LEN-1) / QOA_SLICE_LEN; + unsigned int encoded_size = 8 + /* 8 byte file header */ + num_frames * 8 + /* 8 byte frame headers */ + num_frames * QOA_LMS_LEN * 4 * qoa->channels + /* 4 * 4 bytes lms state per channel */ + num_slices * 8 * qoa->channels; /* 8 byte slices */ + + unsigned char *bytes = (unsigned char *)QOA_MALLOC(encoded_size); + + for (unsigned int c = 0; c < qoa->channels; c++) { + /* Set the initial LMS weights to {0, 0, -1, 2}. This helps with the + prediction of the first few ms of a file. */ + qoa->lms[c].weights[0] = 0; + qoa->lms[c].weights[1] = 0; + qoa->lms[c].weights[2] = -(1<<13); + qoa->lms[c].weights[3] = (1<<14); + + /* Explicitly set the history samples to 0, as we might have some + garbage in there. */ + for (int i = 0; i < QOA_LMS_LEN; i++) { + qoa->lms[c].history[i] = 0; + } + } + + + /* Encode the header and go through all frames */ + unsigned int p = qoa_encode_header(qoa, bytes); + #ifdef QOA_RECORD_TOTAL_ERROR + qoa->error = 0; + #endif + + int frame_len = QOA_FRAME_LEN; + for (unsigned int sample_index = 0; sample_index < qoa->samples; sample_index += frame_len) { + frame_len = qoa_clamp(QOA_FRAME_LEN, 0, qoa->samples - sample_index); + const short *frame_samples = sample_data + sample_index * qoa->channels; + unsigned int frame_size = qoa_encode_frame(frame_samples, qoa, frame_len, bytes + p); + p += frame_size; + } + + *out_len = p; + return bytes; +} + + + +/* ----------------------------------------------------------------------------- + Decoder */ + +unsigned int qoa_max_frame_size(qoa_desc *qoa) { + return QOA_FRAME_SIZE(qoa->channels, QOA_SLICES_PER_FRAME); +} + +unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa) { + unsigned int p = 0; + if (size < QOA_MIN_FILESIZE) { + return 0; + } + + + /* Read the file header, verify the magic number ('qoaf') and read the + total number of samples. */ + qoa_uint64_t file_header = qoa_read_u64(bytes, &p); + + if ((file_header >> 32) != QOA_MAGIC) { + return 0; + } + + qoa->samples = file_header & 0xffffffff; + if (!qoa->samples) { + return 0; + } + + /* Peek into the first frame header to get the number of channels and + the samplerate. */ + qoa_uint64_t frame_header = qoa_read_u64(bytes, &p); + qoa->channels = (frame_header >> 56) & 0x0000ff; + qoa->samplerate = (frame_header >> 32) & 0xffffff; + + if (qoa->channels == 0 || qoa->samples == 0 || qoa->samplerate == 0) { + return 0; + } + + return 8; +} + +unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len) { + unsigned int p = 0; + *frame_len = 0; + + if (size < 8 + QOA_LMS_LEN * 4 * qoa->channels) { + return 0; + } + + /* Read and verify the frame header */ + qoa_uint64_t frame_header = qoa_read_u64(bytes, &p); + unsigned int channels = (frame_header >> 56) & 0x0000ff; + unsigned int samplerate = (frame_header >> 32) & 0xffffff; + unsigned int samples = (frame_header >> 16) & 0x00ffff; + unsigned int frame_size = (frame_header ) & 0x00ffff; + + unsigned int data_size = frame_size - 8 - QOA_LMS_LEN * 4 * channels; + unsigned int num_slices = data_size / 8; + unsigned int max_total_samples = num_slices * QOA_SLICE_LEN; + + if ( + channels != qoa->channels || + samplerate != qoa->samplerate || + frame_size > size || + samples * channels > max_total_samples + ) { + return 0; + } + + + /* Read the LMS state: 4 x 2 bytes history, 4 x 2 bytes weights per channel */ + for (unsigned int c = 0; c < channels; c++) { + qoa_uint64_t history = qoa_read_u64(bytes, &p); + qoa_uint64_t weights = qoa_read_u64(bytes, &p); + for (int i = 0; i < QOA_LMS_LEN; i++) { + qoa->lms[c].history[i] = ((signed short)(history >> 48)); + history <<= 16; + qoa->lms[c].weights[i] = ((signed short)(weights >> 48)); + weights <<= 16; + } + } + + + /* Decode all slices for all channels in this frame */ + for (unsigned int sample_index = 0; sample_index < samples; sample_index += QOA_SLICE_LEN) { + for (unsigned int c = 0; c < channels; c++) { + qoa_uint64_t slice = qoa_read_u64(bytes, &p); + + int scalefactor = (slice >> 60) & 0xf; + slice <<= 4; + + int slice_start = sample_index * channels + c; + int slice_end = qoa_clamp(sample_index + QOA_SLICE_LEN, 0, samples) * channels + c; + + for (int si = slice_start; si < slice_end; si += channels) { + int predicted = qoa_lms_predict(&qoa->lms[c]); + int quantized = (slice >> 61) & 0x7; + int dequantized = qoa_dequant_tab[scalefactor][quantized]; + int reconstructed = qoa_clamp_s16(predicted + dequantized); + + sample_data[si] = reconstructed; + slice <<= 3; + + qoa_lms_update(&qoa->lms[c], reconstructed, dequantized); + } + } + } + + *frame_len = samples; + return p; +} + +short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *qoa) { + unsigned int p = qoa_decode_header(bytes, size, qoa); + if (!p) { + return NULL; + } + + /* Calculate the required size of the sample buffer and allocate */ + int total_samples = qoa->samples * qoa->channels; + short *sample_data = (short *)QOA_MALLOC(total_samples * sizeof(short)); + + unsigned int sample_index = 0; + unsigned int frame_len; + unsigned int frame_size; + + /* Decode all frames */ + do { + short *sample_ptr = sample_data + sample_index * qoa->channels; + frame_size = qoa_decode_frame(bytes + p, size - p, qoa, sample_ptr, &frame_len); + + p += frame_size; + sample_index += frame_len; + } while (frame_size && sample_index < qoa->samples); + + qoa->samples = sample_index; + return sample_data; +} + + + +/* ----------------------------------------------------------------------------- + File read/write convenience functions */ + +#ifndef QOA_NO_STDIO +#include + +int qoa_write(const char *filename, const short *sample_data, qoa_desc *qoa) { + FILE *f = fopen(filename, "wb"); + unsigned int size; + void *encoded; + + if (!f) { + return 0; + } + + encoded = qoa_encode(sample_data, qoa, &size); + if (!encoded) { + fclose(f); + return 0; + } + + fwrite(encoded, 1, size, f); + fclose(f); + + QOA_FREE(encoded); + return size; +} + +void *qoa_read(const char *filename, qoa_desc *qoa) { + FILE *f = fopen(filename, "rb"); + int size, bytes_read; + void *data; + short *sample_data; + + if (!f) { + return NULL; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + if (size <= 0) { + fclose(f); + return NULL; + } + fseek(f, 0, SEEK_SET); + + data = (unsigned char *)QOA_MALLOC(size); + if (!data) { + fclose(f); + return NULL; + } + + bytes_read = fread(data, 1, size, f); + fclose(f); + + sample_data = qoa_decode(data, bytes_read, qoa); + QOA_FREE(data); + return sample_data; +} + +#endif /* QOA_NO_STDIO */ +#endif /* QOA_IMPLEMENTATION */ diff --git a/Maintainer/XTConvert/xtconvert_cli.cpp b/Maintainer/XTConvert/xtconvert_cli.cpp index 929d8bb29b..60cf630179 100644 --- a/Maintainer/XTConvert/xtconvert_cli.cpp +++ b/Maintainer/XTConvert/xtconvert_cli.cpp @@ -3,10 +3,13 @@ int main(int argc, char** argv) { XTConvert::Spec s; - s.target_platform = XTConvert::TargetPlatform::TPL; + s.target_platform = XTConvert::TargetPlatform::DSG; + + s.package_type = XTConvert::PackageType::Episode; s.input_dir = argv[1]; - s.destination = argv[2]; + s.use_assets_dir = argv[2]; + s.destination = argv[3]; return !XTConvert::Convert(s); } From 372ae6232a19afc38c55f7190894f925d74bb918 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sat, 30 Nov 2024 13:34:27 -0500 Subject: [PATCH 022/286] libxtconvert.cpp: add GIF files to graphics lists --- Maintainer/XTConvert/libxtconvert.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index c8461bd4b6..721cf05ca6 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -361,7 +361,7 @@ class Converter // int orig_h = FreeImage_GetHeight(image); // 2x downscale by default - if(!m_cur_dir.textures_1x.contains(filename)) + if(output_format != TargetPlatform::Desktop && !m_cur_dir.textures_1x.contains(filename)) { FIBITMAP* scaled = GraphicsLoad::fast2xScaleDown(image); FreeImage_Unload(image); @@ -386,6 +386,7 @@ class Converter save_success = QFile::copy(mask_path, out_path.chopped(4) + "m.gif"); // set the size text here...! + size_text = QString("%1\n%2\n").arg(image_w, 4).arg(image_h, 4); } else if(output_format == TargetPlatform::T3X) { @@ -613,7 +614,7 @@ class Converter write_graphics_list = &m_main_graphics_list; // write graphics list - if(write_graphics_list) + if(write_graphics_list && save_success && !size_text.isEmpty()) { QString type, index; @@ -683,6 +684,9 @@ class Converter || filename.contains("mount"); } + if(output_format == TargetPlatform::Desktop) + make_size_file = false; + // write to size file if(make_size_file && save_success && !size_text.isEmpty()) { From da680c05aa59ac61b9b889449493512dd5fde215 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sat, 30 Nov 2024 15:30:41 -0500 Subject: [PATCH 023/286] libxtconvert.cpp: save mask images resulting from PNGs --- Maintainer/XTConvert/libxtconvert.cpp | 136 ++++++++++++++++---------- 1 file changed, 84 insertions(+), 52 deletions(-) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 721cf05ca6..549e0669f6 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -235,15 +235,45 @@ class Converter QString m_error; + bool save_mask(FIBITMAP* mask, const QString& mask_path) + { + // convert to 8-bit paletted mask + FIBITMAP* mask_2 = FreeImage_ColorQuantizeEx(mask, FIQ_LFPQUANT, 2); + if(mask_2) + { + // the above call actually makes an 8-bit image, and we want a 1-bit image if possible + FreeImage_Unload(mask_2); + + // use black and white + mask_2 = FreeImage_Threshold(mask, 127); + } + else + mask_2 = FreeImage_ConvertTo8Bits(mask); + + if(mask_2) + { + FreeImage_Save(FIF_GIF, mask_2, mask_path.toUtf8()); + return true; + } + + return false; + } + bool convert_image(const QString& filename, const QString& in_path, const QString& out_path) { FIBITMAP* image = GraphicsLoad::loadImage(in_path); if(!image) - return false; + { + qInfo() << "Warning: invalid graphics file at [" << in_path << "], skipping..."; + return true; + } + + QString filename_type, filename_index; + bool standard_filename_format = validate_image_filename(filename, filename_type, filename_index); FIBITMAP* mask = nullptr; QString mask_path; - if(filename.endsWith(".gif")) + if(filename.endsWith(".gif") && filename_type != "background2" && filename_type != "tile") { // find the mask!! QString filename_stem = filename.chopped(4); @@ -254,26 +284,32 @@ class Converter mask = GraphicsLoad::loadImage(mask_path); } - if(!mask) - mask_path.clear(); - // keep trying to find mask, in fallback dir and in appropriate graphics dir - for(int i = 0; i < 2; i++) + for(int i = 0; i < 3; i++) { if(mask) break; - QString dir_piece = (i == 0) ? "fallback" : filename.split('-')[0]; auto s = QDir::separator(); - QString dir_to_check = m_assets_dir.path() + s + "graphics" + s + dir_piece + s; + + QString dir_to_check; + + if(i == 0) + dir_to_check = m_cur_dir.dir.path() + s + ".." + s; + else if(i == 1) + dir_to_check = m_assets_dir.path() + s + "graphics" + s + "fallback" + s; + else + dir_to_check = m_assets_dir.path() + s + "graphics" + s + filename.split('-')[0] + s; // look for mask GIF - mask = GraphicsLoad::loadImage(dir_to_check + filename_stem + "m.gif"); + mask_path = dir_to_check + filename_stem + "m.gif"; + mask = GraphicsLoad::loadImage(mask_path); if(mask) break; // look for PNG + mask_path.clear(); mask = GraphicsLoad::loadImage(dir_to_check + filename_stem + ".png"); // convert PNG to mask @@ -282,6 +318,8 @@ class Converter } // Okay, finding a mask failed. Imagine that image is fully opaque in that case, and ignore the mask logic. + if(!mask) + mask_path.clear(); // qInfo() << "Failed to find mask for GIF " << in_path; } @@ -291,11 +329,9 @@ class Converter if(m_spec.convert_gifs != ConvertGIFs::All && GraphicsLoad::validateBitmaskRequired(image, mask)) output_format = TargetPlatform::Desktop; else if(mask) - GraphicsLoad::mergeWithMask(image, mask); - - // either way we can free the mask bitmap now - if(mask) { + // merge with mask and free + GraphicsLoad::mergeWithMask(image, mask); FreeImage_Unload(mask); mask = nullptr; } @@ -305,37 +341,16 @@ class Converter // save mask fallback here if we are in the global graphics dir if(m_cur_dir.make_fallback_masks && output_format != TargetPlatform::Desktop) { - QString type, index; - FIBITMAP* fallback_mask = nullptr; - // make sure that the texture is valid and should have transparency - if(validate_image_filename(filename, type, index) && type != "background2" && type != "tile") + if(standard_filename_format && filename_type != "background2" && filename_type != "tile") fallback_mask = FreeImage_Clone(image); - // convert to 8-bit paletted mask if(fallback_mask) { GraphicsLoad::RGBAToMask(fallback_mask); - // should not fail (there are only 256 shades of gray) - FIBITMAP* fallback_mask_2 = FreeImage_ColorQuantizeEx(fallback_mask, FIQ_LFPQUANT, 2); - if(fallback_mask_2) - { - FreeImage_Unload(fallback_mask_2); - fallback_mask_2 = FreeImage_Threshold(fallback_mask, 127); - } - else - fallback_mask_2 = FreeImage_ConvertTo8Bits(fallback_mask); - - FreeImage_Unload(fallback_mask); - - fallback_mask = fallback_mask_2; - } - - if(fallback_mask) - { QString fallback_mask_path = m_temp_dir.filePath("graphics/fallback/"); // make fallback folder if it isn't present @@ -347,7 +362,7 @@ class Converter // don't clobber a version copied from the original fallback folder if(!QFileInfo::exists(fallback_mask_path)) - FreeImage_Save(FIF_GIF, fallback_mask, fallback_mask_path.toUtf8()); + save_mask(fallback_mask, fallback_mask_path); FreeImage_Unload(fallback_mask); } @@ -366,7 +381,10 @@ class Converter FIBITMAP* scaled = GraphicsLoad::fast2xScaleDown(image); FreeImage_Unload(image); if(!scaled) + { + qInfo() << "Scaling failed at [" << in_path << "], aborting..."; return false; + } image = scaled; } @@ -382,8 +400,17 @@ class Converter qInfo() << "mask required for" << in_path; save_success = QFile::copy(in_path, out_path); - if(save_success && !mask_path.isEmpty()) - save_success = QFile::copy(mask_path, out_path.chopped(4) + "m.gif"); + if(save_success && mask) + { + QString mask_out_path = out_path.chopped(4) + "m.gif"; + + if(!mask_path.isEmpty()) + save_success = QFile::copy(mask_path, mask_out_path); + else + save_success = save_mask(mask, mask_out_path); + + FreeImage_Unload(mask); + } // set the size text here...! size_text = QString("%1\n%2\n").arg(image_w, 4).arg(image_h, 4); @@ -535,7 +562,10 @@ class Converter FreeImage_Unload(image); if(!scaled) + { + qInfo() << "Scaling failed at [" << in_path << "], aborting..."; return false; + } image = scaled; } @@ -547,6 +577,7 @@ class Converter if(!p.convert(PaletteTex::HALF, 0)) { FreeImage_Unload(image); + qInfo() << "Palettizing failed at [" << in_path << "], aborting..."; return false; } @@ -616,20 +647,18 @@ class Converter // write graphics list if(write_graphics_list && save_success && !size_text.isEmpty()) { - QString type, index; - - bool valid = validate_image_filename(filename, type, index); + bool valid = standard_filename_format; // only allow valid subdirectories for main graphics list if(valid && write_graphics_list == &m_main_graphics_list) { // special case: yoshib and yoshit stored in yoshi - if(type.startsWith('y')) + if(filename_type.startsWith('y')) { if(!m_cur_dir.dir.path().endsWith("yoshi")) valid = false; } - else if(!m_cur_dir.dir.path().endsWith(type)) + else if(!m_cur_dir.dir.path().endsWith(filename_type)) valid = false; } @@ -640,9 +669,9 @@ class Converter QDir graphics_list_parent = fi.dir(); QString relative_out_path = graphics_list_parent.relativeFilePath(used_out_path); - write_graphics_list->write(type.toUtf8()); + write_graphics_list->write(filename_type.toUtf8()); write_graphics_list->write(" "); - write_graphics_list->write(index.toUtf8()); + write_graphics_list->write(filename_index.toUtf8()); write_graphics_list->write("\n"); write_graphics_list->write(relative_out_path.toUtf8()); write_graphics_list->write("\n"); @@ -669,17 +698,17 @@ class Converter else if(m_cur_dir.make_size_files == DirInfo::SIZEFILES_PREVIEW_ASSETS) { // UI and background sprites - make_size_file = in_path.contains("/ui/") - || filename.contains("/background2/"); + make_size_file = in_path.contains("/graphics/ui/") + || in_path.contains("/graphics/background2/"); } else if(m_cur_dir.make_size_files == DirInfo::SIZEFILES_PREVIEW_EPISODE) { // player and mount sprites - make_size_file = filename.contains("mario") - || filename.contains("luigi") - || filename.contains("peach") - || filename.contains("toad") - || filename.contains("link") + make_size_file = filename_type == "mario" + || filename_type == "luigi" + || filename_type == "peach" + || filename_type == "toad" + || filename_type == "link" || filename.contains("yoshi") || filename.contains("mount"); } @@ -698,6 +727,9 @@ class Converter file.close(); } + if(!save_success) + qInfo() << "Saving failed at [" << in_path << "], aborting..."; + return save_success; } From e71414849882bbf5f467581046759b5f42a32aa8 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sat, 30 Nov 2024 16:15:51 -0500 Subject: [PATCH 024/286] graphics_load.cpp: fast2xScaleDown - fix failure on 1px textures --- Maintainer/XTConvert/graphics_load.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Maintainer/XTConvert/graphics_load.cpp b/Maintainer/XTConvert/graphics_load.cpp index 741c1cafa4..bd3d8713e5 100644 --- a/Maintainer/XTConvert/graphics_load.cpp +++ b/Maintainer/XTConvert/graphics_load.cpp @@ -165,17 +165,27 @@ FIBITMAP *GraphicsLoad::fast2xScaleDown(FIBITMAP *image) const uint32_t *src_pixels = reinterpret_cast(FreeImage_GetBits(image)); auto src_pitch_px = static_cast(FreeImage_GetPitch(image)) / 4; - FIBITMAP *dest = FreeImage_Allocate(src_w / 2, src_h / 2, 32); + // FIXME: round up instead of down + auto dest_w = src_w / 2; + auto dest_h = src_h / 2; + + if(dest_w == 0) + dest_w = 1; + + if(dest_h == 0) + dest_h = 1; + + FIBITMAP *dest = FreeImage_Allocate(dest_w, dest_h, 32); if(!dest) return nullptr; - uint32_t *dest_pixels = reinterpret_cast(FreeImage_GetBits(dest)); + uint32_t *dest_pixels = reinterpret_cast(FreeImage_GetBits(dest)); auto dest_pitch_px = static_cast(FreeImage_GetPitch(dest)) / 4; - for(uint32_t src_y = 0, dest_y = 0; dest_y < src_h / 2; src_y += 2, dest_y += 1) + for(uint32_t src_y = 0, dest_y = 0; dest_y < dest_h; src_y += 2, dest_y += 1) { - for(uint32_t src_x = 0, dest_x = 0; dest_x < src_w / 2; src_x += 2, dest_x += 1) + for(uint32_t src_x = 0, dest_x = 0; dest_x < dest_w; src_x += 2, dest_x += 1) { dest_pixels[dest_y * dest_pitch_px + dest_x] = src_pixels[src_y * src_pitch_px + src_x]; } From dc770d2af854f261c5ef4fa76650da67b279169e Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sat, 30 Nov 2024 19:36:20 -0500 Subject: [PATCH 025/286] libxtconvert.cpp: update _meta.ini for asset packs --- Maintainer/XTConvert/libxtconvert.cpp | 101 ++++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 6 deletions(-) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 549e0669f6..696ffcedb0 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -461,8 +461,6 @@ class Converter memcpy(params.input_img.pixels.data() + params.input_img.stride * row, FreeImage_GetBits(image) + stride * (1024 * i + row), params.input_img.w * sizeof(Tex3DS::RGBA)); } - // FIXME: check if possible to do 5551 here, or possibly just do 5551 by force (maybe unless there's intermediate opacity?) - params.output_path = used_out_path.toUtf8(); if(i > 0) params.output_path.push_back('0' + i); @@ -928,19 +926,110 @@ class Converter m_cur_dir.graphics_list->close(); if(m_spec.package_type == PackageType::Episode) - update_meta_episode(); + return update_meta_episode(); else - update_meta_asset_pack(); + return update_meta_asset_pack(); + } + bool update_meta_episode() + { return true; } - void update_meta_episode() + const char* platform_name() const { + switch(m_spec.target_platform) + { + case TargetPlatform::Desktop: + return "main"; + case TargetPlatform::TPL: + return "wii"; + case TargetPlatform::T3X: + return "3ds"; + case TargetPlatform::DSG: + return "dsi"; + default: + return "unknown"; + } } - void update_meta_asset_pack() + bool update_meta_asset_pack() { + // load several values from gameinfo + QSettings gameinfo(m_temp_dir.filePath("gameinfo.ini"), QSettings::IniFormat); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + gameinfo.setIniCodec("UTF-8"); +#endif + + gameinfo.beginGroup("game"); + QString pack_id = gameinfo.value("id", "").toString(); + QString version = gameinfo.value("version", "").toString(); + bool show_id = gameinfo.value("show-id", false).toBool(); + gameinfo.endGroup(); + + gameinfo.beginGroup("android"); + QString gfx_logo = gameinfo.value("logo", "").toString(); + QString gfx_background = gameinfo.value("background", "").toString(); + int gfx_background_frames = gameinfo.value("background-frames", 0).toInt(); + int gfx_background_delay = gameinfo.value("background-delay", 0).toInt(); + gameinfo.endGroup(); + + // validate: + if(pack_id.isEmpty()) + { + qInfo() << "Failed: gameinfo.ini does not have a pack id"; + return false; + } + + + // save info to _meta.ini + QSettings meta(m_temp_dir.filePath("_meta.ini"), QSettings::IniFormat); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + meta.setIniCodec("UTF-8"); +#endif + + // contents + meta.beginGroup("content"); + + meta.setValue("engine", "TheXTech"); + meta.setValue("platform", platform_name()); + meta.setValue("pack-id", pack_id); + + if(!version.isEmpty()) + meta.setValue("version", version); + + meta.endGroup(); + + // properties + meta.beginGroup("properties"); + + if(show_id) + meta.setValue("show-id", show_id); + + if(!gfx_logo.isEmpty()) + meta.setValue("logo", gfx_logo); + + if(!gfx_background.isEmpty()) + meta.setValue("background", gfx_background); + + if(gfx_background_frames) + meta.setValue("background-frames", gfx_background_frames); + + if(gfx_background_delay) + meta.setValue("background-delay", gfx_background_delay); + + meta.endGroup(); + + // finish and save + meta.sync(); + + if(meta.status() != QSettings::NoError) + { + qInfo() << "Failed: could not update _meta.ini"; + return false; + } + + return true; } bool build_soundbank() From cb05c05c71bed4f46f2df893138844fca0d3d8b0 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:43:19 -0500 Subject: [PATCH 026/286] XTConvert: update Wii texture conversion code Fixes horizontally-arranged textures --- Maintainer/XTConvert/libxtconvert.cpp | 97 ++++++++++++++++--------- Maintainer/XTConvert/src/export_tpl.cpp | 4 +- Maintainer/XTConvert/src/export_tpl.h | 2 +- 3 files changed, 67 insertions(+), 36 deletions(-) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 696ffcedb0..433e7e9834 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -477,48 +477,79 @@ class Converter FreeImage_FlipVertical(image); // palettize texture - int input_w = FreeImage_GetWidth(image); + int input_w_full = FreeImage_GetWidth(image); int input_h_full = FreeImage_GetHeight(image); - int stride = FreeImage_GetPitch(image); - - save_success = false; - for(int i = 0; i < 3; i++) - { - int start_y = i * 1024; - int input_h = input_h_full - start_y; - if(input_h <= 0) - break; - if(input_h > 1024) - input_h = 1024; + // do PaletteTex conversion on the whole thing + PaletteTex p(FreeImage_GetBits(image), input_w_full, input_h_full); - PaletteTex p(FreeImage_GetBits(image) + stride * start_y, input_w, input_h); + save_success = true; - PaletteTex::PaletteSize used_size = PaletteTex::HALF; - if(!p.convert(used_size, 90)) - { - used_size = PaletteTex::BYTE; + PaletteTex::PaletteSize used_size = PaletteTex::HALF; + if(!p.convert(used_size, 90)) + { + used_size = PaletteTex::BYTE; - if(!p.convert(used_size, 0)) - break; - } + if(!p.convert(used_size, 0)) + save_success = false; + } - QByteArray tpl_data; - tpl_data.resize(ExportTPL::data_size(used_size, input_w, input_h)); - ExportTPL::fill_data(reinterpret_cast(&tpl_data[0]), used_size, input_w, input_h, p.palette(), p.indexes()); + if(save_success) + { + // decide whether to do horizontal mode here + bool horiz_mode = (input_w_full > 1024 && input_h_full <= 1024); - auto tpl_path = used_out_path; - if(i > 0) - tpl_path += char('0' + i); + save_success = false; - QFile file(tpl_path); - if(!file.open(QIODevice::WriteOnly)) - break; - else + for(int i = 0; i < 3; i++) { - file.write(tpl_data); - file.close(); - save_success = true; + // resolve correct portion of image to save + int start_x = 0; + int start_y = 0; + + int input_w = input_w_full; + int input_h = input_h_full; + + if(horiz_mode) + { + start_x = i * 1024; + input_w = input_w_full - start_x; + if(input_w <= 0) + break; + } + else + { + start_y = i * 1024; + input_h = input_h_full - start_y; + if(input_h <= 0) + break; + } + + if(input_w > 1024) + input_w = 1024; + if(input_h > 1024) + input_h = 1024; + + // fill TPL data + auto input_data = p.indexes() + input_w_full * start_y + start_x; + + QByteArray tpl_data; + tpl_data.resize(ExportTPL::data_size(used_size, input_w, input_h)); + ExportTPL::fill_data(reinterpret_cast(&tpl_data[0]), used_size, input_w, input_h, p.palette(), input_data, input_w_full); + + auto tpl_path = used_out_path; + if(i > 0) + tpl_path += char('0' + i); + + QFile file(tpl_path); + if(!file.open(QIODevice::WriteOnly)) + break; + else + { + file.write(tpl_data); + file.close(); + save_success = true; + } } } diff --git a/Maintainer/XTConvert/src/export_tpl.cpp b/Maintainer/XTConvert/src/export_tpl.cpp index faf81c2271..7624b32cc4 100644 --- a/Maintainer/XTConvert/src/export_tpl.cpp +++ b/Maintainer/XTConvert/src/export_tpl.cpp @@ -49,7 +49,7 @@ size_t data_size(PaletteTex::PaletteSize index_size, int input_w, int input_h) return file_hdr + img_data + pal_data; } -void fill_data(uint8_t* out, PaletteTex::PaletteSize index_size, int input_w, int input_h, const liq_color* palette, const uint8_t* indexes) +void fill_data(uint8_t* out, PaletteTex::PaletteSize index_size, int input_w, int input_h, const liq_color* palette, const uint8_t* indexes, int input_w_full) { size_t indexes_per_byte = (index_size == PaletteTex::HALF) ? 2 : 1; @@ -143,7 +143,7 @@ void fill_data(uint8_t* out, PaletteTex::PaletteSize index_size, int input_w, in continue; } - const uint8_t* src = indexes + (start_y + dy) * input_w + start_x; + const uint8_t* src = indexes + (start_y + dy) * input_w_full + start_x; if(index_size == PaletteTex::HALF) { diff --git a/Maintainer/XTConvert/src/export_tpl.h b/Maintainer/XTConvert/src/export_tpl.h index 63baace93f..e873c435bd 100644 --- a/Maintainer/XTConvert/src/export_tpl.h +++ b/Maintainer/XTConvert/src/export_tpl.h @@ -13,7 +13,7 @@ namespace ExportTPL size_t data_size(PaletteTex::PaletteSize index_size, int input_w, int input_h); -void fill_data(uint8_t* out, PaletteTex::PaletteSize index_size, int input_w, int input_h, const liq_color* palette, const uint8_t* indexes); +void fill_data(uint8_t* out, PaletteTex::PaletteSize index_size, int input_w, int input_h, const liq_color* palette, const uint8_t* indexes, int input_w_full); } // namespace ExportTPL From 8d9959d17ecf97108be114399aaf97d5da3143ae Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sun, 1 Dec 2024 14:16:52 -0500 Subject: [PATCH 027/286] libxtconvert.cpp: update post-process steps --- Maintainer/XTConvert/libxtconvert.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 433e7e9834..c7bc7f622c 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -956,10 +956,18 @@ class Converter if(m_cur_dir.graphics_list) m_cur_dir.graphics_list->close(); + // do post-processing steps + bool post_process_success = true; + if(m_spec.package_type == PackageType::Episode) - return update_meta_episode(); + post_process_success &= update_meta_episode(); else - return update_meta_asset_pack(); + post_process_success &= update_meta_asset_pack(); + + if(m_spec.target_platform == TargetPlatform::DSG && m_spec.package_type == PackageType::AssetPack) + post_process_success &= build_soundbank(); + + return post_process_success; } bool update_meta_episode() From c28ca27c9b3e743cb53eb4db48a2f87270eb0ff1 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:28:37 -0500 Subject: [PATCH 028/286] XTConvert: fix Qt5 compatibility --- Maintainer/XTConvert/CMakeLists.txt | 10 +++---- Maintainer/XTConvert/libxtconvert.cpp | 14 ++++++---- Maintainer/XTConvert/src/export_audio.cpp | 32 +++++++++++------------ 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Maintainer/XTConvert/CMakeLists.txt b/Maintainer/XTConvert/CMakeLists.txt index b5e7f45cbc..ccc601d3a9 100644 --- a/Maintainer/XTConvert/CMakeLists.txt +++ b/Maintainer/XTConvert/CMakeLists.txt @@ -7,7 +7,7 @@ add_subdirectory(3rdparty/tex3ds EXCLUDE_FROM_ALL) add_subdirectory(3rdparty/libimagequant EXCLUDE_FROM_ALL) add_subdirectory(3rdparty/spc2it EXCLUDE_FROM_ALL) include(cmake/build_libarchive.cmake) -find_package(Qt6 REQUIRED COMPONENTS Core) +find_package(Qt5 REQUIRED COMPONENTS Core) add_library(libxtconvert libxtconvert.cpp @@ -17,10 +17,10 @@ add_library(libxtconvert src/export_audio.cpp ) -add_dependencies(libxtconvert archive_static lz4_pack_static libromfs3ds libtex3ds FreeImage_Local libpng_Local SDLMixerX_Local SDL2_Local) +add_dependencies(libxtconvert archive_static lz4_pack_static libromfs3ds libtex3ds FreeImage_Local PGE_FileFormatsQt libpng_Local SDLMixerX_Local SDL2_Local) -target_link_libraries(libxtconvert PRIVATE Qt6::Core) -target_link_libraries(libxtconvert PUBLIC archive_static lz4_pack_static libromfs3ds libtex3ds PGE_FreeImage imagequant_a libspc2it) +target_link_libraries(libxtconvert PRIVATE Qt5::Core) +target_link_libraries(libxtconvert PUBLIC archive_static lz4_pack_static libromfs3ds libtex3ds PGE_FreeImage imagequant_a libspc2it PGE_FileFormatsQt) if(PGE_STATIC_SDLMIXER) target_link_libraries(libxtconvert PUBLIC PGE_SDLMixerX_static PGE_SDL2_static) @@ -34,5 +34,5 @@ target_include_directories(libxtconvert PRIVATE src) project(xtconvert CXX) add_executable(xtconvert_cli xtconvert_cli.cpp) -target_link_libraries(xtconvert_cli PRIVATE Qt6::Core) +target_link_libraries(xtconvert_cli PRIVATE Qt5::Core) target_link_libraries(xtconvert_cli PUBLIC libxtconvert) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index c7bc7f622c..990131b24c 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -1,3 +1,6 @@ +#include +#include + #include #include @@ -13,6 +16,7 @@ #include #include +#include #include "archive.h" #include "archive_entry.h" @@ -461,7 +465,7 @@ class Converter memcpy(params.input_img.pixels.data() + params.input_img.stride * row, FreeImage_GetBits(image) + stride * (1024 * i + row), params.input_img.w * sizeof(Tex3DS::RGBA)); } - params.output_path = used_out_path.toUtf8(); + params.output_path = (const char*)used_out_path.toUtf8(); if(i > 0) params.output_path.push_back('0' + i); @@ -535,7 +539,7 @@ class Converter QByteArray tpl_data; tpl_data.resize(ExportTPL::data_size(used_size, input_w, input_h)); - ExportTPL::fill_data(reinterpret_cast(&tpl_data[0]), used_size, input_w, input_h, p.palette(), input_data, input_w_full); + ExportTPL::fill_data(reinterpret_cast(tpl_data.data()), used_size, input_w, input_h, p.palette(), input_data, input_w_full); auto tpl_path = used_out_path; if(i > 0) @@ -618,7 +622,7 @@ class Converter for(int i = 0; i < 16; ++i) { liq_color c = p.palette()[i]; - uint8_t* dest = reinterpret_cast(&dsg_data[i * 2]); + uint8_t* dest = reinterpret_cast(&dsg_data.data()[i * 2]); write_uint16_le(dest, color_to_rgb5a1(c)); } @@ -629,7 +633,7 @@ class Converter // save image indexes const uint8_t* src = p.indexes(); - uint8_t* out_base = reinterpret_cast(&dsg_data[32]); + uint8_t* out_base = reinterpret_cast(&dsg_data.data()[32]); int out_stride = container_w / 2; for(int row = 0; row < input_h; row++) { @@ -1081,7 +1085,7 @@ class Converter if(m_spec.destination.size() == 0) return false; - QTemporaryFile temp_iso = QTemporaryFile(); + QTemporaryFile temp_iso; if(!temp_iso.open()) return false; diff --git a/Maintainer/XTConvert/src/export_audio.cpp b/Maintainer/XTConvert/src/export_audio.cpp index 28fbcebb45..876e598c3d 100644 --- a/Maintainer/XTConvert/src/export_audio.cpp +++ b/Maintainer/XTConvert/src/export_audio.cpp @@ -33,21 +33,21 @@ void export_wav(QByteArray& out, const Mix_Chunk* chunk) out.resize(44 + chunk->alen); // THIS STRONGLY ASSUMES LITTLE ENDIAN BUILD - memcpy(&out[0], "RIFF", 4); - *(uint32_t *)&out[4] = out.size() - 8; - memcpy(&out[8], "WAVE", 4); - memcpy(&out[12], "fmt ", 4); - *(uint32_t *)&out[16] = 16; - *(uint16_t *)&out[20] = 1; - *(uint16_t *)&out[22] = 2; - *(uint32_t *)&out[24] = samplerate; - *(uint32_t *)&out[28] = samplerate * 4; - *(uint16_t *)&out[32] = 4; - *(uint16_t *)&out[34] = 16; - memcpy(&out[36], "data", 4); - *(uint32_t *)&out[40] = out.size() - 44; - - memcpy(&out[44], chunk->abuf, chunk->alen); + memcpy(&out.data()[0], "RIFF", 4); + *(uint32_t *)&out.data()[4] = out.size() - 8; + memcpy(&out.data()[8], "WAVE", 4); + memcpy(&out.data()[12], "fmt ", 4); + *(uint32_t *)&out.data()[16] = 16; + *(uint16_t *)&out.data()[20] = 1; + *(uint16_t *)&out.data()[22] = 2; + *(uint32_t *)&out.data()[24] = samplerate; + *(uint32_t *)&out.data()[28] = samplerate * 4; + *(uint16_t *)&out.data()[32] = 4; + *(uint16_t *)&out.data()[34] = 16; + memcpy(&out.data()[36], "data", 4); + *(uint32_t *)&out.data()[40] = out.size() - 44; + + memcpy(&out.data()[44], chunk->abuf, chunk->alen); } void export_qoa(QByteArray& out, const Mix_Chunk* chunk) @@ -109,7 +109,7 @@ void export_qoa(QByteArray& out, const Mix_Chunk* chunk) } out.resize(out_len); - memcpy(&out[0], encoded, out_len); + memcpy(&out.data()[0], encoded, out_len); QOA_FREE(encoded); } From afd620b1f03ecfe990600ead8df3f25054fb8753 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:28:52 -0500 Subject: [PATCH 029/286] libxtconvert.cpp: update _meta.ini for episodes (still missing translation support) --- Maintainer/XTConvert/libxtconvert.cpp | 156 +++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 1 deletion(-) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 990131b24c..6ab81de23f 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -153,6 +153,15 @@ class Converter bool convert_font_inis = false; }; + struct EpisodeInfo + { + QString path; + QString title; + std::map title_translations; + bool playstyle_classic = false; + QString char_block; + }; + QDir m_input_dir; QDir m_assets_dir; @@ -162,6 +171,8 @@ class Converter DirInfo m_cur_dir; QFile m_main_graphics_list; + EpisodeInfo m_episode_info; + void sync_cur_dir(const QString& in_file) { QFileInfo fi(in_file); @@ -854,6 +865,82 @@ class Converter } } + bool scan_episode(const QString& in_path) + { + QString rel_path = m_input_dir.relativeFilePath(in_path); + + if(!m_episode_info.path.isEmpty()) + { + qInfo() << "Input directory includes multiple episodes: [" << m_episode_info.path << "] and [" << rel_path << "]"; + return false; + } + + if(rel_path.contains(QDir::separator())) + { + qInfo() << "Episode file must be in top-level directory, not subdirectory:", in_path; + return false; + } + + m_episode_info.path = rel_path; + + WorldData head; + + if(!FileFormats::OpenWorldFileHeader(in_path, head)) + { + qInfo() << "Episode [" << rel_path << "] could not be opened."; + return false; + } + + bool is_wldx = (head.meta.RecentFormat == WorldData::PGEX); + bool is_wld38a = (head.meta.RecentFormat == WorldData::SMBX38A); + if(is_wldx && head.meta.configPackId == "SMBX2") + { + qInfo() << "Episode [" << rel_path << "] targets SMBX2. Cannot proceed."; + return false; + } + + if(is_wld38a) + { + qInfo() << "Episode [" << rel_path << "] targets SMBX-38A. Cannot proceed."; + return false; + } + + // set episode title + m_episode_info.title = head.EpisodeTitle; + + if(m_episode_info.title.isEmpty()) + m_episode_info.title = rel_path; + + // set playstyle + if(is_wldx) + m_episode_info.playstyle_classic = false; + else + m_episode_info.playstyle_classic = true; + + // set character block + m_episode_info.char_block = "00000"; + + if(head.nocharacter1) + m_episode_info.char_block[0] = '1'; + + if(head.nocharacter2) + m_episode_info.char_block[1] = '1'; + + if(head.nocharacter3) + m_episode_info.char_block[2] = '1'; + + if(head.nocharacter4) + m_episode_info.char_block[3] = '1'; + + if(head.nocharacter5) + m_episode_info.char_block[4] = '1'; + + return true; + } + + void scan_episode_translation(const QString& in_path) + {} + bool convert_file(const QString& filename, const QString& in_path, const QString& out_path) { sync_cur_dir(in_path); @@ -886,12 +973,15 @@ class Converter else if(m_spec.package_type == PackageType::Episode && (filename.endsWith(".wld") || filename.endsWith(".wldx"))) { // load the episode information, or complain if there are multiple WLD(X) files - qInfo() << "copying" << out_path; + if(!scan_episode(in_path)) + return false; + return QFile::copy(in_path, out_path); } else if(m_spec.package_type == PackageType::Episode && filename.startsWith("translation_") && filename.endsWith(".json")) { // load the translated episode name + scan_episode_translation(in_path); qInfo() << "copying" << out_path; return QFile::copy(in_path, out_path); } @@ -976,6 +1066,67 @@ class Converter bool update_meta_episode() { + if(m_episode_info.path.isEmpty()) + { + qInfo() << "No episode found"; + return false; + } + + // load ID from asset pack's gameinfo + QSettings gameinfo(m_assets_dir.filePath("gameinfo.ini"), QSettings::IniFormat); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + gameinfo.setIniCodec("UTF-8"); +#endif + + gameinfo.beginGroup("game"); + QString pack_id = gameinfo.value("id", "").toString(); + gameinfo.endGroup(); + + + // save info to _meta.ini + QSettings meta(m_temp_dir.filePath("_meta.ini"), QSettings::IniFormat); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + meta.setIniCodec("UTF-8"); +#endif + + // contents + meta.beginGroup("content"); + + meta.setValue("engine", "TheXTech"); + meta.setValue("platform", platform_name()); + meta.setValue("type", "episode"); + meta.setValue("filename", m_episode_info.path); + + if(!pack_id.isEmpty()) + meta.setValue("pack-id", pack_id); + + meta.endGroup(); + + // properties + meta.beginGroup("properties"); + + meta.setValue("title", m_episode_info.title); + meta.setValue("is-classic", m_episode_info.playstyle_classic); + meta.setValue("char-block", m_episode_info.char_block); + + for(const auto& i : m_episode_info.title_translations) + { + QString key = "title-"; + key += i.first; + meta.setValue(key, i.second); + } + + meta.endGroup(); + + // finish and save + meta.sync(); + + if(meta.status() != QSettings::NoError) + { + qInfo() << "Failed: could not update _meta.ini"; + return false; + } + return true; } @@ -1036,6 +1187,7 @@ class Converter meta.setValue("engine", "TheXTech"); meta.setValue("platform", platform_name()); + meta.setValue("type", "asset-pack"); meta.setValue("pack-id", pack_id); if(!version.isEmpty()) @@ -1224,6 +1376,8 @@ class Converter else m_assets_dir.setPath(m_spec.input_dir); + m_episode_info = EpisodeInfo(); + if(!build_temp_dir()) return false; From 954d77d32176b684c59fc535a5ae67640c15948d Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:45:54 -0500 Subject: [PATCH 030/286] XTConvert: update detection of whether textures should have masks --- Maintainer/XTConvert/CMakeLists.txt | 1 + Maintainer/XTConvert/libxtconvert.cpp | 24 ++- Maintainer/XTConvert/src/has_no_mask.cpp | 234 +++++++++++++++++++++++ Maintainer/XTConvert/src/has_no_mask.h | 12 ++ 4 files changed, 264 insertions(+), 7 deletions(-) create mode 100644 Maintainer/XTConvert/src/has_no_mask.cpp create mode 100644 Maintainer/XTConvert/src/has_no_mask.h diff --git a/Maintainer/XTConvert/CMakeLists.txt b/Maintainer/XTConvert/CMakeLists.txt index ccc601d3a9..1980176de3 100644 --- a/Maintainer/XTConvert/CMakeLists.txt +++ b/Maintainer/XTConvert/CMakeLists.txt @@ -15,6 +15,7 @@ add_library(libxtconvert src/texconv.cpp src/export_tpl.cpp src/export_audio.cpp + src/has_no_mask.cpp ) add_dependencies(libxtconvert archive_static lz4_pack_static libromfs3ds libtex3ds FreeImage_Local PGE_FileFormatsQt libpng_Local SDLMixerX_Local SDL2_Local) diff --git a/Maintainer/XTConvert/libxtconvert.cpp b/Maintainer/XTConvert/libxtconvert.cpp index 6ab81de23f..232ef9211c 100644 --- a/Maintainer/XTConvert/libxtconvert.cpp +++ b/Maintainer/XTConvert/libxtconvert.cpp @@ -30,6 +30,7 @@ #include "texconv.h" #include "export_tpl.h" #include "export_audio.h" +#include "has_no_mask.h" #include "libxtconvert.h" @@ -286,9 +287,20 @@ class Converter QString filename_type, filename_index; bool standard_filename_format = validate_image_filename(filename, filename_type, filename_index); + // decide whether to look for mask + bool may_have_mask = standard_filename_format; + if(filename_type == "background2") + may_have_mask = false; + else if(filename_type == "tile") + may_have_mask = false; + if(filename_type == "block" && check_BlockHasNoMask(filename_index.toUInt())) + may_have_mask = false; + if(filename_type == "background" && check_BackgroundHasNoMask(filename_index.toUInt())) + may_have_mask = false; + FIBITMAP* mask = nullptr; QString mask_path; - if(filename.endsWith(".gif") && filename_type != "background2" && filename_type != "tile") + if(filename.endsWith(".gif") && may_have_mask) { // find the mask!! QString filename_stem = filename.chopped(4); @@ -354,13 +366,9 @@ class Converter QString used_out_path = out_path; // save mask fallback here if we are in the global graphics dir - if(m_cur_dir.make_fallback_masks && output_format != TargetPlatform::Desktop) + if(m_cur_dir.make_fallback_masks && output_format != TargetPlatform::Desktop && may_have_mask) { - FIBITMAP* fallback_mask = nullptr; - - // make sure that the texture is valid and should have transparency - if(standard_filename_format && filename_type != "background2" && filename_type != "tile") - fallback_mask = FreeImage_Clone(image); + FIBITMAP* fallback_mask = FreeImage_Clone(image); if(fallback_mask) { @@ -1357,6 +1365,8 @@ class Converter GraphicsLoad::FreeImage_Sentinel fs; MixerX_Sentinel ms(m_spec); + init_mask_arrays(); + if(!ms.valid) { qInfo() << "Error: MixerX could not be (re)initialized"; diff --git a/Maintainer/XTConvert/src/has_no_mask.cpp b/Maintainer/XTConvert/src/has_no_mask.cpp new file mode 100644 index 0000000000..bf9bb479e3 --- /dev/null +++ b/Maintainer/XTConvert/src/has_no_mask.cpp @@ -0,0 +1,234 @@ +#include + +namespace XTConvert +{ + +static std::array BlockHasNoMask; +static std::array BackgroundHasNoMask; + +void init_mask_arrays() +{ + // init BackgroundHasNoMask + BackgroundHasNoMask[187] = true; + BackgroundHasNoMask[188] = true; + BackgroundHasNoMask[189] = true; + BackgroundHasNoMask[190] = true; + + BackgroundHasNoMask[172] = true; + BackgroundHasNoMask[167] = true; + BackgroundHasNoMask[164] = true; + BackgroundHasNoMask[165] = true; + BackgroundHasNoMask[158] = true; + BackgroundHasNoMask[146] = true; + BackgroundHasNoMask[12] = true; + BackgroundHasNoMask[14] = true; + BackgroundHasNoMask[15] = true; + BackgroundHasNoMask[22] = true; + BackgroundHasNoMask[30] = true; + BackgroundHasNoMask[39] = true; + BackgroundHasNoMask[40] = true; + BackgroundHasNoMask[41] = true; + BackgroundHasNoMask[42] = true; + BackgroundHasNoMask[43] = true; + BackgroundHasNoMask[44] = true; + BackgroundHasNoMask[47] = true; + BackgroundHasNoMask[52] = true; + BackgroundHasNoMask[53] = true; + BackgroundHasNoMask[55] = true; + BackgroundHasNoMask[56] = true; + BackgroundHasNoMask[60] = true; + BackgroundHasNoMask[61] = true; + BackgroundHasNoMask[64] = true; + BackgroundHasNoMask[75] = true; + BackgroundHasNoMask[76] = true; + BackgroundHasNoMask[77] = true; + BackgroundHasNoMask[78] = true; + BackgroundHasNoMask[79] = true; + BackgroundHasNoMask[83] = true; + BackgroundHasNoMask[87] = true; + BackgroundHasNoMask[88] = true; + BackgroundHasNoMask[91] = true; + BackgroundHasNoMask[98] = true; + BackgroundHasNoMask[99] = true; + BackgroundHasNoMask[107] = true; + BackgroundHasNoMask[115] = true; + BackgroundHasNoMask[116] = true; + BackgroundHasNoMask[117] = true; + BackgroundHasNoMask[118] = true; + BackgroundHasNoMask[119] = true; + BackgroundHasNoMask[122] = true; + BackgroundHasNoMask[123] = true; + BackgroundHasNoMask[124] = true; + + BackgroundHasNoMask[139] = true; + BackgroundHasNoMask[140] = true; + BackgroundHasNoMask[141] = true; + BackgroundHasNoMask[144] = true; + BackgroundHasNoMask[145] = true; + + BackgroundHasNoMask[169] = true; + + + // init BlockHasNoMask + BlockHasNoMask[336] = true; + BlockHasNoMask[337] = true; + BlockHasNoMask[338] = true; + BlockHasNoMask[339] = true; + BlockHasNoMask[303] = true; + BlockHasNoMask[304] = true; + BlockHasNoMask[348] = true; + BlockHasNoMask[353] = true; + BlockHasNoMask[354] = true; + BlockHasNoMask[355] = true; + BlockHasNoMask[356] = true; + BlockHasNoMask[3] = true; + BlockHasNoMask[4] = true; + BlockHasNoMask[13] = true; + BlockHasNoMask[15] = true; + BlockHasNoMask[16] = true; + BlockHasNoMask[17] = true; + BlockHasNoMask[19] = true; + BlockHasNoMask[21] = true; + BlockHasNoMask[34] = true; + BlockHasNoMask[36] = true; + BlockHasNoMask[23] = true; + BlockHasNoMask[29] = true; + BlockHasNoMask[40] = true; + BlockHasNoMask[43] = true; + BlockHasNoMask[46] = true; + BlockHasNoMask[47] = true; + BlockHasNoMask[48] = true; + BlockHasNoMask[49] = true; + BlockHasNoMask[50] = true; + BlockHasNoMask[51] = true; + BlockHasNoMask[52] = true; + BlockHasNoMask[53] = true; + BlockHasNoMask[54] = true; + BlockHasNoMask[59] = true; + BlockHasNoMask[60] = true; + BlockHasNoMask[61] = true; + BlockHasNoMask[63] = true; + BlockHasNoMask[65] = true; + BlockHasNoMask[67] = true; + BlockHasNoMask[70] = true; + BlockHasNoMask[71] = true; + BlockHasNoMask[72] = true; + BlockHasNoMask[73] = true; + BlockHasNoMask[74] = true; + BlockHasNoMask[75] = true; + BlockHasNoMask[76] = true; + BlockHasNoMask[81] = true; + BlockHasNoMask[83] = true; + BlockHasNoMask[84] = true; + BlockHasNoMask[85] = true; + BlockHasNoMask[86] = true; + BlockHasNoMask[87] = true; + BlockHasNoMask[91] = true; + BlockHasNoMask[93] = true; + BlockHasNoMask[94] = true; + BlockHasNoMask[95] = true; + BlockHasNoMask[96] = true; + BlockHasNoMask[97] = true; + BlockHasNoMask[98] = true; + BlockHasNoMask[99] = true; + BlockHasNoMask[100] = true; + BlockHasNoMask[101] = true; + BlockHasNoMask[102] = true; + BlockHasNoMask[103] = true; + BlockHasNoMask[111] = true; + BlockHasNoMask[118] = true; + BlockHasNoMask[119] = true; + BlockHasNoMask[120] = true; + BlockHasNoMask[121] = true; + BlockHasNoMask[122] = true; + BlockHasNoMask[123] = true; + BlockHasNoMask[124] = true; + BlockHasNoMask[125] = true; + BlockHasNoMask[126] = true; + BlockHasNoMask[127] = true; + BlockHasNoMask[131] = true; + BlockHasNoMask[134] = true; + BlockHasNoMask[136] = true; + BlockHasNoMask[159] = true; + BlockHasNoMask[160] = true; + BlockHasNoMask[166] = true; + BlockHasNoMask[183] = true; + BlockHasNoMask[184] = true; + BlockHasNoMask[186] = true; + BlockHasNoMask[187] = true; + BlockHasNoMask[188] = true; + BlockHasNoMask[190] = true; + BlockHasNoMask[198] = true; + BlockHasNoMask[199] = true; + BlockHasNoMask[200] = true; + BlockHasNoMask[201] = true; + BlockHasNoMask[202] = true; + BlockHasNoMask[203] = true; + BlockHasNoMask[204] = true; + BlockHasNoMask[205] = true; + BlockHasNoMask[206] = true; + BlockHasNoMask[216] = true; + BlockHasNoMask[217] = true; + BlockHasNoMask[218] = true; + BlockHasNoMask[223] = true; + BlockHasNoMask[226] = true; + BlockHasNoMask[227] = true; + BlockHasNoMask[228] = true; + BlockHasNoMask[229] = true; + BlockHasNoMask[230] = true; + BlockHasNoMask[231] = true; + BlockHasNoMask[232] = true; + BlockHasNoMask[233] = true; + BlockHasNoMask[234] = true; + BlockHasNoMask[235] = true; + BlockHasNoMask[236] = true; + BlockHasNoMask[237] = true; + BlockHasNoMask[238] = true; + BlockHasNoMask[239] = true; + BlockHasNoMask[250] = true; + BlockHasNoMask[251] = true; + BlockHasNoMask[252] = true; + BlockHasNoMask[253] = true; + BlockHasNoMask[254] = true; + BlockHasNoMask[255] = true; + BlockHasNoMask[256] = true; + BlockHasNoMask[257] = true; + BlockHasNoMask[258] = true; + // BlockHasNoMask[261] = true; // invalid, Block 261 is sizable and this flag is never read for sizable blocks in SMBX 1.3 + BlockHasNoMask[262] = true; + BlockHasNoMask[264] = true; + BlockHasNoMask[263] = true; + BlockHasNoMask[273] = true; + BlockHasNoMask[272] = true; + BlockHasNoMask[276] = true; + BlockHasNoMask[281] = true; + BlockHasNoMask[282] = true; + BlockHasNoMask[283] = true; + BlockHasNoMask[291] = true; + BlockHasNoMask[292] = true; + BlockHasNoMask[320] = true; + BlockHasNoMask[322] = true; + BlockHasNoMask[323] = true; + BlockHasNoMask[330] = true; + BlockHasNoMask[331] = true; + BlockHasNoMask[369] = true; + BlockHasNoMask[370] = true; +} + +bool check_BlockHasNoMask(unsigned int index) +{ + if(index < BlockHasNoMask.size()) + return BlockHasNoMask[index]; + + return false; +} + +bool check_BackgroundHasNoMask(unsigned int index) +{ + if(index < BackgroundHasNoMask.size()) + return BackgroundHasNoMask[index]; + + return false; +} + +} // namespace XTConvert diff --git a/Maintainer/XTConvert/src/has_no_mask.h b/Maintainer/XTConvert/src/has_no_mask.h new file mode 100644 index 0000000000..ea1510ad94 --- /dev/null +++ b/Maintainer/XTConvert/src/has_no_mask.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace XTConvert +{ + +void init_mask_arrays(); +bool check_BlockHasNoMask(unsigned int index); +bool check_BackgroundHasNoMask(unsigned int index); + +} // namespace XTConvert From 84c384ce0b734d7851a166e5884d1b1bbd0e09d9 Mon Sep 17 00:00:00 2001 From: ds-sloth <72112344+ds-sloth@users.noreply.github.com> Date: Sun, 1 Dec 2024 16:22:37 -0500 Subject: [PATCH 031/286] XTConvert: scan for episode title translations --- Maintainer/XTConvert/json.hpp | 22684 ++++++++++++++++++++++++ Maintainer/XTConvert/libxtconvert.cpp | 35 +- 2 files changed, 22718 insertions(+), 1 deletion(-) create mode 100644 Maintainer/XTConvert/json.hpp diff --git a/Maintainer/XTConvert/json.hpp b/Maintainer/XTConvert/json.hpp new file mode 100644 index 0000000000..2a32a82963 --- /dev/null +++ b/Maintainer/XTConvert/json.hpp @@ -0,0 +1,22684 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 3.7.0 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2019 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 7 +#define NLOHMANN_JSON_VERSION_PATCH 0 + +#include // all_of, find, for_each +#include // assert +#include // and, not, or +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#include // istream, ostream +#include // random_access_iterator_tag +#include // unique_ptr +#include // accumulate +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +// #include + + +#include + +// #include + + +#include // transform +#include // array +#include // and, not +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +// #include + + +#include // exception +#include // runtime_error +#include // to_string + +// #include + + +#include // size_t + +namespace nlohmann +{ +namespace detail +{ +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // pair +// #include +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to + * the public domain worldwide. This software is distributed without + * any warranty. + * + * For details, see . + * SPDX-License-Identifier: CC0-1.0 + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 9) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 9 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(_MSC_VER) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(VER / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(__COMPCERT__) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif JSON_HEDLEY_TI_VERSION_CHECK(8,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) [[deprecated("Since " #since)]] + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) [[deprecated("Since " #since "; use " #replacement)]] +#elif \ + JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(8,3,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) + #define JSON_HEDLEY_DEPRECATED(since) _declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(__cplusplus) && (__cplusplus >= 201703L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT [[nodiscard]] +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN [[noreturn]] +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,0,0) || \ + (JSON_HEDLEY_TI_VERSION_CHECK(17,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) + #define JSON_HEDLEY_UNREACHABLE() __assume(0) +#elif JSON_HEDLEY_TI_VERSION_CHECK(6,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_UNREACHABLE() std::_nassert(0) + #else + #define JSON_HEDLEY_UNREACHABLE() _nassert(0) + #endif + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return value +#elif defined(EXIT_FAILURE) + #define JSON_HEDLEY_UNREACHABLE() abort() +#else + #define JSON_HEDLEY_UNREACHABLE() + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return value +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() +#endif + +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif JSON_HEDLEY_TI_VERSION_CHECK(6,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#elif \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && !defined(JSON_HEDLEY_ARM_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) + #define JSON_HEDLEY_ASSUME(expr) ((void) ((expr) ? 1 : (__builtin_unreachable(), 1))) +#else + #define JSON_HEDLEY_ASSUME(expr) ((void) (expr)) +#endif + + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if \ + JSON_HEDLEY_HAS_WARNING("-Wvariadic-macros") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR constexpr + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable(!!(expr)) +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability(expr, value, probability) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1, probability) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0, probability) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#if !defined(JSON_HEDLEY_BUILTIN_UNPREDICTABLE) + #define JSON_HEDLEY_BUILTIN_UNPREDICTABLE(expr) __builtin_expect_with_probability(!!(expr), 1, 0.5) +#endif +#elif \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect(!!(expr), (expected)) : (((void) (expected)), !!(expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + JSON_HEDLEY_CONSTEXPR double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + JSON_HEDLEY_CONSTEXPR double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (((void) (expected)), !!(expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(14, 0, 0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_TI_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else + #define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) + #define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) + #define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif JSON_HEDLEY_TI_VERSION_CHECK(7,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else + #define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) + #define JSON_HEDLEY_PRIVATE + #define JSON_HEDLEY_PUBLIC __declspec(dllexport) + #define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else + #if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_EABI__) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) + #define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) + #define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) + #else + #define JSON_HEDLEY_PRIVATE + #define JSON_HEDLEY_PUBLIC + #endif + #define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + !defined(JSON_HEDLEY_PGI_VERSION) + #if \ + (__cplusplus >= 201703L) || \ + ((__cplusplus >= 201103L) && JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough)) + #define JSON_HEDLEY_FALL_THROUGH [[fallthrough]] + #elif (__cplusplus >= 201103L) && JSON_HEDLEY_HAS_CPP_ATTRIBUTE(clang::fallthrough) + #define JSON_HEDLEY_FALL_THROUGH [[clang::fallthrough]] + #elif (__cplusplus >= 201103L) && JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) + #define JSON_HEDLEY_FALL_THROUGH [[gnu::fallthrough]] + #endif +#endif +#if !defined(JSON_HEDLEY_FALL_THROUGH) + #if JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(fallthrough,7,0,0) && !defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) + #elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough + #else + #define JSON_HEDLEY_FALL_THROUGH + #endif +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* Note the double-underscore. For internal use only; no API + * guarantees! */ +#if defined(JSON_HEDLEY__IS_CONSTEXPR) + #undef JSON_HEDLEY__IS_CONSTEXPR +#endif + +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY__IS_CONSTEXPR(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY__IS_CONSTEXPR(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(JSON_HEDLEY_SUNPRO_VERSION) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY__IS_CONSTEXPR(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY__IS_CONSTEXPR(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY__IS_CONSTEXPR(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY__IS_CONSTEXPR) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY__IS_CONSTEXPR(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY__IS_CONSTEXPR(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + JSON_HEDLEY_HAS_FEATURE(c_static_assert) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201703L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + (defined(__cplusplus) && JSON_HEDLEY_TI_VERSION_CHECK(8,3,0)) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) static_assert(expr, message) +#elif defined(__cplusplus) && (__cplusplus >= 201103L) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) static_assert(expr) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (*((T*) &(expr))) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_CPP_CAST(T, expr) static_cast(expr) +#else + #define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE_MSG(expr, msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((__diagnose_if__(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE_MSG(expr, msg) __attribute__((__diagnose_if__(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE_MSG(expr, msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#define JSON_HEDLEY_REQUIRE(expr) JSON_HEDLEY_REQUIRE_MSG(expr, #expr) + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow to disable exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + + +namespace nlohmann +{ +namespace detail +{ +//////////////// +// exceptions // +//////////////// + +/*! +@brief general exception of the @ref basic_json class + +This class is an extension of `std::exception` objects with a member @a id for +exception ids. It is used as the base class for all exceptions thrown by the +@ref basic_json class. This class can hence be used as "wildcard" to catch +exceptions. + +Subclasses: +- @ref parse_error for exceptions indicating a parse error +- @ref invalid_iterator for exceptions indicating errors with iterators +- @ref type_error for exceptions indicating executing a member function with + a wrong type +- @ref out_of_range for exceptions indicating access out of the defined range +- @ref other_error for exceptions indicating other library errors + +@internal +@note To have nothrow-copy-constructible exceptions, we internally use + `std::runtime_error` which can cope with arbitrary-length error messages. + Intermediate strings are built with static functions and then passed to + the actual constructor. +@endinternal + +@liveexample{The following code shows how arbitrary library exceptions can be +caught.,exception} + +@since version 3.0.0 +*/ +class exception : public std::exception +{ + public: + /// returns the explanatory string + JSON_HEDLEY_RETURNS_NON_NULL + const char* what() const noexcept override + { + return m.what(); + } + + /// the id of the exception + const int id; + + protected: + JSON_HEDLEY_NON_NULL(3) + exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} + + static std::string name(const std::string& ename, int id_) + { + return "[json.exception." + ename + "." + std::to_string(id_) + "] "; + } + + private: + /// an exception object as storage for error messages + std::runtime_error m; +}; + +/*! +@brief exception indicating a parse error + +This exception is thrown by the library when a parse error occurs. Parse errors +can occur during the deserialization of JSON text, CBOR, MessagePack, as well +as when using JSON Patch. + +Member @a byte holds the byte index of the last read character in the input +file. + +Exceptions have ids 1xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. +json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. +json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. +json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. +json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. +json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. +json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. +json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. +json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. +json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. +json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. +json.exception.parse_error.114 | parse error: Unsupported BSON record type 0x0F | The parsing of the corresponding BSON record type is not implemented (yet). + +@note For an input with n bytes, 1 is the index of the first character and n+1 + is the index of the terminating null byte or the end of file. This also + holds true when reading a byte vector (CBOR or MessagePack). + +@liveexample{The following code shows how a `parse_error` exception can be +caught.,parse_error} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref out_of_range for exceptions indicating access out of the defined range +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class parse_error : public exception +{ + public: + /*! + @brief create a parse error exception + @param[in] id_ the id of the exception + @param[in] pos the position where the error occurred (or with + chars_read_total=0 if the position cannot be + determined) + @param[in] what_arg the explanatory string + @return parse_error object + */ + static parse_error create(int id_, const position_t& pos, const std::string& what_arg) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + position_string(pos) + ": " + what_arg; + return parse_error(id_, pos.chars_read_total, w.c_str()); + } + + static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") + + ": " + what_arg; + return parse_error(id_, byte_, w.c_str()); + } + + /*! + @brief byte index of the parse error + + The byte index of the last read character in the input file. + + @note For an input with n bytes, 1 is the index of the first character and + n+1 is the index of the terminating null byte or the end of file. + This also holds true when reading a byte vector (CBOR or MessagePack). + */ + const std::size_t byte; + + private: + parse_error(int id_, std::size_t byte_, const char* what_arg) + : exception(id_, what_arg), byte(byte_) {} + + static std::string position_string(const position_t& pos) + { + return " at line " + std::to_string(pos.lines_read + 1) + + ", column " + std::to_string(pos.chars_read_current_line); + } +}; + +/*! +@brief exception indicating errors with iterators + +This exception is thrown if iterators passed to a library function do not match +the expected semantics. + +Exceptions have ids 2xx. + +name / id | example message | description +----------------------------------- | --------------- | ------------------------- +json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. +json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. +json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. +json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. +json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. +json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. +json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. +json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. +json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. +json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). + +@liveexample{The following code shows how an `invalid_iterator` exception can be +caught.,invalid_iterator} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref out_of_range for exceptions indicating access out of the defined range +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class invalid_iterator : public exception +{ + public: + static invalid_iterator create(int id_, const std::string& what_arg) + { + std::string w = exception::name("invalid_iterator", id_) + what_arg; + return invalid_iterator(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + invalid_iterator(int id_, const char* what_arg) + : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating executing a member function with a wrong type + +This exception is thrown in case of a type error; that is, a library function is +executed on a JSON value whose type does not match the expected semantics. + +Exceptions have ids 3xx. + +name / id | example message | description +----------------------------- | --------------- | ------------------------- +json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. +json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. +json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t &. +json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. +json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. +json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. +json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. +json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. +json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. +json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. +json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. +json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. +json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. +json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. +json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | +json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) | + +@liveexample{The following code shows how a `type_error` exception can be +caught.,type_error} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref out_of_range for exceptions indicating access out of the defined range +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class type_error : public exception +{ + public: + static type_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("type_error", id_) + what_arg; + return type_error(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating access out of the defined range + +This exception is thrown in case a library function is called on an input +parameter that exceeds the expected range, for instance in case of array +indices or nonexisting object keys. + +Exceptions have ids 4xx. + +name / id | example message | description +------------------------------- | --------------- | ------------------------- +json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. +json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. +json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. +json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. +json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. +json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. +json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. | +json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | +json.exception.out_of_range.409 | BSON key cannot contain code point U+0000 (at byte 2) | Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string | + +@liveexample{The following code shows how an `out_of_range` exception can be +caught.,out_of_range} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class out_of_range : public exception +{ + public: + static out_of_range create(int id_, const std::string& what_arg) + { + std::string w = exception::name("out_of_range", id_) + what_arg; + return out_of_range(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating other library errors + +This exception is thrown in case of errors that cannot be classified with the +other exception types. + +Exceptions have ids 5xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref out_of_range for exceptions indicating access out of the defined range + +@liveexample{The following code shows how an `other_error` exception can be +caught.,other_error} + +@since version 3.0.0 +*/ +class other_error : public exception +{ + public: + static other_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("other_error", id_) + what_arg; + return other_error(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + + +#include // not +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type + +namespace nlohmann +{ +namespace detail +{ +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // not +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval + +// #include + + +#include // random_access_iterator_tag + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; +} // namespace detail +} // namespace nlohmann + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + + +#include + +// #include + + +// http://en.cppreference.com/w/cpp/experimental/is_detected +namespace nlohmann +{ +namespace detail +{ +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template