diff --git a/.appveyor.yml b/.appveyor.yml index 43b3bbe..7df2a60 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,22 +4,22 @@ environment: global: PLATFORMTOOLSET: "v140" matrix: - - BUILD_TYPE: Debug - COMPILER: MinGW - COMPILER_FAMILY: MinGW - GENERATOR: "MinGW Makefiles" - PLATFORM: Win32 - QT_BUILD: OFF - QT_PATH: C:\Qt\5.15.2\mingw81_32 - APPVEYOR_BUILD_WORKER_IMAGE: "Visual Studio 2019" - - BUILD_TYPE: Release - COMPILER: MinGW - COMPILER_FAMILY: MinGW - GENERATOR: "MinGW Makefiles" - PLATFORM: Win32 - QT_BUILD: OFF - QT_PATH: C:\Qt\5.15.2\mingw81_32 - APPVEYOR_BUILD_WORKER_IMAGE: "Visual Studio 2019" +# - BUILD_TYPE: Debug +# COMPILER: MinGW +# COMPILER_FAMILY: MinGW +# GENERATOR: "MinGW Makefiles" +# PLATFORM: Win32 +# QT_BUILD: OFF +# QT_PATH: C:\Qt\5.15.2\mingw81_32 +# APPVEYOR_BUILD_WORKER_IMAGE: "Visual Studio 2019" +# - BUILD_TYPE: Release +# COMPILER: MinGW +# COMPILER_FAMILY: MinGW +# GENERATOR: "MinGW Makefiles" +# PLATFORM: Win32 +# QT_BUILD: OFF +# QT_PATH: C:\Qt\5.15.2\mingw81_32 +# APPVEYOR_BUILD_WORKER_IMAGE: "Visual Studio 2019" - BUILD_TYPE: Debug COMPILER: MinGW-w64 COMPILER_FAMILY: MinGW diff --git a/CMakeLists.txt b/CMakeLists.txt index c98bd76..7c1f12e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ if(NOT CMAKE_DEBUG_POSTFIX) endif() option(PGEFL_ENABLE_RWOPS "Enable SDL_RWops interfaces (if this option is enabled, then PGE-FL has a build-time dependency on SDL2's headers)" OFF) +option(PGEFL_DISABLE_SMBX38A "Disable loading of SMBX-38A content" OFF) if(NOT DEFINED PGE_FORCE_QT5 AND NOT DEFINED PGEFL_PREFER_QT5) option(PGE_FORCE_QT5 "Try to find Qt5 instead of Qt6 if available" ON) @@ -47,7 +48,7 @@ macro(pgeflFindQt) endif() endmacro() -if(NOT DEFINED PGEFL_QT_SUPPORT AND NOT VITA AND NOT PS2) +if(NOT DEFINED PGEFL_QT_SUPPORT AND NOT VITA AND NOT PSP AND NOT PS2) pgeflFindQt() endif() @@ -55,7 +56,7 @@ if(PGEFL_QT_SUPPORT) pgeflFindQt() endif() -if(VITA OR PS2 OR PGEFL_USE_QT5 OR PGEFL_USE_QT6) +if(VITA OR PSP OR PS2 OR PGEFL_USE_QT5 OR PGEFL_USE_QT6) set(OPT_DEF_PGEFL_QT_SUPPORT OFF) else() set(OPT_DEF_PGEFL_QT_SUPPORT ON) @@ -64,7 +65,7 @@ endif() if(NOT PGEFL_QT_SUPPORT) option(PGEFL_QT_SUPPORT "Build also Qt variant" ${OPT_DEF_PGEFL_QT_SUPPORT}) endif() -if(PGEFL_QT_SUPPORT AND NOT VITA AND NOT NINTENDO_3DS AND NOT NINTENDO_WII AND NOT NINTENDO_WIIU AND NOT NINTENDO_SWITCH) +if(PGEFL_QT_SUPPORT AND NOT VITA AND NOT PSP AND NOT NINTENDO_3DS AND NOT NINTENDO_WII AND NOT NINTENDO_WIIU AND NOT NINTENDO_SWITCH) message("== PGE-FL Qt Edition WILL be built!") if(POLICY CMP0071) # Automoc cmake_policy(SET CMP0071 NEW) @@ -103,6 +104,25 @@ if(PGEFL_ENABLE_RWOPS) target_compile_definitions(pgefl PUBLIC -DPGEFL_ENABLE_RWOPS) endif() +if(PGEFL_DISABLE_SMBX38A) + target_compile_definitions(pgefl PRIVATE -DPGEFL_DISABLE_SMBX38A) +endif() + +if(NOT EMSCRIPTEN) + try_compile(HAS_STD_STOX_WRAPPED + ${CMAKE_CURRENT_BINARY_DIR}/cmake-checks + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/checks/stdstrtoi/has_std_stol.cpp + CMAKE_FLAGS -DCMAKE_CXX_STANDARD=11 + ) +endif() + +if(NOT EMSCRIPTEN AND NOT HAS_STD_STOX_WRAPPED) + target_compile_definitions(pgefl PRIVATE -DPGEFL_MISSING_STD_STOX_FUNCS) +endif() + +pgefl_target_disable_cxx_warning_flag(pgefl "c++20-extensions" CPP20EXTENSIONS) +pgefl_target_disable_cxx_warning_flag(pgefl "c++20-attribute-extensions" CPP20ATTRIBEXTENSIONS) + if(PGEFL_QT_SUPPORT) add_library(pgefl_qt STATIC ${PGE_FILE_LIBRARY_SRCS} @@ -118,6 +138,10 @@ if(PGEFL_QT_SUPPORT) if(PGEFL_ENABLE_RWOPS) target_compile_definitions(pgefl_qt PUBLIC -DPGEFL_ENABLE_RWOPS) endif() + + if(PGEFL_DISABLE_SMBX38A) + target_compile_definitions(pgefl PRIVATE -DPGEFL_DISABLE_SMBX38A) + endif() endif() # Don't install libraries when PGE-FL was built as a part of Moondust master project diff --git a/CSVReader.h b/CSVReader.h index 057f8ee..ea4d93e 100644 --- a/CSVReader.h +++ b/CSVReader.h @@ -27,11 +27,8 @@ #ifndef CSVReader_H #define CSVReader_H -#include -#include #include #include -#include #include #include #include diff --git a/build_props.cmake b/build_props.cmake index 901a764..ecb2fbd 100644 --- a/build_props.cmake +++ b/build_props.cmake @@ -114,6 +114,7 @@ endif() if(LIBRARY_PROJECT AND NOT WIN32 AND NOT VITA + AND NOT PSP AND NOT PS2 AND NOT NINTENDO_DS AND NOT NINTENDO_3DS @@ -156,3 +157,10 @@ macro(pgefl_disable_cxx_warning_flag WARNINGFLAG WARNING_VAR) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-${WARNINGFLAG}") endif() endmacro() + +macro(pgefl_target_disable_cxx_warning_flag TARGETNAME WARNINGFLAG WARNING_VAR) + check_cxx_compiler_flag("-W${WARNINGFLAG}" HAVE_CXX_W_${WARNING_VAR}) + if(HAVE_CXX_W_${WARNING_VAR}) + target_compile_options(${TARGETNAME} PRIVATE "$<$:-Wno-${WARNINGFLAG}>") + endif() +endmacro() diff --git a/cmake/checks/stdstrtoi/has_std_stol.cpp b/cmake/checks/stdstrtoi/has_std_stol.cpp new file mode 100644 index 0000000..360c1c7 --- /dev/null +++ b/cmake/checks/stdstrtoi/has_std_stol.cpp @@ -0,0 +1,11 @@ +#include + +int main() +{ + long x1 = std::stol("42"); + unsigned long long x2 = std::stoull("42"); + float x3 = std::stof("42.0"); + double x4 = std::stod("42.0"); + + return 0; +} diff --git a/file_formats.h b/file_formats.h index 446808f..3b30e0b 100644 --- a/file_formats.h +++ b/file_formats.h @@ -48,6 +48,8 @@ # define PGEFL_DEPRECATED(func) func #endif +#define PGEFL_CALLBACK_API + /*! * \brief PGE File library class of static functions. * Library is buildable in both Qt and STL applications @@ -63,6 +65,8 @@ class FileFormats PGE_FILES_INHERED static const unsigned int c_latest_version_smbx38a = 69; static const unsigned int c_version_default = 0xFFFFFFFF; + static bool g_use_legacy_pgex_parser; + /******************************non-SMBX64 Meda-data file***********************************/ /*! @@ -161,6 +165,13 @@ class FileFormats PGE_FILES_INHERED * @return true if file successfully opened and parsed, false if error occouped */ static bool OpenLevelFileT(PGE_FileFormats_misc::TextInput &file, LevelData &FileData); + /** + * @brief Parses a level file data with auto-detection of a file type (SMBX1...64 LVL or PGE-LVLX) + * @param [__in] file Input file descriptor + * @param [__out] callbacks Callbacks to receive level data + * @return true if file successfully opened and parsed, false if error occouped + */ + static bool OpenLevelFileT(PGE_FileFormats_misc::TextInput &file, const LevelLoadCallbacks &callbacks); /*! * \brief Parses a level file header only with auto-detection of a file type (SMBX1...64 LVL or PGE-LVLX) * \param [__in] filePath Full path to file which must be opened @@ -260,6 +271,13 @@ class FileFormats PGE_FILES_INHERED * \return true if file successfully parsed, false if error occouped */ static bool ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelData /*output*/ &FileData); + /*! + * \brief Parses SMBX1...64 level file data + * \param [__in] in Input file descriptor + * \param [__in] callbacks Callbacks for level load + * \return true if file successfully parsed, false if error occouped + */ + static bool ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, const LevelLoadCallbacks &callbacks); /*! * \brief Generates SMBX1...64 Level file data and saves into file * \param [__in] filePath Target file path @@ -331,6 +349,13 @@ class FileFormats PGE_FILES_INHERED * \return true if file successfully parsed, false if error occouped */ static bool ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelData /*output*/ &FileData); + /*! + * \brief Parses SMBX-38A level file data from raw data string + * \param [__in] in File input descriptor + * \param [__out] callbacks LevelLoadCallbacks Level data structure + * \return true if file successfully parsed, false if error occouped + */ + static bool ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, const LevelLoadCallbacks &callbacks); #if 0 // Removed /*! * \brief Parses SMBX-38A level file data from raw data string (Old algorithm) @@ -390,6 +415,13 @@ class FileFormats PGE_FILES_INHERED * \return true if file successfully parsed, false if error occouped */ static bool ReadExtendedLvlFileHeaderT(PGE_FileFormats_misc::TextInput &inf, LevelData &FileData); + /*! + * \brief Parses PGE-X Level file header from the file + * \param [__in] inf Input file descriptor + * \param [__in] callbacks Level load callbacks (will only attempt to call the load_head and on_error callbacks) + * \return true if file successfully parsed, false if error occouped + */ + static bool ReadExtendedLvlFileHeaderT(PGE_FileFormats_misc::TextInput &inf, const LevelLoadCallbacks &callbacks); /*! * \brief Parses PGE-X level file data from file * \param [__in] filePath Full path to the file @@ -412,6 +444,13 @@ class FileFormats PGE_FILES_INHERED * \return true if file successfully parsed, false if error occouped */ static bool ReadExtendedLvlFile(PGE_FileFormats_misc::TextInput &in, LevelData /*output*/ &FileData); + /*! + * \brief Parses PGE-X level file data from file input descriptor + * \param [__in] in File Input descriptor + * \param [__in] callbacks Level load callbacks + * \return true if file successfully parsed, false if error occouped + */ + static bool ReadExtendedLvlFile(PGE_FileFormats_misc::TextInput &in, const LevelLoadCallbacks &callbacks); /*! * \brief Generates PGE-X Level file * \param [__in] filePath Target file path @@ -604,6 +643,13 @@ class FileFormats PGE_FILES_INHERED * @return true if file successfully opened and parsed, false if error occouped */ static bool OpenWorldFileT(PGE_FileFormats_misc::TextInput &file, WorldData &data); + /** + * @brief Parses a level world map data with auto-detection of a file type (SMBX1...64 LVL or PGE-LVLX) + * @param [__in] file Input file descriptor + * \param [__out] callbacks WorldLoadCallbacks Callbacks to store loaded world data + * @return true if file successfully opened and parsed, false if error occouped + */ + static bool OpenWorldFileT(PGE_FileFormats_misc::TextInput &file, const WorldLoadCallbacks &data); /*! * \brief Parses a world map file header only with auto-detection of a file type (SMBX1...64 LVL or PGE-WLDX) * \param [__in] filePath Full path to file which must be opened @@ -691,6 +737,13 @@ class FileFormats PGE_FILES_INHERED * \return true if file successfully parsed, false if error occouped */ static bool ReadSMBX64WldFile(PGE_FileFormats_misc::TextInput &in, WorldData /*output*/ &FileData); + /*! + * \brief Parses SMBX1...64 World map file from raw data from file input descriptor + * \param [__in] in File Input descriptor + * \param [__out] callbacks WorldLoadCallbacks Callbacks to store loaded world data + * \return true if file successfully parsed, false if error occouped + */ + static bool ReadSMBX64WldFile(PGE_FileFormats_misc::TextInput &in, const WorldLoadCallbacks &callbacks); /*! * \brief Saves level data into file of SMBX1...64 World map format * \param [__in] filePath Target file path @@ -761,6 +814,13 @@ class FileFormats PGE_FILES_INHERED * \return true if file successfully parsed, false if error occouped */ static bool ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput &in, WorldData /*output*/ &FileData); + /*! + * \brief Parses SMBX-38A world map file data from raw data string + * \param [__in] in File input descriptor + * \param [__out] callbacks WorldLoadCallbacks Callbacks to store loaded world data + * \return true if file successfully parsed, false if error occouped + */ + static bool ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput &in, const WorldLoadCallbacks& callbacks); /*! * \brief Generates SMBX-38A Level file data and saves into file * \param [__in] filePath Target file path @@ -831,6 +891,13 @@ class FileFormats PGE_FILES_INHERED * \return true if file successfully parsed, false if error occouped */ static bool ReadExtendedWldFile(PGE_FileFormats_misc::TextInput &in, WorldData /*output*/ &FileData); + /*! + * \brief Parses PGE-X World map file from file input descriptor + * \param [__in] in File Input descriptor + * \param [__out] callbacks WorldLoadCallbacks Callbacks to store loaded world data + * \return true if file successfully parsed, false if error occouped + */ + static bool ReadExtendedWldFile(PGE_FileFormats_misc::TextInput &in, const WorldLoadCallbacks& callbacks); /*! * \brief Saves world map data into file of PGE-X World map format * \param [__in] filePath Target file path diff --git a/lvl_filedata.h b/lvl_filedata.h index cad076e..9d95cf3 100644 --- a/lvl_filedata.h +++ b/lvl_filedata.h @@ -34,6 +34,7 @@ #define LVL_FILEDATA_H #include "pge_file_lib_globs.h" +#include "pge_base_callbacks.h" #include "meta_filedata.h" #include "pge_ff_units.h" @@ -119,9 +120,9 @@ struct PlayerPoint //! Y-position of player spawn point long y = 0; //! Height of player spawn point (used to calculate position of bottom to place playable character correctly) - long h = 32; + long h = 0; //! Wodth of player spawn point (used to calculate position of bottom to place playable character correctly) - long w = 24; + long w = 0; //! Defined ID of player unsigned int id = 0; //! Initial direction of playable character (-1 is left, 1 is right, 0 is right by default) @@ -660,6 +661,9 @@ struct LevelEvent_Sets //! Ariphmetical expression calculates height of section PGESTRING expression_pos_h; + //! internal variable describing whether this struct was generated by a legacy field + bool _pgefl_mdx_priv_legacy_field = false; + //! Enable autoscroll for this section bool autoscrol = false; enum AutoScrollStyle @@ -992,6 +996,41 @@ struct LevelItemSetup38A PGELIST data; }; +/*! + * \brief Level header data structure. Contains all available settings on the level. + */ +struct LevelHead +{ + PGESTRING LevelName; + int stars = 0; + PGESTRING open_level_on_fail; + unsigned open_level_on_fail_warpID = 0; + PGELIST player_names_overrides; + PGESTRING custom_params; + PGESTRING configPackId; + unsigned int engineFeatureLevel = 0; + PGELIST music_files; + + struct MusicOverrider + { + enum Type { + LEVEL = 0, + SPECIAL = 1 + }; + Type type = Type::LEVEL; + uint32_t id = 0; + PGESTRING fileName; + }; + + //! Override default musics + PGELIST music_overrides; + + //! Recently used (open or save) file format + int RecentFormat = 0; + //! Recently used format version (for SMBX1...64 files only) + unsigned int RecentFormatVersion = 0; +}; + /*! * \brief Level data structure. Contains all available settings and element lists on the level. */ @@ -1032,16 +1071,7 @@ struct LevelData //! JSON-like string with a custom properties (without master brackets, like "param":"value,["subparam":value]) PGESTRING custom_params; - struct MusicOverrider - { - enum Type { - LEVEL = 0, - SPECIAL = 1 - }; - Type type = Type::LEVEL; - uint32_t id = 0; - PGESTRING fileName; - }; + using MusicOverrider = LevelHead::MusicOverrider; //! Override default musics PGELIST music_overrides; @@ -1135,6 +1165,49 @@ struct LevelData bool layerIsExist(const PGESTRING &title); }; +struct LevelLoadCallbacks : PGE_FileFormats_misc::LoadCallbacks +{ + callback load_head = nullptr; + callback load_bookmark = nullptr; + callback load_crash_data = nullptr; + callback load_section = nullptr; + callback load_startpoint = nullptr; + callback load_block = nullptr; + callback load_bgo = nullptr; + callback load_npc = nullptr; + callback load_phys = nullptr; + callback load_warp = nullptr; + callback load_layer = nullptr; + callback load_event = nullptr; + callback load_var = nullptr; + callback load_arr = nullptr; + callback load_script = nullptr; + callback load_levelitem38a = nullptr; + callback load_music_override = nullptr; + callback load_junk_line = nullptr; +}; + +struct LevelSaveCallbacks : PGE_FileFormats_misc::SaveCallbacks +{ + callback save_head = nullptr; + callback save_bookmark = nullptr; + callback save_crash_data = nullptr; + callback save_section = nullptr; + callback save_startpoint = nullptr; + callback save_block = nullptr; + callback save_bgo = nullptr; + callback save_npc = nullptr; + callback save_phys = nullptr; + callback save_warp = nullptr; + callback save_layer = nullptr; + callback save_event = nullptr; + callback save_var = nullptr; + callback save_arr = nullptr; + callback save_script = nullptr; + callback save_levelitem38a = nullptr; + callback save_music_override = nullptr; + callback save_junk_line = nullptr; +}; #endif // LVL_FILEDATA_H diff --git a/meta_filedata.h b/meta_filedata.h index 3f4f054..0e0ec3e 100644 --- a/meta_filedata.h +++ b/meta_filedata.h @@ -32,17 +32,34 @@ #ifndef META_FILEDATA_H #define META_FILEDATA_H +#include "pge_base_callbacks.h" #include "pge_file_lib_globs.h" #include +#include + +/** + * @brief Common data structure for errors + */ +struct FileFormatsError +{ + //! Error messsage + PGESTRING ERROR_info; + //! Line data where error was occured + PGESTRING ERROR_linedata; + //! Number of line where error was occured + long ERROR_linenum = -1; + + //! Fill error info from an exception + void add_exc_info(const std::exception& e, long linenum, PGESTRING&& line); +}; /** * @brief Common data structure meta-data */ -struct FileFormatMeta +struct FileFormatMeta : public FileFormatsError { FileFormatMeta(): ReadFileValid(true), - ERROR_linenum(-1), RecentFormat(0), RecentFormatVersion(0), modified(true), @@ -51,12 +68,6 @@ struct FileFormatMeta {} //! Is file parsed correctly, false if some error is occouped bool ReadFileValid; - //! Error messsage - PGESTRING ERROR_info; - //! Line data where error was occouped - PGESTRING ERROR_linedata; - //! Number of line where error was occouped - long ERROR_linenum; //! Recently used (open or save) file format int RecentFormat; //! Recently used format version (for SMBX1...64 files only) @@ -167,4 +178,14 @@ struct MetaData FileFormatMeta meta; }; +struct MetaLoadCallbacks : PGE_FileFormats_misc::LoadCallbacks +{ + callback load_bookmark = nullptr; +}; + +struct MetaSaveCallbacks : PGE_FileFormats_misc::SaveCallbacks +{ + callback save_bookmark = nullptr; +}; + #endif // META_FILEDATA_H diff --git a/pge_base_callbacks.h b/pge_base_callbacks.h new file mode 100644 index 0000000..74f202e --- /dev/null +++ b/pge_base_callbacks.h @@ -0,0 +1,89 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! + * \file pge_base_callbacks.h + * \brief Contains base structures used for PGE-FL's callbacks + */ + +#pragma once +#ifndef PGE_BASE_CALLBACKS_H +#define PGE_BASE_CALLBACKS_H + +#include +#include + +#ifdef PGE_FILES_QT +typedef int pge_size_t; +#else +typedef size_t pge_size_t; +#endif + +struct FileFormatsError; + +namespace PGE_FileFormats_misc +{ + +// error that callbacks may raise when unpacking object +class callback_error : public std::exception +{ + const char* const m_message = nullptr; +public: + callback_error(const char* message) noexcept : m_message(message) {} + virtual const char* what() const noexcept + { + return m_message; + } +}; + +// way that callbacks may fully terminate parse process without any error +class callback_interrupt : public std::exception {}; + + +struct LoadCallbacks +{ + template using callback = bool (*)(void* userdata, obj_t& obj); + using err_callback = void (*)(void* userdata, FileFormatsError& err); + + void* userdata = nullptr; + err_callback on_error = nullptr; +}; + +struct SaveCallbacks +{ + template using callback = bool (*)(const void* userdata, obj_t& obj, pge_size_t index); + using err_callback = void (*)(void* err_userdata, FileFormatsError& err); + + const void* userdata = nullptr; + void* err_userdata = nullptr; + err_callback on_error = nullptr; +}; + +} + +#include "meta_filedata.h" // def'n of FileFormatsError + +#endif // #ifndef PGE_BASE_CALLBACKS_H diff --git a/pge_file_lib_globs.h b/pge_file_lib_globs.h index 81a7845..0f76e62 100644 --- a/pge_file_lib_globs.h +++ b/pge_file_lib_globs.h @@ -121,6 +121,17 @@ struct SDL_RWops; */ namespace PGE_FileFormats_misc { + +#ifndef PGE_FILES_QT +// moved from pge_file_lib_private.h, publicly used by TheXTech's macOS build + +/** + * @brief Decode percent-formatted URL + * @param sSrc source string + */ +PGESTRING url_decode(const std::string &sSrc); +#endif // #ifndef PGE_FILES_QT + /** * @brief Check the header to identify valid SMBX64 file format * @param src Header source string diff --git a/pge_file_lib_private.h b/pge_file_lib_private.h index 4b2e9e0..8c3a878 100644 --- a/pge_file_lib_private.h +++ b/pge_file_lib_private.h @@ -29,6 +29,7 @@ #define PGE_FILE_LIB_PRIVATE_H_ #include "pge_file_lib_globs.h" +#include "src/mdx/common/mdx_value.h" #ifdef PGE_FILES_QT #include @@ -93,6 +94,10 @@ inline PGESTRING PGE_ReplSTRING(PGESTRING src, PGESTRING from, PGESTRING to) { return src.replace(from, to); } +inline void PGE_ReplSTRING_inline(PGESTRING& src, PGESTRING from, PGESTRING to) +{ + src.replace(from, to); +} inline PGESTRING PGE_RemSubSTRING(const PGESTRING &src, const PGESTRING &substr) { return QString(src).remove(substr); @@ -265,12 +270,13 @@ inline PGESTRING PGE_URLDEC(const PGESTRING &src) #else /* ------ PGE_FILES_QT ------ */ +#ifndef __has_cpp_attribute +# define __has_cpp_attribute(x) 0 /* Don't fail on older compilers! */ +#endif + #include #include #include -#include -#include -#include #include #include #include @@ -288,6 +294,38 @@ static char ToLowerFun(char ch) #define ToLowerFun ::tolower #endif +#ifdef PGEFL_MISSING_STD_STOX_FUNCS +# include +/* Workaround for toolchains that has these definitions missing */ +namespace std +{ + +inline long stol(const std::string &str, size_t* pos = nullptr, int base = 10) +{ + (void)pos; + return ::strtol(str.c_str(), nullptr, base); +} + +inline unsigned long long stoull(const std::string &str, size_t* pos = nullptr, int base = 10) +{ + (void)pos; + return ::strtoull(str.c_str(), nullptr, base); +} + +inline float stof(const std::string &str, size_t* pos= nullptr) +{ + (void)pos; + return ::strtof(str.c_str(), nullptr); +} + +inline float stod(const std::string &str, size_t* pos= nullptr) +{ + (void)pos; + return ::strtod(str.c_str(), nullptr); +} +} +#endif + typedef std::string::size_type pge_size_t; inline PGESTRING PGESTR_Simpl(PGESTRING str) { @@ -340,7 +378,6 @@ namespace PGE_FileFormats_misc void RemoveSub(std::string &sInput, const std::string &sub); bool hasEnding(std::string const &fullString, std::string const &ending); PGESTRING url_encode(const PGESTRING &sSrc); - PGESTRING url_decode(const std::string &sSrc); std::string base64_encode(unsigned char const *bytes_to_encode, size_t in_len, bool no_padding = false); std::string base64_encode(std::string const &source, bool no_padding = false); std::string base64_decode(std::string const &encoded_string); @@ -362,6 +399,11 @@ inline PGESTRING PGE_ReplSTRING(PGESTRING src, const PGESTRING &from, const PGES return src; } +inline void PGE_ReplSTRING_inline(PGESTRING& src, const PGESTRING &from, const PGESTRING &to) +{ + PGE_FileFormats_misc::replaceAll(src, from, to); +} + inline PGESTRING PGE_RemSubSTRING(PGESTRING src, const PGESTRING &substr) { PGE_FileFormats_misc::RemoveSub(src, substr); @@ -469,17 +511,16 @@ inline PGESTRING removeSpaces(const PGESTRING &src) template PGESTRING fromNum(T num) { - std::ostringstream n; - n << num; - return n.str(); + PGESTRING out; + MDX_save_value(out, num); + return out; } inline PGESTRING fromBoolToNum(bool num) { - std::ostringstream n; - n << static_cast(num); - return n.str(); + return fromNum((int)num); } + #define PGE_URLENC(src) PGE_FileFormats_misc::url_encode(src) #define PGE_URLDEC(src) PGE_FileFormats_misc::url_decode(src) #define PGE_BASE64ENC(src) PGE_FileFormats_misc::base64_encode(src) @@ -491,6 +532,7 @@ inline PGESTRING fromBoolToNum(bool num) #define PGE_BASE64DEC_A(src) PGE_FileFormats_misc::base64_decodeA(src) #endif /* ------ PGE_FILES_QT ------ */ + inline bool PGE_floatEqual(double l, double r, double precission) { return static_cast(l * std::pow(10.0, precission)) == @@ -531,4 +573,21 @@ static inline int PGE_toNearestS(double o) return int(PGE_toNearestU(o)) * sign; } + +#if __has_cpp_attribute(likely) +# define PGE_ATTR_LIKELY [[likely]] +#elif __has_cpp_attribute(__likely__) +# define PGE_ATTR_LIKELY [[__likely__]] +#else +# define PGE_ATTR_LIKELY +#endif + +#if __has_cpp_attribute(unlikely) +# define PGE_ATTR_UNLIKELY [[unlikely]] +#elif __has_cpp_attribute(__unlikely__) +# define PGE_ATTR_UNLIKELY [[__unlikely__]] +#else +# define PGE_ATTR_UNLIKELY +#endif + #endif // PGE_FILE_LIB_PRIVATE_H_ diff --git a/pge_file_library.cmake b/pge_file_library.cmake index 3d35c41..f61ed0c 100644 --- a/pge_file_library.cmake +++ b/pge_file_library.cmake @@ -7,7 +7,6 @@ list(APPEND PGE_FILE_LIBRARY_SRCS ${CMAKE_CURRENT_LIST_DIR}/src/ConvertUTF_PGEFF.c ${CMAKE_CURRENT_LIST_DIR}/src/file_formats.cpp ${CMAKE_CURRENT_LIST_DIR}/src/smbx64/file_rw_lvl.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/smbx38a/file_rw_lvl_38a.cpp ${CMAKE_CURRENT_LIST_DIR}/src/pgex/file_rw_lvlx.cpp ${CMAKE_CURRENT_LIST_DIR}/src/pgex/file_rw_meta.cpp ${CMAKE_CURRENT_LIST_DIR}/src/smbx64/file_rw_npc_txt.cpp @@ -26,10 +25,31 @@ list(APPEND PGE_FILE_LIBRARY_SRCS ${CMAKE_CURRENT_LIST_DIR}/src/wld_filedata.cpp ${CMAKE_CURRENT_LIST_DIR}/src/pge_file_lib_globs.cpp ${CMAKE_CURRENT_LIST_DIR}/src/pgex/file_rw_savx.cpp -# ${CMAKE_CURRENT_LIST_DIR}/src/smbx38a/file_rw_lvl_38a_old.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/smbx38a/file_rw_wld_38a.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/common/mdx_exception.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/common/value/mdx_value_string.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/common/value/mdx_value_numeric.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/common/value/mdx_value_boollist.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/common/value/mdx_value_qstringlist.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/common/mdx_object.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/common/mdx_section.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/common/mdx_file.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/mdx_meta_file.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/mdx_meta_file_rw.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/mdx_level_file.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/mdx_level_file_rw.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/mdx_world_file.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/mdx_world_file_rw.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/mdx_gamesave_file.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/mdx/mdx_gamesave_file_rw.cpp ) +if(NOT PGEFL_DISABLE_SMBX38A) + list(APPEND PGE_FILE_LIBRARY_SRCS + ${CMAKE_CURRENT_LIST_DIR}/src/smbx38a/file_rw_lvl_38a.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/smbx38a/file_rw_wld_38a.cpp + ) +endif() + if(PGEFL_ENABLE_RWOPS) list(APPEND PGE_FILE_LIBRARY_SRCS ${CMAKE_CURRENT_LIST_DIR}/src/pge_file_lib_globs_rwops.cpp diff --git a/save_filedata.h b/save_filedata.h index 6e28e17..64eab71 100644 --- a/save_filedata.h +++ b/save_filedata.h @@ -37,6 +37,7 @@ #include "pge_file_lib_globs.h" #include "meta_filedata.h" +#include "pge_base_callbacks.h" //! Game Save specific Visible element entry typedef PGEPAIR visibleItem; @@ -101,7 +102,7 @@ struct saveUserData //! Optionally, for example, level filename PGESTRING location_name; //! Name of data section - PGESTRING name; + PGESTRING name = "default"; //! key=value Data entries PGELIST data; }; @@ -122,6 +123,44 @@ struct saveLevelInfo unsigned int exits_got; }; +/*! + * \brief Game save header data structure. Contains general values for the game save. + */ +struct GamesaveHead +{ + //! Number of lives + int lives = 3; + //! Hundreds of coins, used in TheXTech to replace the legacy lives system. In the file format, 0 is reserved as "unspecified", and 1 is the first non-negative value. + int hundreds = 0; + //! Number of coins + unsigned int coins = 0; + //! Number of points + unsigned int points = 0; + //! Total stars + unsigned int totalStars = 0; + + //! Last world map position X + long worldPosX = 0; + //! Last world map position Y + long worldPosY = 0; + + //! Last entered/exited warp Array-ID on the HUB-based episodes. + unsigned long last_hub_warp = 0; + //! Last visited sub-hub level file + PGESTRING last_hub_level_file; + + //! Current world music ID + unsigned int musicID = 0; + //! Current world music file (custom music) + PGESTRING musicFile; + + //! Is episode was completed in last time + bool gameCompleted = false; + + //! count of levels and paths (used for completion percent computation) + unsigned int lvl_path_count = 0; +}; + /*! * \brief Game save data structure */ @@ -180,4 +219,32 @@ struct GamesaveData PGELIST levelInfo; }; +struct GamesaveLoadCallbacks : PGE_FileFormats_misc::LoadCallbacks +{ + callback load_head = nullptr; + callback load_charstate = nullptr; + callback load_selchar = nullptr; + callback load_vis_level = nullptr; + callback load_vis_path = nullptr; + callback load_vis_scene = nullptr; + callback load_star = nullptr; + callback load_saved_layer = nullptr; + callback load_level_info = nullptr; + callback load_userdata = nullptr; +}; + +struct GamesaveSaveCallbacks : PGE_FileFormats_misc::SaveCallbacks +{ + callback save_head = nullptr; + callback save_charstate = nullptr; + callback save_selchar = nullptr; + callback save_vis_level = nullptr; + callback save_vis_path = nullptr; + callback save_vis_scene = nullptr; + callback save_star = nullptr; + callback save_saved_layer = nullptr; + callback save_level_info = nullptr; + callback save_userdata = nullptr; +}; + #endif // SAVE_FILEDATA_H diff --git a/smbx64.h b/smbx64.h index 43c41ca..9c8f672 100644 --- a/smbx64.h +++ b/smbx64.h @@ -35,6 +35,8 @@ #include "pge_file_lib_globs.h" #include "pge_file_lib_private.h" +#include "src/mdx/mdx_utils.h" + /*! * \brief SMBX64 Standard validation and raw data conversion functions */ @@ -48,7 +50,9 @@ namespace SMBX64 *out = input.toUInt(&ok); if(!ok) throw std::invalid_argument("Could not convert to unsigned int"); #else - *out = static_cast(std::stoul(input)); + if(input.size() != 0 && MDX_load_uint(*out, input.c_str()) == input.c_str() + input.size()) PGE_ATTR_LIKELY + return; + throw std::invalid_argument("Could not convert number"); #endif } @@ -59,7 +63,9 @@ namespace SMBX64 *out = input.toULong(&ok); if(!ok) throw std::invalid_argument("Could not convert to unsigned long"); #else - *out = std::stoul(input); + if(input.size() != 0 && MDX_load_ulong(*out, input.c_str()) == input.c_str() + input.size()) PGE_ATTR_LIKELY + return; + throw std::invalid_argument("Could not convert number"); #endif } @@ -70,7 +76,9 @@ namespace SMBX64 *out = input.toULongLong(&ok); if(!ok) throw std::invalid_argument("Could not convert to unsigned long long"); #else - *out = std::stoull(input); + if(input.size() != 0 && MDX_load_ulonglong(*out, input.c_str()) == input.c_str() + input.size()) PGE_ATTR_LIKELY + return; + throw std::invalid_argument("Could not convert number"); #endif } inline void ReadUInt(int*out, PGESTRING &input) @@ -80,7 +88,9 @@ namespace SMBX64 *out = static_cast(input.toUInt(&ok)); if(!ok) throw std::invalid_argument("Could not convert to unsigned int"); #else - *out = static_cast(static_cast(std::stoul(input))); + if(input.size() != 0 && MDX_load_uint(*out, input.c_str()) == input.c_str() + input.size()) PGE_ATTR_LIKELY + return; + throw std::invalid_argument("Could not convert number"); #endif } inline void ReadUInt(long*out, PGESTRING &input) @@ -90,7 +100,9 @@ namespace SMBX64 *out = static_cast(input.toULong(&ok)); if(!ok) throw std::invalid_argument("Could not convert to unsigned long"); #else - *out = static_cast(std::stoul(input)); + if(input.size() != 0 && MDX_load_ulong(*out, input.c_str()) == input.c_str() + input.size()) PGE_ATTR_LIKELY + return; + throw std::invalid_argument("Could not convert number"); #endif } inline void ReadUInt(long long*out, PGESTRING &input) @@ -100,7 +112,9 @@ namespace SMBX64 *out = static_cast(input.toULongLong(&ok)); if(!ok) throw std::invalid_argument("Could not convert to unsigned long long"); #else - *out = static_cast(std::stoull(input)); + if(input.size() != 0 && MDX_load_ulonglong(*out, input.c_str()) == input.c_str() + input.size()) PGE_ATTR_LIKELY + return; + throw std::invalid_argument("Could not convert number"); #endif } @@ -111,7 +125,9 @@ namespace SMBX64 *out = input.toInt(&ok); if(!ok) throw std::invalid_argument("Could not convert to int"); #else - *out = std::stoi(input); + if(input.size() != 0 && MDX_load_int(*out, input.c_str()) == input.c_str() + input.size()) PGE_ATTR_LIKELY + return; + throw std::invalid_argument("Could not convert number"); #endif } @@ -122,7 +138,9 @@ namespace SMBX64 *out = input.toLong(&ok); if(!ok) throw std::invalid_argument("Could not convert to long"); #else - *out = std::stol(input); + if(input.size() != 0 && MDX_load_long(*out, input.c_str()) == input.c_str() + input.size()) PGE_ATTR_LIKELY + return; + throw std::invalid_argument("Could not convert number"); #endif } inline void ReadSInt(long long*out, PGESTRING &input) @@ -132,7 +150,9 @@ namespace SMBX64 *out = input.toLongLong(&ok); if(!ok) throw std::invalid_argument("Could not convert to long long"); #else - *out = std::stoll(input); + if(input.size() != 0 && MDX_load_longlong(*out, input.c_str()) == input.c_str() + input.size()) PGE_ATTR_LIKELY + return; + throw std::invalid_argument("Could not convert number"); #endif } @@ -231,6 +251,8 @@ namespace SMBX64 *out = qRound(input.toDouble(&ok)); if(!ok) throw std::invalid_argument("Could not convert to Double"); #else + if(input.size() != 0 && MDX_load_int(*out, input.c_str()) == input.c_str() + input.size()) PGE_ATTR_LIKELY + return; *out = static_cast(std::round(std::stod(input))); #endif } @@ -242,6 +264,8 @@ namespace SMBX64 *out = static_cast(std::round(input.toDouble(&ok))); if(!ok) throw std::invalid_argument("Could not convert to Double"); #else + if(input.size() != 0 && MDX_load_long(*out, input.c_str()) == input.c_str() + input.size()) PGE_ATTR_LIKELY + return; *out = static_cast(std::round(std::stod(input))); #endif } @@ -253,13 +277,26 @@ namespace SMBX64 out->clear(); return; } - *out = input; + PGESTRING &target = *out; - if(target[0] == PGEChar('\"')) - PGE_RemStrRng(target, 0, 1); - if( (!IsEmpty(target)) && (target[target.size()-1] == PGEChar('\"')) ) + + if(input[0] != PGEChar('\"') && input[input.size() - 1] != PGEChar('\"')) + target = std::move(input); + else if(input[0] != PGEChar('\"')) + { + target = std::move(input); PGE_RemStrRng(target, int(target.size() - 1), 1); - target = PGE_ReplSTRING(target, "\"", "\'");//Correct damaged by SMBX line + } + else + { + target = input; + PGE_RemStrRng(target, 0, 1); + + if( (!IsEmpty(target)) && (target[target.size()-1] == PGEChar('\"')) ) + PGE_RemStrRng(target, int(target.size() - 1), 1); + } + + PGE_ReplSTRING_inline(target, "\"", "\'");//Correct damaged by SMBX line } diff --git a/src/file_formats.cpp b/src/file_formats.cpp index 98ca14e..bc9fce9 100644 --- a/src/file_formats.cpp +++ b/src/file_formats.cpp @@ -26,9 +26,11 @@ #include "pge_file_lib_sys.h" #include "pge_file_lib_private.h" +#include "CSVUtils.h" #include "file_formats.h" +bool FileFormats::g_use_legacy_pgex_parser = false; PGESTRING FileFormats::removeQuotes(const PGESTRING &str) { @@ -75,6 +77,20 @@ PGESTRING FileFormats::getErrorString(FileFormats::ErrorCodes errCode) return "Unknown error"; } +/***************************************************************************/ +void FileFormatsError::add_exc_info(const std::exception& e, long linenum, PGESTRING&& line) +{ +#ifdef PGE_FILES_QT + ERROR_info += QString::fromStdString(exception_to_pretty_string(e)); +#else + ERROR_info += exception_to_pretty_string(e); +#endif + ERROR_linenum = linenum; + ERROR_linedata = std::move(line); + PGE_CutLength(ERROR_linedata, 50); + PGE_FilterBinary(ERROR_linedata); +} + /***************************************************************************/ CrashData::CrashData() : used(false), untitled(false), modifyed(false), strictModeSMBX64(false), fmtID(0), fmtVer(64) {} diff --git a/src/file_rwopen.cpp b/src/file_rwopen.cpp index cee4048..ce6f95d 100644 --- a/src/file_rwopen.cpp +++ b/src/file_rwopen.cpp @@ -93,9 +93,15 @@ bool FileFormats::OpenLevelFileT(PGE_FileFormats_misc::TextInput &file, LevelDat if(PGE_StartsWith(firstLine, "SMBXFile")) { +#ifdef PGEFL_DISABLE_SMBX38A + FileData.meta.ReadFileValid = false; + FileData.meta.ERROR_info = "SMBX-38A unsupported"; + return false; +#else //Read SMBX65-38A LVL File if(!ReadSMBX38ALvlFile(file, FileData)) return false; +#endif } else if(PGE_FileFormats_misc::PGE_DetectSMBXFile(firstLine)) { @@ -125,6 +131,50 @@ bool FileFormats::OpenLevelFileT(PGE_FileFormats_misc::TextInput &file, LevelDat return true; } +bool FileFormats::OpenLevelFileT(PGE_FileFormats_misc::TextInput &file, const LevelLoadCallbacks &cb) +{ + PGESTRING firstLine; + + file.read(firstLine, 8); + file.seek(0, PGE_FileFormats_misc::TextInput::begin); + + if(PGE_StartsWith(firstLine, "SMBXFile")) + { +#ifdef PGEFL_DISABLE_SMBX38A + if(!cb.on_error) + return false; + + FileFormatsError error; + error.ERROR_info = "SMBX-38A unsupported"; + + cb.on_error(cb.userdata, error); + + return false; +#else + //Read SMBX65-38A LVL File + if(!ReadSMBX38ALvlFile(file, cb)) + return false; +#endif + } + else if(PGE_FileFormats_misc::PGE_DetectSMBXFile(firstLine)) + { + //Disable UTF8 for SMBX64 files + if(!file.reOpen(false)) + return false; + //Read SMBX LVL File + if(!ReadSMBX64LvlFile(file, cb)) + return false; + } + else + { + //Read PGE LVLX File + if(!ReadExtendedLvlFile(file, cb)) + return false; + } + + return true; +} + bool FileFormats::OpenLevelFileHeader(const PGESTRING &filePath, LevelData &data) { PGE_FileFormats_misc::TextFileInput file; @@ -168,8 +218,14 @@ bool FileFormats::OpenLevelFileHeaderT(PGE_FileFormats_misc::TextInput &file, Le if(PGE_StartsWith(firstLine, "SMBXFile")) { +#ifdef PGEFL_DISABLE_SMBX38A + data.meta.ReadFileValid = false; + data.meta.ERROR_info = "SMBX-38A unsupported"; + return false; +#else //Read SMBX65-38A LVL File return ReadSMBX38ALvlFileHeaderT(file, data); +#endif } else if(PGE_FileFormats_misc::PGE_DetectSMBXFile(firstLine)) { @@ -234,6 +290,10 @@ bool FileFormats::SaveLevelFile(LevelData &FileData, return true; } //break; +#ifdef PGEFL_DISABLE_SMBX38A + case LVL_SMBX38A: + break; +#else case LVL_SMBX38A: { uint32_t outVer = formatVersion == c_version_default ? c_latest_version_smbx38a : formatVersion; @@ -245,6 +305,7 @@ bool FileFormats::SaveLevelFile(LevelData &FileData, return true; } //break; +#endif // #ifdef PGEFL_DISABLE_SMBX38A } FileData.meta.ERROR_info = "Unsupported file type"; @@ -275,6 +336,10 @@ bool FileFormats::SaveLevelData(LevelData &FileData, return true; } //break; +#ifdef PGEFL_DISABLE_SMBX38A + case LVL_SMBX38A: + break; +#else case LVL_SMBX38A: { uint32_t outVer = formatVersion == c_version_default ? c_latest_version_smbx38a : formatVersion; @@ -282,6 +347,7 @@ bool FileFormats::SaveLevelData(LevelData &FileData, return true; } //break; +#endif // #ifdef PGEFL_DISABLE_SMBX38A } FileData.meta.ERROR_info = "Unsupported file type"; @@ -353,9 +419,15 @@ bool FileFormats::OpenWorldFileT(PGE_FileFormats_misc::TextInput &file, WorldDat if(PGE_StartsWith(firstLine, "SMBXFile")) { +#ifdef PGEFL_DISABLE_SMBX38A + data.meta.ReadFileValid = false; + data.meta.ERROR_info = "SMBX-38A unsupported"; + return false; +#else //Read SMBX-38A WLD File if(!ReadSMBX38AWldFile(file, data)) return false; +#endif } else if(PGE_FileFormats_misc::PGE_DetectSMBXFile(firstLine)) { @@ -385,6 +457,50 @@ bool FileFormats::OpenWorldFileT(PGE_FileFormats_misc::TextInput &file, WorldDat return true; } +bool FileFormats::OpenWorldFileT(PGE_FileFormats_misc::TextInput &file, const WorldLoadCallbacks &cb) +{ + PGESTRING firstLine; + + file.read(firstLine, 8); + file.seek(0, PGE_FileFormats_misc::TextInput::begin); + + if(PGE_StartsWith(firstLine, "SMBXFile")) + { +#ifdef PGEFL_DISABLE_SMBX38A + if(!cb.on_error) + return false; + + FileFormatsError error; + error.ERROR_info = "SMBX-38A unsupported"; + + cb.on_error(cb.userdata, error); + + return false; +#else + //Read SMBX-38A WLD File + if(!ReadSMBX38AWldFile(file, cb)) + return false; +#endif + } + else if(PGE_FileFormats_misc::PGE_DetectSMBXFile(firstLine)) + { + //Disable UTF8 for SMBX64 files + if(!file.reOpen(false)) + return false; + //Read SMBX WLD File + if(!ReadSMBX64WldFile(file, cb)) + return false; + } + else + { + //Read PGE WLDX File + if(!ReadExtendedWldFile(file, cb)) + return false; + } + + return true; +} + bool FileFormats::OpenWorldFileHeader(const PGESTRING &filePath, WorldData &data) { PGE_FileFormats_misc::TextFileInput file; @@ -431,7 +547,13 @@ bool FileFormats::OpenWorldFileHeaderT(PGE_FileFormats_misc::TextInput &file, Wo if(PGE_StartsWith(firstLine, "SMBXFile")) { //Read SMBX-38A WLD File +#ifdef PGEFL_DISABLE_SMBX38A + data.meta.ReadFileValid = false; + data.meta.ERROR_info = "SMBX-38A unsupported"; + return false; +#else return ReadSMBX38AWldFileHeaderT(file, data); +#endif } else if(PGE_FileFormats_misc::PGE_DetectSMBXFile(firstLine)) { @@ -491,6 +613,10 @@ bool FileFormats::SaveWorldFile(WorldData &FileData, return true; } //break; +#ifdef PGEFL_DISABLE_SMBX38A + case WLD_SMBX38A: + break; +#else case WLD_SMBX38A: { uint32_t outVer = formatVersion == c_version_default ? c_latest_version_smbx38a : formatVersion; @@ -502,6 +628,7 @@ bool FileFormats::SaveWorldFile(WorldData &FileData, return true; } //break; +#endif // #ifdef PGEFL_DISABLE_SMBX38A } FileData.meta.ERROR_info = "Unsupported file type"; @@ -530,6 +657,10 @@ bool FileFormats::SaveWorldData(WorldData &FileData, return true; } //break; +#ifdef PGEFL_DISABLE_SMBX38A + case WLD_SMBX38A: + break; +#else case WLD_SMBX38A: { uint32_t outVer = formatVersion == c_version_default ? c_latest_version_smbx38a : formatVersion; @@ -537,6 +668,7 @@ bool FileFormats::SaveWorldData(WorldData &FileData, return true; } //break; +#endif // #ifdef PGEFL_DISABLE_SMBX38A } FileData.meta.ERROR_info = "Unsupported file type"; diff --git a/src/lvl_filedata.cpp b/src/lvl_filedata.cpp index 55408c8..958fa45 100644 --- a/src/lvl_filedata.cpp +++ b/src/lvl_filedata.cpp @@ -374,6 +374,9 @@ PlayerPoint FileFormats::CreateLvlPlayerPoint(unsigned int id) { PlayerPoint dummyPlayer; dummyPlayer.id = id; + + dummyPlayer.w = 24; + switch(id) { case 1: diff --git a/src/mdx/common/mdx_exception.cpp b/src/mdx/common/mdx_exception.cpp new file mode 100644 index 0000000..c9ed620 --- /dev/null +++ b/src/mdx/common/mdx_exception.cpp @@ -0,0 +1,123 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +#include +#include + +#include "mdx/common/mdx_exception.h" + +const char* MDX_exception::what() const noexcept +{ + return "MDX_exception"; +} + +const char* MDX_parse_error::what() const noexcept +{ + return "MDX_parse_error"; +} + +// misc error for MDX parsing +const char* MDX_parse_error_misc::what() const noexcept +{ + return m_message; +} + +// primitive error denoting that a term is malformed +const char* MDX_bad_term::what() const noexcept +{ + return m_message; +} + +// primitive error denoting that a delimiter is missing +MDX_missing_delimiter::MDX_missing_delimiter(char expected) noexcept +{ + static const char prefix[] = "Missing "; + static_assert(sizeof(m_description) == sizeof(prefix) + 1, "Description size inconsistent with prefix size"); + + memcpy(m_description, prefix, sizeof(prefix) - 1); + m_description[sizeof(prefix) - 1] = expected; + m_description[sizeof(prefix)] = '\0'; +} + +const char* MDX_missing_delimiter::what() const noexcept +{ + return m_description; +} + +// derived error denoting an unexpected character +MDX_unexpected_character::MDX_unexpected_character(char unexpected) noexcept +{ + static const char prefix[] = "Unexpected "; + static_assert(sizeof(m_description) == sizeof(prefix) + 1, "Description size inconsistent with prefix size"); + + memcpy(m_description, prefix, sizeof(prefix) - 1); + m_description[sizeof(prefix) - 1] = unexpected; + m_description[sizeof(prefix)] = '\0'; +} + +const char* MDX_unexpected_character::what() const noexcept +{ + return m_description; +} + +// derived error indicating which field caused a parse failure +MDX_bad_field::MDX_bad_field(const char* field_name) noexcept +{ + static const char prefix[] = "Bad field "; + memcpy(m_description, prefix, sizeof(prefix) - 1); + strncpy(m_description + sizeof(prefix) - 1, field_name, sizeof(m_description) - sizeof(prefix)); + m_description[sizeof(m_description) - 1] = '\0'; +} + +MDX_bad_field::MDX_bad_field(const char* field_name, size_t len) noexcept +{ + static const char prefix[] = "Bad field "; + memcpy(m_description, prefix, sizeof(prefix) - 1); + size_t buff_left = sizeof(m_description) - sizeof(prefix); + if(buff_left > len) + buff_left = len; + strncpy(m_description + sizeof(prefix) - 1, field_name, buff_left); + m_description[sizeof(m_description) - 1] = '\0'; +} + +const char* MDX_bad_field::what() const noexcept +{ + return m_description; +} + +// derived error indicating which array item caused a parse failure + +MDX_bad_array::MDX_bad_array(size_t index) noexcept +{ + static const char prefix[] = "Bad item "; + memcpy(m_description, prefix, sizeof(prefix) - 1); + snprintf(m_description + sizeof(prefix) - 1, sizeof(m_description) - (sizeof(prefix) - 1), "%lu", (long unsigned)index); +} + +const char* MDX_bad_array::what() const noexcept +{ + return m_description; +} diff --git a/src/mdx/common/mdx_exception.h b/src/mdx/common/mdx_exception.h new file mode 100644 index 0000000..a6fcd1c --- /dev/null +++ b/src/mdx/common/mdx_exception.h @@ -0,0 +1,105 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_exception.h + * + * \brief Defines a set of exceptions used to interrupt parsing in MDX + * + */ + +#pragma once +#ifndef MDX_EXCEPTION_HPP +#define MDX_EXCEPTION_HPP + +#include + +class MDX_exception : public std::exception +{ + virtual const char* what() const noexcept; +}; + +// base error for MDX parsing +class MDX_parse_error : public MDX_exception +{ + virtual const char* what() const noexcept; +}; + +// misc error for MDX parsing +class MDX_parse_error_misc : public MDX_parse_error +{ + const char* const m_message = nullptr; +public: + MDX_parse_error_misc(const char* message) noexcept : m_message(message) {} + virtual const char* what() const noexcept; +}; + +// derived error denoting that a term is malformed +class MDX_bad_term : public MDX_parse_error +{ + const char* const m_message = nullptr; +public: + MDX_bad_term(const char* message) noexcept : m_message(message) {} + virtual const char* what() const noexcept; +}; + +// derived error denoting that a delimiter is missing +class MDX_missing_delimiter : public MDX_parse_error +{ + char m_description[10]; +public: + MDX_missing_delimiter(char expected) noexcept; + virtual const char* what() const noexcept; +}; + +// derived error denoting an unexpected character +class MDX_unexpected_character : public MDX_parse_error +{ + char m_description[13]; +public: + MDX_unexpected_character(char unexpected) noexcept; + virtual const char* what() const noexcept; +}; + +// derived error indicating which field caused a parse failure +class MDX_bad_field : public MDX_parse_error +{ + char m_description[16]; +public: + MDX_bad_field(const char* field_name) noexcept; + MDX_bad_field(const char* field_name, size_t len) noexcept; + virtual const char* what() const noexcept; +}; + +// derived error indicating which array item caused a parse failure +class MDX_bad_array : public MDX_parse_error +{ + char m_description[16]; +public: + MDX_bad_array(size_t index) noexcept; + virtual const char* what() const noexcept; +}; + +#endif // #ifndef MDX_EXCEPTION_HPP diff --git a/src/mdx/common/mdx_field.h b/src/mdx/common/mdx_field.h new file mode 100644 index 0000000..d1780b0 --- /dev/null +++ b/src/mdx/common/mdx_field.h @@ -0,0 +1,255 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_field.h + * + * \brief Code to represent single fields (marker:value pairs) + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#pragma once +#ifndef MDX_FIELD_H +#define MDX_FIELD_H + +#include "pge_file_lib_globs.h" +#include "mdx/common/mdx_exception.h" +#include "mdx/common/mdx_value.h" + +#include + + +const char* MDX_skip_field(const char* line); + +struct MDX_BaseObject; + +struct MDX_BaseField +{ +public: + enum class SaveMode + { + normal, //!< store if can_save returns true + no_skip, //!< always store + not_only, //!< if only these values are non-default, the object will be ignored + } m_save_mode = SaveMode::normal; + +protected: + const char* const m_field_name; + + MDX_BaseField(MDX_BaseObject* parent, const char* field_name, SaveMode save_mode = SaveMode::normal); + + /* attempts to load the matched field to the destination, and returns the new load pointer following the ';'. */ + virtual const char* do_load(void* dest, const char* field_data) const = 0; + /* confirms whether the field is non-default. */ + virtual bool can_save(const void* src, const void* /* obj_t */ ref) const = 0; + /* tries to write the field, and returns false if this is impossible. */ + virtual bool do_save(std::string& out, const void* src) const = 0; + +public: + /* attempts to match the field name. if successful, returns true and modifies the load pointer. */ + bool try_load(void* dest, const char*& field_name) const; + + /* confirms whether the field is non-default, and writes it to out if so. */ + bool try_save(std::string& out, const void* src, const void* ref) const; +}; + +template +struct MDX_Field : public MDX_BaseField +{ + using MDX_BaseField::m_field_name; + using SaveMode = typename MDX_BaseField::SaveMode; + + field_t obj_t::* const m_field = nullptr; + + MDX_Field(MDX_BaseObject* parent, const char* field_name, field_t obj_t::* field, SaveMode save_mode = SaveMode::normal) + : MDX_BaseField(parent, field_name, save_mode), m_field(field) {} + + virtual const char* do_load(void* _dest, const char* field_data) const + { + obj_t& dest = *reinterpret_cast(_dest); + + return MDX_load_value(dest.*m_field, field_data); + } + + virtual bool can_save(const void* _src, const void* _ref) const + { + const obj_t& src = *reinterpret_cast(_src); + const obj_t& ref = *reinterpret_cast(_ref); + + return !MDX_Value::is_ref(src.*m_field, ref.*m_field); + } + + virtual bool do_save(std::string& out, const void* _src) const + { + const obj_t& src = *reinterpret_cast(_src); + + return MDX_save_value(out, src.*m_field); + } +}; + +template +struct MDX_NonNegField : public MDX_Field +{ + using MDX_Field::MDX_Field; + using MDX_Field::m_field; + + virtual const char* do_load(void* _dest, const char* field_data) const + { + obj_t& dest = *reinterpret_cast(_dest); + + const char* ret = MDX_load_value(dest.*m_field, field_data); + + if(*field_data == '-' || dest.*m_field < 0) + throw(MDX_bad_term("Negative value")); + + return ret; + } +}; + +template +struct MDX_UniqueField : public MDX_BaseField +{ + using MDX_BaseField::m_field_name; + + using load_func_t = const char* (*)(obj_t& dest, const char* field_data); + using save_func_t = bool (*)(std::string& out, const obj_t& src); + + load_func_t m_load_func = nullptr; + save_func_t m_save_func = nullptr; + + MDX_UniqueField(MDX_BaseObject* parent, const char* field_name, load_func_t load_func, save_func_t save_func) + : MDX_BaseField(parent, field_name), m_load_func(load_func), m_save_func(save_func) {} + + virtual const char* do_load(void* _dest, const char* field_data) const + { + obj_t& dest = *reinterpret_cast(_dest); + + if(!m_load_func) + return field_data; + + return m_load_func(dest, field_data); + } + + virtual bool can_save(const void* src, const void* ref) const + { + (void)src; (void)ref; + return (bool)(m_save_func); + } + + virtual bool do_save(std::string& out, const void* _src) const + { + const obj_t& src = *reinterpret_cast(_src); + + return m_save_func(out, src); + } +}; + +template +struct MDX_NestedField : public MDX_BaseField +{ + using MDX_BaseField::m_field_name; + + substruct_t obj_t::* const m_substruct = nullptr; + field_t substruct_t::* const m_field = nullptr; + + MDX_NestedField(MDX_BaseObject* parent, const char* field_name, substruct_t obj_t::* substruct, field_t substruct_t::* field) + : MDX_BaseField(parent, field_name), m_substruct(substruct), m_field(field) {} + + virtual const char* do_load(void* _dest, const char* field_data) const + { + obj_t& dest = *reinterpret_cast(_dest); + + return MDX_load_value(dest.*m_substruct.*m_field, field_data); + } + + virtual bool can_save(const void* _src, const void* _ref) const + { + const obj_t& src = *reinterpret_cast(_src); + const obj_t& ref = *reinterpret_cast(_ref); + + return !MDX_Value::is_ref(src.*m_substruct.*m_field, ref.*m_substruct.*m_field); + } + + virtual bool do_save(std::string& out, const void* _src) const + { + const obj_t& src = *reinterpret_cast(_src); + + return MDX_save_value(out, src.*m_substruct.*m_field); + } +}; + +template +struct MDX_NonNegNestedField : public MDX_NestedField +{ + using MDX_NestedField::MDX_NestedField; + using MDX_NestedField::m_substruct; + using MDX_NestedField::m_field; + + virtual const char* do_load(void* _dest, const char* field_data) const + { + obj_t& dest = *reinterpret_cast(_dest); + + const char* ret = MDX_load_value(dest.*m_substruct.*m_field, field_data); + + if(*field_data == '-' || dest.*m_substruct.*m_field < 0) + throw(MDX_bad_term("Illegal negative")); + + return ret; + } +}; + +template +struct MDX_FieldXtra : public MDX_BaseField +{ + MDX_FieldXtra(MDX_BaseObject* parent) + : MDX_BaseField(parent, "XTRA") {} + + virtual const char* do_load(void* _dest, const char* field_data) const + { + obj_t& dest = *reinterpret_cast(_dest); + return MDX_load_value(dest.meta.custom_params, field_data); + } + + virtual bool can_save(const void* _src, const void* ref) const + { + (void)ref; + const obj_t& src = *reinterpret_cast(_src); + + return src.meta.custom_params != PGESTRING(); + } + + virtual bool do_save(std::string& out, const void* _src) const + { + const obj_t& src = *reinterpret_cast(_src); + + return MDX_save_value(out, src.meta.custom_params); + } +}; + +#include "mdx/common/mdx_field.hpp" + +#endif // #ifndef MDX_FIELD_H diff --git a/src/mdx/common/mdx_field.hpp b/src/mdx/common/mdx_field.hpp new file mode 100644 index 0000000..c26963d --- /dev/null +++ b/src/mdx/common/mdx_field.hpp @@ -0,0 +1,166 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_field.hpp + * + * \brief Code to represent single fields (marker:value pairs) + * + * Written as an hpp because inlining these significantly improves performance. + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#pragma once +#ifndef PGE_FIELD_HPP +#define PGE_FIELD_HPP + +#include "mdx/common/mdx_field.h" +#include "mdx/common/mdx_object.h" + +#include "pge_file_lib_globs.h" + + +inline const char* MDX_skip_field(const char* line) +{ + bool escape = false; + const char* tag_begin = line; + const char* tag_end = nullptr; + + try + { + while(true) + { + if(*line == '\0') + { + if(tag_end) + throw MDX_missing_delimiter(';'); + else + throw MDX_missing_delimiter(':'); + } + else if(escape) + { + // ignore character + escape = false; + } + else if(*line == ';') + { + if(!tag_end) + throw MDX_missing_delimiter(':'); + else + return line + 1; + } + else if(*line == ':') + { + if(!tag_end) + tag_end = line; + else + throw MDX_unexpected_character(':'); + } + else if(*line == '\\') + { + if(!tag_end) + throw MDX_unexpected_character('\\'); + else + escape = true; + } + + line++; + } + } + catch(...) + { + if(tag_end) + std::throw_with_nested(MDX_bad_field(tag_begin, tag_end - tag_begin)); + else + std::throw_with_nested(MDX_bad_field(tag_begin, line - tag_begin)); + } +} + +inline const char* MDX_finish_term(const char* line) +{ + if(*line != ';') + throw MDX_missing_delimiter(';'); + + return line + 1; +} + + +inline MDX_BaseField::MDX_BaseField(MDX_BaseObject* parent, const char* field_name, SaveMode save_mode) + : m_save_mode(save_mode), m_field_name(field_name) +{ + parent->m_fields.push_back(this); +} + +inline bool MDX_BaseField::try_load(void* dest, const char*& field_name) const +{ + int i; + + for(i = 0; m_field_name[i] != '\0'; i++) + { + if(field_name[i] != m_field_name[i]) + return false; + } + + if(field_name[i] == ':') + { + try + { + field_name = MDX_finish_term(do_load(dest, field_name + i + 1)); + } + catch(const MDX_parse_error&) + { + std::throw_with_nested(MDX_bad_field(m_field_name)); + } + return true; + } + + return false; +} + +inline bool MDX_BaseField::try_save(std::string& out, const void* src, const void* ref) const +{ + if(m_save_mode != SaveMode::no_skip && !can_save(src, ref)) + return false; + + auto old_size = out.size(); + + out += m_field_name; + out += ':'; + + bool do_skip = !do_save(out, src); + if(do_skip) + { + out.resize(old_size); + return false; + } + + out += ';'; + + return true; +} + +#endif // #ifndef PGE_FIELD_HPP diff --git a/src/mdx/common/mdx_file.cpp b/src/mdx/common/mdx_file.cpp new file mode 100644 index 0000000..f69f068 --- /dev/null +++ b/src/mdx/common/mdx_file.cpp @@ -0,0 +1,153 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +#include +#include +#include + +#include "pge_file_lib_globs.h" +#include "pge_base_callbacks.h" + +#include "mdx/common/mdx_field.h" +#include "mdx/common/mdx_section.h" +#include "mdx/common/mdx_file.h" + +bool MDX_BaseFile::load_file(PGE_FileFormats_misc::TextInput& inf, const PGE_FileFormats_misc::LoadCallbacks& cb) +{ +#ifdef PGE_FILES_QT + QString utf16_cur_line; +#endif + + std::string cur_line; + + for(auto* section : m_sections) + section->reset(); + + try + { + inf.seek(0, PGE_FileFormats_misc::TextFileInput::begin); + + while(!inf.eof()) + { +#ifndef PGE_FILES_QT + inf.readLine(cur_line); +#else + inf.readLine(utf16_cur_line); + cur_line = utf16_cur_line.toStdString(); +#endif + + bool handled = false; + for(auto* section : m_sections) + { + if(section->try_load(&cb, inf, cur_line)) + { + handled = true; + break; + } + } + + if(!handled) + { + // ignore line if all spaces + bool all_spaces = true; + for(const auto c : cur_line) + { + if(c != ' ') + all_spaces = false; + } + if(all_spaces) + continue; + + // don't allow embedded nul + if(cur_line.size() != strlen(cur_line.c_str())) + throw MDX_parse_error_misc("Bad section name"); + // otherwise, treat as unrecognized section and skip it + else + { + std::string section_name = cur_line; + MDX_skip_section(inf, cur_line, section_name.c_str()); + } + } + } + } + catch(const PGE_FileFormats_misc::callback_interrupt& i) + { + // NOT actually a problem + } + catch(const std::exception& e) + { + if(!cb.on_error) + return false; + + FileFormatsError err; + +#ifndef PGE_FILES_QT + err.ERROR_info = "Failed to parse PGEX file (line "; + err.ERROR_info += std::to_string(inf.getCurrentLineNumber()); + err.ERROR_info += ")\n"; + + err.add_exc_info(e, inf.getCurrentLineNumber(), std::move(cur_line)); +#else + err.ERROR_info = "Failed to parse PGEX file (line "; + err.ERROR_info += QString::number(inf.getCurrentLineNumber()); + err.ERROR_info += ")\n"; + + err.add_exc_info(e, inf.getCurrentLineNumber(), std::move(utf16_cur_line)); +#endif + + cb.on_error(cb.userdata, err); + return false; + } + + return true; +} + +bool MDX_BaseFile::save_file(PGE_FileFormats_misc::TextOutput& outf, const PGE_FileFormats_misc::SaveCallbacks& cb) +{ + std::string out_buffer; + + try + { + for(auto* section : m_sections) + section->do_save(&cb, outf, out_buffer); + } + catch(const std::exception& e) + { + if(!cb.on_error) + return false; + + FileFormatsError err; + + err.ERROR_info = "Failed to save PGEX file\n"; + err.add_exc_info(e, 0, PGESTRING()); + + cb.on_error(cb.err_userdata, err); + + return false; + } + + return true; +} diff --git a/src/mdx/common/mdx_file.h b/src/mdx/common/mdx_file.h new file mode 100644 index 0000000..c4efa86 --- /dev/null +++ b/src/mdx/common/mdx_file.h @@ -0,0 +1,78 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +#pragma once +#ifndef MDX_FILE_H +#define MDX_FILE_H + +/*! \file mdx_file.h + * + * \brief Code representing entire PGE-X files + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include +#include + +#include "pge_base_callbacks.h" + +#include "pge_file_lib_globs.h" +#include "mdx/common/mdx_field.h" +#include "mdx/common/mdx_section.h" + +struct MDX_BaseFile +{ +protected: + friend struct MDX_BaseSection; + + std::vector m_sections; + + bool load_file(PGE_FileFormats_misc::TextInput& inf, const PGE_FileFormats_misc::LoadCallbacks& cb); + + bool save_file(PGE_FileFormats_misc::TextOutput& outf, const PGE_FileFormats_misc::SaveCallbacks& cb); +}; + +template +struct MDX_File : public MDX_BaseFile +{ + using load_callbacks_t = _load_callbacks_t; + using save_callbacks_t = _save_callbacks_t; + template using section = MDX_Section; + + bool load_file(PGE_FileFormats_misc::TextInput& inf, const load_callbacks_t& cb) + { + return MDX_BaseFile::load_file(inf, cb); + } + + bool save_file(PGE_FileFormats_misc::TextOutput& outf, const save_callbacks_t& cb) + { + return MDX_BaseFile::save_file(outf, cb); + } +}; + +#endif // #ifndef MDX_FILE_H diff --git a/src/mdx/common/mdx_macros.h b/src/mdx/common/mdx_macros.h new file mode 100644 index 0000000..edd5aa8 --- /dev/null +++ b/src/mdx/common/mdx_macros.h @@ -0,0 +1,83 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +#pragma once +#ifndef MDX_MACROS_H +#define MDX_MACROS_H + +/*! \file mdx_macros.h + * + * \brief Unpleasant macros used for declarative syntax + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#define MDX_FIELD_NAME(MEMBER_NAME) field_ ## MEMBER_NAME +#define MDX_FIELD(NAME, MEMBER_NAME) MDX_Field MDX_FIELD_NAME(MEMBER_NAME){this, NAME, &obj_t::MEMBER_NAME} +#define MDX_FIELD_NO_SKIP(NAME, MEMBER_NAME) MDX_Field MDX_FIELD_NAME(MEMBER_NAME){this, NAME, &obj_t::MEMBER_NAME, MDX_BaseField::SaveMode::no_skip} +#define MDX_FIELD_NOT_ONLY(NAME, MEMBER_NAME) MDX_Field MDX_FIELD_NAME(MEMBER_NAME){this, NAME, &obj_t::MEMBER_NAME, MDX_BaseField::SaveMode::not_only} + +#define MDX_FIELD_NONNEG(NAME, MEMBER_NAME) MDX_NonNegField MDX_FIELD_NAME(MEMBER_NAME){this, NAME, &obj_t::MEMBER_NAME} +#define MDX_FIELD_NONNEG_NO_SKIP(NAME, MEMBER_NAME) MDX_NonNegField MDX_FIELD_NAME(MEMBER_NAME){this, NAME, &obj_t::MEMBER_NAME, MDX_BaseField::SaveMode::no_skip} +#define MDX_FIELD_NONNEG_NOT_ONLY(NAME, MEMBER_NAME) MDX_NonNegField MDX_FIELD_NAME(MEMBER_NAME){this, NAME, &obj_t::MEMBER_NAME, MDX_BaseField::SaveMode::not_only} + +#define MDX_UNIQUE_FIELD_NAME(LOAD_FUNC) unique_field_ ## LOAD_FUNC +#define MDX_UNIQUE_FIELD(NAME, LOAD_FUNC, SAVE_FUNC) MDX_UniqueField MDX_UNIQUE_FIELD_NAME(LOAD_FUNC){this, NAME, LOAD_FUNC, SAVE_FUNC} + +#define MDX_NESTED_FIELD_NAME(SUBSTRUCT_NAME, MEMBER_NAME) nested_field_ ## SUBSTRUCT_NAME ## MEMBER_NAME +#define MDX_NESTED_FIELD(NAME, SUBSTRUCT_NAME, MEMBER_NAME) MDX_NestedField MDX_NESTED_FIELD_NAME(SUBSTRUCT_NAME, MEMBER_NAME){this, NAME, &obj_t::SUBSTRUCT_NAME, &decltype(obj_t::SUBSTRUCT_NAME)::MEMBER_NAME} + +#define MDX_NESTED_FIELD_NONNEG(NAME, SUBSTRUCT_NAME, MEMBER_NAME) MDX_NonNegNestedField MDX_NESTED_FIELD_NAME(SUBSTRUCT_NAME, MEMBER_NAME){this, NAME, &obj_t::SUBSTRUCT_NAME, &decltype(obj_t::SUBSTRUCT_NAME)::MEMBER_NAME} + +#define MDX_FIELD_XTRA() MDX_FieldXtra xtra_field{this} + +#define MDX_CALLBACK_LOAD(CALLBACK_NAME) load_ ## CALLBACK_NAME +#define MDX_CALLBACK_SAVE(CALLBACK_NAME) save_ ## CALLBACK_NAME +#define MDX_SECTION_NAME(CALLBACK_NAME) section_ ## CALLBACK_NAME +#define MDX_SECTION(NAME, OBJ_T, CALLBACK_NAME) section MDX_SECTION_NAME(CALLBACK_NAME){this, NAME, false, &load_callbacks_t::MDX_CALLBACK_LOAD(CALLBACK_NAME), &save_callbacks_t::MDX_CALLBACK_SAVE(CALLBACK_NAME)} +#define MDX_SECTION_SINGLE(NAME, OBJ_T, CALLBACK_NAME) section MDX_SECTION_NAME(OBJ_T){this, NAME, true, &load_callbacks_t::MDX_CALLBACK_LOAD(CALLBACK_NAME), &save_callbacks_t::MDX_CALLBACK_SAVE(CALLBACK_NAME)} + +#define MDX_SETUP_OBJECT(OBJ_T, BODY) template<> \ +struct MDX_Object : MDX_BaseObject \ +{ \ + using obj_t = OBJ_T; \ + BODY \ +} \ + +#define MDX_ENABLE_SUB_LIST(OBJ_T) template<> \ +struct MDX_Value> : public MDX_Value_ObjectList> {}; \ +\ +template<> \ +const MDX_Object MDX_Value_ObjectList>::s_obj_loader{} \ + +#define MDX_ENABLE_SUB_STRUCT(OBJ_T) template<> \ +struct MDX_Value : public MDX_Value_Object> {}; \ +\ +template<> \ +const MDX_Object MDX_Value_Object>::s_obj_loader{} \ + +#endif // #ifndef MDX_MACROS_H diff --git a/src/mdx/common/mdx_object.cpp b/src/mdx/common/mdx_object.cpp new file mode 100644 index 0000000..d3f0731 --- /dev/null +++ b/src/mdx/common/mdx_object.cpp @@ -0,0 +1,79 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +#include +#include "mdx/common/mdx_field.h" +#include "mdx/common/mdx_object.h" + +void MDX_BaseObject::load_object(void* dest, const char* line) const +{ + const char* cur_data = line; + size_t next_field = 0; + + while(*cur_data != '\0') + { + size_t try_field = next_field; + while(true) + { + if(m_fields[try_field]->try_load(dest, cur_data)) + { + next_field++; + if(next_field == m_fields.size()) + next_field = 0; + + break; + } + + try_field++; + if(try_field == m_fields.size()) + try_field = 0; + + // couldn't find field + if(try_field == next_field) + { + cur_data = MDX_skip_field(cur_data); + break; + } + } + } +} + +bool MDX_BaseObject::save_object(std::string& out, const void* src, const void* ref) const +{ + size_t out_size_pre = out.size(); + + bool any_field = false; + for(const auto* field : m_fields) + { + bool not_only = (field->m_save_mode == MDX_BaseField::SaveMode::not_only); + any_field |= field->try_save(out, src, ref) && !not_only; + } + + if(!any_field) + out.resize(out_size_pre); + + return any_field; +} diff --git a/src/mdx/common/mdx_object.h b/src/mdx/common/mdx_object.h new file mode 100644 index 0000000..043f0fe --- /dev/null +++ b/src/mdx/common/mdx_object.h @@ -0,0 +1,63 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +#pragma once +#ifndef MDX_OBJECT_H +#define MDX_OBJECT_H + +/*! \file mdx_object.h + * + * \brief Code to represent objects using sequences of fields + * + * Used as rows in sections or items in object arrays + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include + +struct MDX_BaseObject +{ + template friend struct MDX_Value_ObjectList; + template friend struct MDX_Value_Object; + friend struct MDX_BaseSection; + friend struct MDX_BaseField; + +protected: + std::vector m_fields; + + void load_object(void* dest, const char* line) const; + + bool save_object(std::string& out, const void* src, const void* ref) const; +}; + +/* This gets defined by the user for each object type, and then gets used by sections or item lists. + In those contents it is sometimes called obj_loader_t. */ +template +struct MDX_Object; + +#endif // #ifndef MDX_OBJECT_H diff --git a/src/mdx/common/mdx_section.cpp b/src/mdx/common/mdx_section.cpp new file mode 100644 index 0000000..4bc0c89 --- /dev/null +++ b/src/mdx/common/mdx_section.cpp @@ -0,0 +1,215 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +#include +#include +#include + +#include + +#include "pge_file_lib_globs.h" +#include "mdx/common/mdx_field.h" +#include "mdx/common/mdx_object.h" +#include "mdx/common/mdx_section.h" +#include "mdx/common/mdx_file.h" + +inline bool MDX_line_is_section_end(const std::string& cur_line, const char* section_name) +{ + if(cur_line.size() <= 4) + return false; + + const char* c = cur_line.c_str(); + const char* s = section_name; + + // scan to find difference between strings + while(true) + { + if(*c != *s || *c == '\0') + break; + + c++; + s++; + } + + // section name should be a prefix to current line + if(*s != '\0') + return false; + + // current line must have a suffix of precisely "_END" + if(c[0] != '_' || c[1] != 'E' || c[2] != 'N' || c[3] != 'D' || c[4] != '\0') + return false; + + // current line must not have embedded null + if((size_t)(&c[4] - &cur_line[0]) != cur_line.size()) + return false; + + return true; +} + +void MDX_skip_section(PGE_FileFormats_misc::TextInput& inf, std::string& cur_line, const char* section_name) +{ +#ifdef PGE_FILES_QT + QString utf16_cur_line; +#endif + + while(!inf.eof()) + { +#ifndef PGE_FILES_QT + inf.readLine(cur_line); +#else + inf.readLine(utf16_cur_line); + cur_line = utf16_cur_line.toStdString(); +#endif + + if(MDX_line_is_section_end(cur_line, section_name)) + return; + } + + throw MDX_parse_error_misc("Unterminated section"); +} + +MDX_BaseSection::MDX_BaseSection(struct MDX_BaseFile* parent, const char* section_name, bool combine_objects, const MDX_BaseObject& obj_loader, void* obj_ptr, const void* ref_ptr) + : m_section_name(section_name), m_obj_loader(obj_loader), m_obj_ptr(obj_ptr), m_ref_ptr(ref_ptr), m_combine_objects(combine_objects) +{ + parent->m_sections.push_back(this); +} + +bool MDX_BaseSection::try_load(const void* cb, PGE_FileFormats_misc::TextInput& inf, std::string& cur_line) +{ +#ifdef PGE_FILES_QT + QString utf16_cur_line; +#endif + + // check match + if(cur_line != m_section_name) + return false; + + // skip if there is no callback registered + if(!has_load_callback(cb)) + return false; + + while(true) + { +#ifndef PGE_FILES_QT + inf.readLine(cur_line); +#else + inf.readLine(utf16_cur_line); + cur_line = utf16_cur_line.toStdString(); +#endif + + // empty line (or EOF) + if(cur_line.empty()) + { + if(inf.eof()) + throw MDX_parse_error_misc("Unterminated section"); + else + { + // allow it because PGE-X does + } + } + // ordinary line + else if(*(cur_line.end() - 1) == ';') + { + if(!m_combine_objects) + reset(); + + m_obj_loader.load_object(m_obj_ptr, cur_line.c_str()); + + if(!m_combine_objects) + { + if(!load_callback(cb)) + { + MDX_skip_section(inf, cur_line, m_section_name); + return true; + } + } + } + // section end line + else if(MDX_line_is_section_end(cur_line, m_section_name)) + { + if(m_combine_objects) + load_callback(cb); + + return true; + } + // unterminated line + else + throw MDX_missing_delimiter(';'); + } +} + +void MDX_BaseSection::do_save(const void* cb, PGE_FileFormats_misc::TextOutput& outf, std::string& out_buffer) +{ +#ifdef PGE_FILES_QT + QString utf16_out; +#endif + + // skip if there is no callback registered + if(!has_save_callback(cb)) + return; + + size_t out_buffer_size_pre = out_buffer.size(); + bool restore = true; + + out_buffer += m_section_name; + out_buffer += '\n'; + + for(size_t index = 0; save_callback(cb, index); index++) + { + if(!m_obj_loader.save_object(out_buffer, m_obj_ptr, m_ref_ptr)) + continue; + + out_buffer += '\n'; + restore = false; + + if(out_buffer.size() > 2048) + { +#ifdef PGE_FILES_QT + utf16_out = QString::fromStdString(out_buffer); + outf.write(utf16_out); +#else + outf.write(out_buffer); +#endif + out_buffer.clear(); + } + } + + if(restore) + out_buffer.resize(out_buffer_size_pre); + else + { + out_buffer += m_section_name; + out_buffer += "_END\n"; + +#ifdef PGE_FILES_QT + utf16_out = QString::fromStdString(out_buffer); + outf.write(utf16_out); +#else + outf.write(out_buffer); +#endif + out_buffer.clear(); + } +} diff --git a/src/mdx/common/mdx_section.h b/src/mdx/common/mdx_section.h new file mode 100644 index 0000000..11cef4d --- /dev/null +++ b/src/mdx/common/mdx_section.h @@ -0,0 +1,133 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +#pragma once +#ifndef MDX_SECTION_H +#define MDX_SECTION_H + +/*! \file mdx_section.h + * + * \brief Code to represent sections of PGE-X files + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include + +#include "pge_file_lib_globs.h" +#include "mdx/common/mdx_field.h" +#include "mdx/common/mdx_object.h" + +void MDX_skip_section(PGE_FileFormats_misc::TextInput& inf, std::string& cur_line, const char* section_name); + +struct MDX_BaseSection +{ + friend struct MDX_BaseFile; + +protected: + + const char* const m_section_name = ""; + const MDX_BaseObject& m_obj_loader; + void* const m_obj_ptr; + const void* const m_ref_ptr; + bool m_combine_objects = false; + + MDX_BaseSection(struct MDX_BaseFile* parent, const char* section_name, bool combine_objects, const MDX_BaseObject& obj_loader, void* obj_ptr, const void* ref_ptr); + + virtual bool has_load_callback(const void* load_callbacks_table) const = 0; + virtual bool has_save_callback(const void* save_callbacks_table) const = 0; + + virtual bool load_callback(const void* load_callbacks_table) = 0; + virtual bool save_callback(const void* save_callbacks_table, size_t index) = 0; + + /* attempts to match the field name. if successful, returns true and leaves the file pointer following the end of the section. */ + bool try_load(const void* cb, PGE_FileFormats_misc::TextInput& inf, std::string& cur_line); + + void do_save(const void* cb, PGE_FileFormats_misc::TextOutput& outf, std::string& out_buffer); + +public: + virtual void reset() = 0; +}; + +template +struct MDX_Section : public MDX_BaseSection +{ + using obj_t = _obj_t; + +private: + // private fields for load-time + MDX_Object<_obj_t> m_obj_loader; + + obj_t m_obj{}; + const obj_t m_ref{}; + + using load_callback_t = typename load_callbacks_t::template callback; + using save_callback_t = typename save_callbacks_t::template callback; + + typedef load_callback_t load_callbacks_t::* const load_callback_ptr_t; + typedef save_callback_t save_callbacks_t::* const save_callback_ptr_t; + + load_callback_ptr_t m_load_callback = nullptr; + save_callback_ptr_t m_save_callback = nullptr; + +protected: + virtual bool has_load_callback(const void* load_callbacks_table) const + { + const auto& cb = *reinterpret_cast(load_callbacks_table); + return cb.*m_load_callback; + } + + virtual bool has_save_callback(const void* save_callbacks_table) const + { + const auto& cb = *reinterpret_cast(save_callbacks_table); + return cb.*m_save_callback; + } + + virtual bool load_callback(const void* load_callbacks_table) + { + const auto& cb = *reinterpret_cast(load_callbacks_table); + return (cb.*m_load_callback)(cb.userdata, m_obj); + } + + virtual bool save_callback(const void* save_callbacks_table, size_t index) + { + const auto& cb = *reinterpret_cast(save_callbacks_table); + return (cb.*m_save_callback)(cb.userdata, m_obj, index); + } + +public: + MDX_Section(MDX_BaseFile* parent, const char* section_name, bool combine_objects, load_callback_ptr_t load_callback, save_callback_ptr_t save_callback) + : MDX_BaseSection(parent, section_name, combine_objects, m_obj_loader, &m_obj, &m_ref), m_load_callback(load_callback), m_save_callback(save_callback) {} + + virtual void reset() noexcept + { + m_obj.~obj_t(); + new(&m_obj) obj_t(); + } +}; + +#endif // #ifndef MDX_SECTION_H diff --git a/src/mdx/common/mdx_value.h b/src/mdx/common/mdx_value.h new file mode 100644 index 0000000..a310263 --- /dev/null +++ b/src/mdx/common/mdx_value.h @@ -0,0 +1,85 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +#pragma once +#ifndef MDX_VALUE_H +#define MDX_VALUE_H + +#include + +#include "../../../pge_file_lib_globs.h" + +/*! \file mdx_value.h + * + * \brief Code to support saving/loading values (also known as "data" in PGE-X) + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +template +struct MDX_Value +{ + static const char* load(value_t& dest, const char* field_data); + + /* tries to save the field, and returns false (restoring out to its original state) if this is impossible */ + static bool save(std::string& out, const value_t& src); + + /* checks if src matches a reference (which is assumed to be a default value) */ + static bool is_ref(const value_t& src, const value_t& reference) + { + return src == reference; + } +}; + +template<> +struct MDX_Value> +{ + static const char* load(PGELIST& dest, const char* field_data); + static bool save(std::string& out, const PGELIST& src); + static bool is_ref(const PGELIST& src, const PGELIST& /*reference*/) + { + return src.size() == 0; + } +}; + +template +inline const char* MDX_load_value(value_t& dest, const char* field_data) +{ + return MDX_Value::load(dest, field_data); +} + +template +inline bool MDX_save_value(std::string& out, const value_t& src) +{ + return MDX_Value::save(out, src); +} + +#include "value/mdx_value_list.hpp" +#include "value/mdx_value_object.hpp" +#include "value/mdx_value_objectlist.hpp" + +#endif // #ifndef MDX_VALUE_H diff --git a/src/mdx/common/value/mdx_value_boollist.cpp b/src/mdx/common/value/mdx_value_boollist.cpp new file mode 100644 index 0000000..c9c75fa --- /dev/null +++ b/src/mdx/common/value/mdx_value_boollist.cpp @@ -0,0 +1,64 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + + +/*! \file mdx_value_boollist.cpp + * + * \brief Contains code to save/load the special boolean list + * + */ + +#include "mdx/common/mdx_value.h" +#include "mdx/common/mdx_exception.h" + +const char* MDX_Value>::load(PGELIST& dest, const char* field_data) +{ + dest.clear(); + + const char* cur_pos = field_data; + + while(*cur_pos != ';' && *cur_pos != '\0') + { + if(*cur_pos == '1') + dest.push_back(true); + else if(*cur_pos == '0') + dest.push_back(false); + else + throw(MDX_bad_array(dest.size() + 1)); + + cur_pos++; + } + + return cur_pos; +} + +bool MDX_Value>::save(std::string& out, const PGELIST& src) +{ + for(bool i : src) + out += (i) ? '1' : '0'; + + return true; +} diff --git a/src/mdx/common/value/mdx_value_list.hpp b/src/mdx/common/value/mdx_value_list.hpp new file mode 100644 index 0000000..b461b8c --- /dev/null +++ b/src/mdx/common/value/mdx_value_list.hpp @@ -0,0 +1,133 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_value_list.hpp + * + * \brief Code to support saving/loading values which are lists of other values + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#pragma once +#ifndef MDX_VALUE_LIST_HPP +#define MDX_VALUE_LIST_HPP + +#include "../mdx_value.h" +#include "../mdx_exception.h" + +inline const char* MDX_finish_list_item(const char* line) +{ + if(*line == ']') + return line; + else if(*line == ',') + return line + 1; + else + throw MDX_missing_delimiter(','); +} + +/********************** + * MDX_Value * + **********************/ + +template +struct MDX_Value> +{ + static const char* load(PGELIST& dest, const char* field_data); + static bool save(std::string& out, const PGELIST& src); + static bool is_ref(const PGELIST& src, const PGELIST& /*reference*/) + { + return src.size() == 0; + } +}; + +template +const char* MDX_Value>::load(PGELIST& dest, const char* field_data) +{ + dest.clear(); + + const char* cur_pos = field_data; + if(*cur_pos != '[') + throw MDX_missing_delimiter('['); + + cur_pos++; + + while(*cur_pos != ']' && *cur_pos != '\0') + { +#ifndef PGE_FILES_QT + dest.emplace_back(); +#else + dest.push_back(subtype_t()); +#endif + + try + { + cur_pos = MDX_load_value(dest.back(), cur_pos); + cur_pos = MDX_finish_list_item(cur_pos); + } + catch(const MDX_parse_error&) + { + std::throw_with_nested(MDX_bad_array(dest.size())); + } + } + + if(*(cur_pos - 1) == ',') + throw MDX_unexpected_character(']'); + + if(*cur_pos != ']') + throw MDX_missing_delimiter(']'); + + cur_pos++; + + return cur_pos; +} + +template +bool MDX_Value>::save(std::string& out, const PGELIST& src) +{ + out.push_back('['); + + for(const subtype_t& s : src) + { + if(MDX_save_value(out, s)) + out.push_back(','); + } + + // close the array + if(out.back() == ',') + { + out.back() = ']'; + return true; + } + // nothing was written, remove the '[' + else + { + out.pop_back(); + return false; + } +} + +#endif // #ifndef MDX_VALUE_LIST_HPP diff --git a/src/mdx/common/value/mdx_value_numeric.cpp b/src/mdx/common/value/mdx_value_numeric.cpp new file mode 100644 index 0000000..a243908 --- /dev/null +++ b/src/mdx/common/value/mdx_value_numeric.cpp @@ -0,0 +1,498 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_value_numeric.cpp + * + * \brief Contains code to save/load numeric values + * + */ + +#include +#include +#include +#include +#include + +#include "pge_file_lib_private.h" + +#include "mdx/common/mdx_value.h" +#include "mdx/common/mdx_exception.h" +#include "mdx/common/value/milo_yip/itoa.h" + +/****************************** + * private utility functions * + ******************************/ + +template +static const char* s_MDX_load_uint(uint_t& dest, const char* field_data) +{ + const char* const ret_error = field_data; + + uint_t value = 0; + + while(true) + { + char c = *field_data; + + if(c < '0' || c > '9') + { + dest = value; + return field_data; + } + + field_data++; + + uint_t digit = c - '0'; + + if(value >= std::numeric_limits::max() / 10) PGE_ATTR_UNLIKELY + { + if(value > std::numeric_limits::max() / 10 || digit > std::numeric_limits::max() % 10) PGE_ATTR_LIKELY + return ret_error; + } + + value *= 10; + value += digit; + } +} + +template +static const char* s_MDX_load_int(int_t& dest, const char* field_data) +{ + const char* const ret_error = field_data; + + if(sign == 1 && *field_data == '-') + return s_MDX_load_int(dest, field_data); + + if(sign == -1) + { + field_data++; + if(*field_data < '0' || *field_data > '9') + return ret_error; + } + + int_t value = 0; + + while(true) + { + char c = *field_data; + + if(c < '0' || c > '9') + { + dest = value; + return field_data; + } + + field_data++; + + int_t digit = c - '0'; + + if(sign == 1) + { + if(value >= std::numeric_limits::max() / 10) PGE_ATTR_UNLIKELY + { + if(value > std::numeric_limits::max() / 10 || digit > std::numeric_limits::max() % 10) PGE_ATTR_LIKELY + return ret_error; + } + } + else + { + if(value <= std::numeric_limits::min() / 10) PGE_ATTR_UNLIKELY + { + if(value < std::numeric_limits::min() / 10 || digit > -(std::numeric_limits::min() % 10)) PGE_ATTR_LIKELY + return ret_error; + } + } + + value *= 10; + if(sign == 1) + value += digit; + else + value -= digit; + } +} + +static const char* s_MDX_load_double(double& dest, const char* field_data) +{ + const char* const ret_error = field_data; + + int sign = 1; + + if(*field_data == '-') + { + sign = -1; + field_data++; + } + + if((*field_data < '0' || *field_data > '9') && *field_data != '.') + return ret_error; + + double value = 0; + double divisor = 0.1; + + while(true) + { + char c = *field_data; + + if(c == '.') + { + break; + } + else if(c < '0' || c > '9') + { + if(c == 'e') + goto exponent; + + dest = sign * value; + return field_data; + } + + field_data++; + + if(value >= std::numeric_limits::max() / 10) PGE_ATTR_UNLIKELY + return ret_error; + + value *= 10; + value += static_cast(c - '0'); + } + + field_data++; + + while(true) + { + char c = *field_data; + + if(c < '0' || c > '9') + { + // don't allow "." and "-." + if(field_data == ret_error + 1 || (field_data == ret_error + 2 && sign == -1)) + return ret_error; + + if(c == 'e') + goto exponent; + + dest = sign * value; + return field_data; + } + + field_data++; + + value += divisor * static_cast(c - '0'); + divisor *= 0.1; + } + +exponent: + field_data++; + + // allow plus sign (but not followed by minus sign) + if(field_data[0] == '+' && field_data[1] != '-') + field_data++; + + const char* exp_start = field_data; + int allowed_chars = (*exp_start == '-') ? 5 : 4; + + int exponent; + field_data = s_MDX_load_int(exponent, field_data); + if(field_data == exp_start || field_data - exp_start > allowed_chars) + return ret_error; + + if(exponent > DBL_MAX_10_EXP) + return ret_error; + + dest = sign * value * std::pow(10, exponent); + return field_data; +} + +static void s_MDX_sprintf_append(std::string& out, const char* format, ...) +{ + size_t old_size = out.size(); + out.resize(old_size + 32); + + va_list values; + va_start(values, format); + int printed = vsnprintf(&out[old_size], 32, format, values); + va_end(values); + + if(printed >= 32) + { + // we would have lost something, but none of the numbers should ever take this long + out.resize(old_size + 31); + } + else + out.resize(old_size + printed); +} + + +/****************************** + * exported utility functions * + ******************************/ + +const char* MDX_load_int(int& dest, const char* field_data) +{ + return s_MDX_load_int(dest, field_data); +} + +const char* MDX_load_long(long& dest, const char* field_data) +{ + return s_MDX_load_int(dest, field_data); +} + +const char* MDX_load_longlong(long long& dest, const char* field_data) +{ + return s_MDX_load_int(dest, field_data); +} + +const char* MDX_load_uint(int& dest, const char* field_data) +{ + return s_MDX_load_uint(dest, field_data); +} + +const char* MDX_load_ulong(long& dest, const char* field_data) +{ + return s_MDX_load_uint(dest, field_data); +} + +const char* MDX_load_ulonglong(long long& dest, const char* field_data) +{ + return s_MDX_load_uint(dest, field_data); +} + +const char* MDX_load_uint(unsigned int& dest, const char* field_data) +{ + return s_MDX_load_uint(dest, field_data); +} + +const char* MDX_load_ulong(unsigned long& dest, const char* field_data) +{ + return s_MDX_load_uint(dest, field_data); +} + +const char* MDX_load_ulonglong(unsigned long long& dest, const char* field_data) +{ + return s_MDX_load_uint(dest, field_data); +} + +/****************************** + * exported utility functions * + ******************************/ + +template<> +const char* MDX_Value::load(int& dest, const char* field_data) +{ + const char* str_end = s_MDX_load_int(dest, field_data); + + if(str_end == field_data) + throw MDX_bad_term("Bad int"); + + return str_end; +} + +template<> +bool MDX_Value::save(std::string& out, const int& src) +{ + out.resize(out.size() + 32); + char* dest = &out[out.size() - 32]; + + char* end = milo_yip::i32toa((int32_t)src, dest); + + out.resize(out.size() - 32 + end - dest); + + return true; +} + +template<> +const char* MDX_Value::load(unsigned& dest, const char* field_data) +{ + const char* str_end = s_MDX_load_uint(dest, field_data); + + if(str_end == field_data) + throw MDX_bad_term("Bad uint"); + + return str_end; +} + +template<> +bool MDX_Value::save(std::string& out, const unsigned& src) +{ + out.resize(out.size() + 32); + char* dest = &out[out.size() - 32]; + + char* end = milo_yip::u32toa((uint32_t)src, dest); + + out.resize(out.size() - 32 + end - dest); + + return true; +} + +template<> +const char* MDX_Value::load(bool& dest, const char* field_data) +{ + if(*field_data == '1') + dest = true; + else if(*field_data == '0') + dest = false; + else + throw MDX_bad_term("Bad bool"); + + return field_data + 1; +} + +template<> +bool MDX_Value::save(std::string& out, const bool& src) +{ + if(src) + out += '1'; + else + out += '0'; + + return true; +} + +template<> +const char* MDX_Value::load(long& dest, const char* field_data) +{ + const char* str_end = s_MDX_load_int(dest, field_data); + + if(str_end == field_data) + throw MDX_bad_term("Bad long"); + + return str_end; +} + +template<> +bool MDX_Value::save(std::string& out, const long& src) +{ + out.resize(out.size() + 32); + char* dest = &out[out.size() - 32]; + + char* end = milo_yip::i64toa((int64_t)src, dest); + + out.resize(out.size() - 32 + end - dest); + + return true; +} + +template<> +const char* MDX_Value::load(unsigned long& dest, const char* field_data) +{ + const char* str_end = s_MDX_load_uint(dest, field_data); + + if(str_end == field_data) + throw MDX_bad_term("Bad ulong"); + + return str_end; +} + +template<> +bool MDX_Value::save(std::string& out, const unsigned long& src) +{ + out.resize(out.size() + 32); + char* dest = &out[out.size() - 32]; + + char* end = milo_yip::u64toa((uint64_t)src, dest); + + out.resize(out.size() - 32 + end - dest); + + return true; +} + +template<> +const char* MDX_Value::load(long long& dest, const char* field_data) +{ + const char* str_end = s_MDX_load_int(dest, field_data); + + if(str_end == field_data) + throw MDX_bad_term("Bad llong"); + + return str_end; +} + +template<> +bool MDX_Value::save(std::string& out, const long long& src) +{ + s_MDX_sprintf_append(out, "%lld", src); + return true; +} + +template<> +const char* MDX_Value::load(unsigned long long& dest, const char* field_data) +{ + const char* str_end = s_MDX_load_uint(dest, field_data); + + if(str_end == field_data) + throw MDX_bad_term("Bad ullong"); + + return str_end; +} + +template<> +bool MDX_Value::save(std::string& out, const unsigned long long& src) +{ + s_MDX_sprintf_append(out, "%llu", src); + return true; +} + +template<> +const char* MDX_Value::load(float& dest, const char* field_data) +{ + double ret; + const char* str_end = s_MDX_load_double(ret, field_data); + + if(ret > std::numeric_limits::max() + || ret < -std::numeric_limits::max() + || str_end == field_data) + { + throw MDX_bad_term("Bad float"); + } + + dest = ret; + + return str_end; +} + +template<> +bool MDX_Value::save(std::string& out, const float& src) +{ + s_MDX_sprintf_append(out, "%.10g", src); + return true; +} + +template<> +const char* MDX_Value::load(double& dest, const char* field_data) +{ + const char* str_end = s_MDX_load_double(dest, field_data); + + if(str_end == field_data) + throw MDX_bad_term("Bad double"); + + return str_end; +} + +template<> +bool MDX_Value::save(std::string& out, const double& src) +{ + s_MDX_sprintf_append(out, "%.10g", src); + return true; +} diff --git a/src/mdx/common/value/mdx_value_object.hpp b/src/mdx/common/value/mdx_value_object.hpp new file mode 100644 index 0000000..96588cd --- /dev/null +++ b/src/mdx/common/value/mdx_value_object.hpp @@ -0,0 +1,83 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_value_object.hpp + * + * \brief Contains code to save/load objects stored in strings + * + */ + +#pragma once +#ifndef MDX_VALUE_OBJECT_HPP +#define MDX_VALUE_OBJECT_HPP + +#include "../mdx_value.h" + +/******************** + * MDX_Value_Object * + ********************/ + +template +struct MDX_Value_Object +{ + static const obj_loader_t s_obj_loader; + static const char* load(typename obj_loader_t::obj_t& dest, const char* field_data); + static bool save(std::string& out, const typename obj_loader_t::obj_t& src); + static bool is_ref(const PGELIST& /*src*/, const PGELIST& /*reference*/) + { + return false; + } +}; + +template +const char* MDX_Value_Object::load(typename obj_loader_t::obj_t& dest, const char* field_data) +{ + dest = typename obj_loader_t::obj_t(); + + std::string object_string; + + const char* next = MDX_load_value(object_string, field_data); + + s_obj_loader.load_object(&dest, object_string.c_str()); + + return next; +} + +template +bool MDX_Value_Object::save(std::string& out, const typename obj_loader_t::obj_t& src) +{ + const typename obj_loader_t::obj_t ref; + std::string object_string; + + if(!s_obj_loader.save_object(object_string, &src, &ref)) + return false; + + MDX_save_value(out, object_string); + + return true; +} + +#endif // #ifndef MDX_VALUE_OBJECT_HPP diff --git a/src/mdx/common/value/mdx_value_objectlist.hpp b/src/mdx/common/value/mdx_value_objectlist.hpp new file mode 100644 index 0000000..67c49ef --- /dev/null +++ b/src/mdx/common/value/mdx_value_objectlist.hpp @@ -0,0 +1,135 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_value_object.hpp + * + * \brief Contains code to save/load lists of objects, where each object is stored in a string + * + */ + +#pragma once +#ifndef MDX_VALUE_OBJECTLIST_HPP +#define MDX_VALUE_OBJECTLIST_HPP + +#include "../mdx_value.h" +#include "../mdx_exception.h" + +/************************ + * MDX_Value_ObjectList * + ************************/ + +template +struct MDX_Value_ObjectList +{ + static const obj_loader_t s_obj_loader; + static const char* load(PGELIST& dest, const char* field_data); + static bool save(std::string& out, const PGELIST& src); + static bool is_ref(const PGELIST& src, const PGELIST& /*reference*/) + { + return (src.size() == 0); + } +}; + +template +const char* MDX_Value_ObjectList::load(PGELIST& dest, const char* field_data) +{ + dest.clear(); + + const char* cur_pos = field_data; + if(*cur_pos != '[') + throw MDX_missing_delimiter('['); + + cur_pos++; + + std::string object_string; + + while(*cur_pos != ']' && *cur_pos != '\0') + { +#ifndef PGE_FILES_QT + dest.emplace_back(); +#else + dest.push_back(typename obj_loader_t::obj_t()); +#endif + + try + { + cur_pos = MDX_load_value(object_string, cur_pos); + cur_pos = MDX_finish_list_item(cur_pos); + + s_obj_loader.load_object(&dest.back(), object_string.c_str()); + } + catch(const MDX_parse_error&) + { + std::throw_with_nested(MDX_bad_array(dest.size())); + } + } + + if(*(cur_pos - 1) == ',') + throw MDX_unexpected_character(']'); + + if(*cur_pos != ']') + throw MDX_missing_delimiter(']'); + + cur_pos++; + + return cur_pos; +} + +template +bool MDX_Value_ObjectList::save(std::string& out, const PGELIST& src) +{ + std::string object_string; + typename obj_loader_t::obj_t ref; + + out.push_back('['); + + for(const auto& s : src) + { + object_string.clear(); + + if(s_obj_loader.save_object(object_string, &s, &ref)) + { + MDX_save_value(out, object_string); + + out.push_back(','); + } + } + + // close the array + if(out.back() == ',') + { + out.back() = ']'; + return true; + } + // nothing was written, remove the '[' + else + { + out.pop_back(); + return false; + } +} + +#endif // #ifndef MDX_VALUE_OBJECTLIST_HPP diff --git a/src/mdx/common/value/mdx_value_qstringlist.cpp b/src/mdx/common/value/mdx_value_qstringlist.cpp new file mode 100644 index 0000000..de03b6a --- /dev/null +++ b/src/mdx/common/value/mdx_value_qstringlist.cpp @@ -0,0 +1,103 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +#ifdef PGE_FILES_QT + +/*! \file mdx_value_qstringlist.cpp + * + * \brief Code to support saving/loading the QStringList (because it is not actually a PGELIST in Qt5) + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include "mdx/common/mdx_value.h" +#include "mdx/common/mdx_exception.h" + +#include "pge_file_lib_globs.h" + +template<> +const char* MDX_Value::load(QStringList& dest, const char* field_data) +{ + dest.clear(); + + const char* cur_pos = field_data; + if(*cur_pos != '[') + throw MDX_missing_delimiter('['); + + cur_pos++; + + std::string got_utf8; + + while(*cur_pos != ']' && *cur_pos != '\0') + { + try + { + cur_pos = MDX_load_value(got_utf8, cur_pos); + cur_pos = MDX_finish_list_item(cur_pos); + } + catch(const MDX_parse_error&) + { + std::throw_with_nested(MDX_bad_array(dest.size())); + } + + dest.push_back(QString::fromStdString(got_utf8)); + } + + if(*(cur_pos - 1) == ',') + throw MDX_unexpected_character(']'); + + if(*cur_pos != ']') + throw MDX_missing_delimiter(']'); + + cur_pos++; + + return cur_pos; +} + +template<> +bool MDX_Value::save(std::string& out, const QStringList& src) +{ + if(src.size() == 0) + return false; + + std::string src_i_utf8; + + out.push_back('['); + + for(const auto& s : src) + { + src_i_utf8 = s.toStdString(); + + MDX_save_value(out, src_i_utf8); + out.push_back(','); + } + + out.back() = ']'; + + return true; +} +#endif // #ifdef PGE_FILES_QT diff --git a/src/mdx/common/value/mdx_value_string.cpp b/src/mdx/common/value/mdx_value_string.cpp new file mode 100644 index 0000000..93c06f0 --- /dev/null +++ b/src/mdx/common/value/mdx_value_string.cpp @@ -0,0 +1,156 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_value_string.cpp + * + * \brief Code to support saving/loading string values + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include "mdx/common/mdx_value.h" +#include "mdx/common/mdx_exception.h" + +#include "pge_file_lib_globs.h" + +template<> +const char* MDX_Value::load(std::string& dest, const char* field_data) +{ + dest.clear(); + + const char* cur_pos = field_data; + if(*cur_pos != '"') + throw MDX_missing_delimiter('"'); + + bool escape = false; + + while(true) + { + const char cur_byte = *(++cur_pos); + + if(cur_byte == '\0') + break; + + if(escape) + { + char escaped; + if(cur_byte == 'n') + escaped = '\n'; + else if(cur_byte == 'r') + escaped = '\r'; + // something like \ " [ , etc + else + escaped = cur_byte; + + dest.push_back(escaped); + + escape = false; + continue; + } + else if(cur_byte == '\\') + { + escape = true; + continue; + } + else if(cur_byte == ';' || cur_byte == ':') + throw MDX_unexpected_character(cur_byte); + else if(cur_byte == '"') + break; + else + { + escape = false; + dest.push_back(cur_byte); + } + } + + if(*cur_pos != '"') + throw MDX_missing_delimiter('"'); + + cur_pos++; + + return cur_pos; +} + +template<> +bool MDX_Value::save(std::string& out, const std::string& src) +{ + out += '"'; + + for(char c : src) + { + switch(c) + { + case '\n': + out += '\\'; + out += 'n'; + break; + case '\r': + out += '\\'; + out += 'r'; + break; + case '\"': + case ';': + case ':': + case '[': + case ']': + case ',': + case '%': + case '\\': + out += '\\'; + out += c; + break; + default: + out += c; + break; + } + } + + out += '"'; + + return true; +} + +#ifdef PGE_FILES_QT +template<> +const char* MDX_Value::load(QString& dest, const char* field_data) +{ + std::string dest_utf8; + + const char* ret = MDX_load_value(dest_utf8, field_data); + dest = QString::fromStdString(dest_utf8); + + return ret; +} + +template<> +bool MDX_Value::save(std::string& out, const QString& src) +{ + std::string src_utf8 = src.toStdString(); + + return MDX_save_value(out, src_utf8); +} +#endif diff --git a/src/mdx/common/value/milo_yip/itoa.h b/src/mdx/common/value/milo_yip/itoa.h new file mode 100644 index 0000000..8dcf156 --- /dev/null +++ b/src/mdx/common/value/milo_yip/itoa.h @@ -0,0 +1,308 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ITOA_ +#define RAPIDJSON_ITOA_ + +// #include "../rapidjson.h" + +// RAPIDJSON_NAMESPACE_BEGIN +namespace milo_yip { + +inline const char* GetDigitsLut() { + static const char cDigitsLut[200] = { + '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' + }; + return cDigitsLut; +} + +inline char* u32toa(uint32_t value, char* buffer) { + // RAPIDJSON_ASSERT(buffer != 0); + + const char* cDigitsLut = GetDigitsLut(); + + if (value < 10000) { + const uint32_t d1 = (value / 100) << 1; + const uint32_t d2 = (value % 100) << 1; + + if (value >= 1000) + *buffer++ = cDigitsLut[d1]; + if (value >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else if (value < 100000000) { + // value = bbbbcccc + const uint32_t b = value / 10000; + const uint32_t c = value % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + else { + // value = aabbbbcccc in decimal + + const uint32_t a = value / 100000000; // 1 to 42 + value %= 100000000; + + if (a >= 10) { + const unsigned i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else + *buffer++ = static_cast('0' + static_cast(a)); + + const uint32_t b = value / 10000; // 0 to 9999 + const uint32_t c = value % 10000; // 0 to 9999 + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + return buffer; +} + +inline char* i32toa(int32_t value, char* buffer) { + // RAPIDJSON_ASSERT(buffer != 0); + uint32_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u32toa(u, buffer); +} + +inline char* u64toa(uint64_t value, char* buffer) { + // RAPIDJSON_ASSERT(buffer != 0); + const char* cDigitsLut = GetDigitsLut(); + const uint64_t kTen8 = 100000000; + const uint64_t kTen9 = kTen8 * 10; + const uint64_t kTen10 = kTen8 * 100; + const uint64_t kTen11 = kTen8 * 1000; + const uint64_t kTen12 = kTen8 * 10000; + const uint64_t kTen13 = kTen8 * 100000; + const uint64_t kTen14 = kTen8 * 1000000; + const uint64_t kTen15 = kTen8 * 10000000; + const uint64_t kTen16 = kTen8 * kTen8; + + if (value < kTen8) { + uint32_t v = static_cast(value); + if (v < 10000) { + const uint32_t d1 = (v / 100) << 1; + const uint32_t d2 = (v % 100) << 1; + + if (v >= 1000) + *buffer++ = cDigitsLut[d1]; + if (v >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (v >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else { + // value = bbbbcccc + const uint32_t b = v / 10000; + const uint32_t c = v % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + } + else if (value < kTen16) { + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + if (value >= kTen15) + *buffer++ = cDigitsLut[d1]; + if (value >= kTen14) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= kTen13) + *buffer++ = cDigitsLut[d2]; + if (value >= kTen12) + *buffer++ = cDigitsLut[d2 + 1]; + if (value >= kTen11) + *buffer++ = cDigitsLut[d3]; + if (value >= kTen10) + *buffer++ = cDigitsLut[d3 + 1]; + if (value >= kTen9) + *buffer++ = cDigitsLut[d4]; + + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + else { + const uint32_t a = static_cast(value / kTen16); // 1 to 1844 + value %= kTen16; + + if (a < 10) + *buffer++ = static_cast('0' + static_cast(a)); + else if (a < 100) { + const uint32_t i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else if (a < 1000) { + *buffer++ = static_cast('0' + static_cast(a / 100)); + + const uint32_t i = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else { + const uint32_t i = (a / 100) << 1; + const uint32_t j = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + *buffer++ = cDigitsLut[j]; + *buffer++ = cDigitsLut[j + 1]; + } + + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + + return buffer; +} + +inline char* i64toa(int64_t value, char* buffer) { + // RAPIDJSON_ASSERT(buffer != 0); + uint64_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u64toa(u, buffer); +} + +} // namespace internal +// RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ITOA_ diff --git a/src/mdx/mdx_gamesave_file.cpp b/src/mdx/mdx_gamesave_file.cpp new file mode 100644 index 0000000..9a26c18 --- /dev/null +++ b/src/mdx/mdx_gamesave_file.cpp @@ -0,0 +1,187 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_savx_file.cpp + * + * \brief Implements defines MDX structures for the save objects and file + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include "save_filedata.h" +#include "pge_file_lib_private.h" + +#include "mdx/common/mdx_file.h" +#include "mdx/common/mdx_macros.h" + +#include "mdx/mdx_gamesave_file.h" + +MDX_SETUP_OBJECT(GamesaveHead, + MDX_FIELD("LV", lives); + MDX_FIELD("HN", hundreds); + MDX_FIELD("CN", coins); + MDX_FIELD("PT", points); + MDX_FIELD("TS", totalStars); + MDX_FIELD("WX", worldPosX); + MDX_FIELD("WY", worldPosY); + MDX_FIELD("HW", last_hub_warp); + MDX_FIELD("HL", last_hub_level_file); + MDX_FIELD("MI", musicID); + MDX_FIELD("MF", musicFile); + MDX_FIELD("GC", gameCompleted); + MDX_FIELD("TI", lvl_path_count); +); + +MDX_SETUP_OBJECT(saveCharState, + MDX_FIELD_NO_SKIP("ID", id); + MDX_FIELD("ST", state); + MDX_FIELD("IT", itemID); + MDX_FIELD("MT", mountType); + MDX_FIELD("MI", mountID); + MDX_FIELD("HL", health); +); + +MDX_SETUP_OBJECT(savePlayerState, + MDX_FIELD_NONNEG_NO_SKIP("ID", characterID); +); + +MDX_SETUP_OBJECT(visibleItem, + MDX_FIELD_NO_SKIP("ID", first); + MDX_FIELD("V", second); +); + +MDX_SETUP_OBJECT(starOnLevel, + MDX_FIELD_NO_SKIP("L", first); + MDX_FIELD("S", second); +); + +MDX_SETUP_OBJECT(saveLevelInfo, + MDX_FIELD_NO_SKIP("L", level_filename); + MDX_FIELD("S", max_stars); + MDX_FIELD("M", max_medals); + MDX_FIELD("MG", medals_got); + MDX_FIELD("MB", medals_best); + MDX_FIELD("E", exits_got); +); + +static const char* MDX_DataSection_load_location(saveUserData::DataSection& s, const char* field_data) +{ + return MDX_load_value(s.location, field_data); +} + +static bool MDX_DataSection_save_location(std::string& out, const saveUserData::DataSection& s) +{ + if((s.location & saveUserData::DATA_VOLATILE_FLAG) != 0) + return false; + + int location_clean = (s.location & saveUserData::DATA_LOCATION_MASK); + + return MDX_save_value(out, location_clean); +} + +#include "pge_x.h" + +template<> +const char* MDX_Value::load(saveUserData::DataEntry& e, const char* field_data) +{ + PGESTRING got; + const char* ret = MDX_load_value(got, field_data); + + PGESTRINGList dp; + PGE_SPLITSTRING(dp, got, "="); + if(dp.size() < 2) + throw MDX_missing_delimiter('='); + e.key = PGE_ReplSTRING(PGEFile::X2STRING(dp[0]), "\\q", "="); + e.value = PGE_ReplSTRING(PGEFile::X2STRING(dp[1]), "\\q", "="); + + return ret; +} + +template<> +bool MDX_Value::save(std::string& out, const saveUserData::DataEntry& src) +{ + std::string sub; + + PGESTRING temp = src.key; + PGE_ReplSTRING_inline(temp, "=", "\\q"); + + MDX_save_value(sub, temp); + + sub += '='; + + temp = src.value; + PGE_ReplSTRING_inline(temp, "=", "\\q"); + + MDX_save_value(sub, temp); + + return MDX_save_value(out, sub); +} + +using DataSection = saveUserData::DataSection; + +MDX_SETUP_OBJECT(DataSection, + MDX_UNIQUE_FIELD("L", MDX_DataSection_load_location, MDX_DataSection_save_location); + MDX_FIELD_NOT_ONLY("SN", name); + MDX_FIELD_NOT_ONLY("LN", location_name); + MDX_FIELD_NOT_ONLY("D", data); +); + +struct MDX_GamesaveFile : MDX_File +{ + MDX_SECTION_SINGLE("SAVE_HEADER", GamesaveHead, head); + + MDX_SECTION("CHARACTERS", saveCharState, charstate); + + MDX_SECTION("CHARACTERS_PER_PLAYERS", savePlayerState, selchar); + + MDX_SECTION("VIZ_LEVELS", visibleItem, vis_level); + + MDX_SECTION("VIZ_PATHS", visibleItem, vis_path); + + MDX_SECTION("VIZ_SCENERY", visibleItem, vis_scene); + + MDX_SECTION("STARS", starOnLevel, star); + + MDX_SECTION("SAVED_LAYERS", savedLayerSaveEntry, saved_layer); + + MDX_SECTION("LEVEL_INFO", saveLevelInfo, level_info); + + MDX_SECTION("USERDATA", DataSection, userdata); + +}; + +bool MDX_load_gamesave(PGE_FileFormats_misc::TextInput& input, const GamesaveLoadCallbacks& callbacks) +{ + std::unique_ptr f(new MDX_GamesaveFile()); + return f->load_file(input, callbacks); +} + +bool MDX_save_gamesave(PGE_FileFormats_misc::TextOutput& output, const GamesaveSaveCallbacks& callbacks) +{ + std::unique_ptr f(new MDX_GamesaveFile()); + return f->save_file(output, callbacks); +} diff --git a/src/mdx/mdx_gamesave_file.h b/src/mdx/mdx_gamesave_file.h new file mode 100644 index 0000000..14d8cc3 --- /dev/null +++ b/src/mdx/mdx_gamesave_file.h @@ -0,0 +1,45 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! + * \file mdx_gamesave_file.h + * \brief Contains data structure definitions for the MDX save loader + */ + +#pragma once +#ifndef MDX_GAMESAVE_FILE_H +#define MDX_GAMESAVE_FILE_H + +#include "pge_file_lib_globs.h" +#include "save_filedata.h" + +bool MDX_load_gamesave(PGE_FileFormats_misc::TextInput& input, const GamesaveLoadCallbacks& callbacks); +bool MDX_load_gamesave(PGE_FileFormats_misc::TextInput &file, GamesaveData &FileData); + +bool MDX_save_gamesave(PGE_FileFormats_misc::TextOutput& output, const GamesaveSaveCallbacks& callbacks); +bool MDX_save_gamesave(PGE_FileFormats_misc::TextOutput &file, const GamesaveData &FileData); + +#endif // #ifndef MDX_GAMESAVE_FILE_H diff --git a/src/mdx/mdx_gamesave_file_rw.cpp b/src/mdx/mdx_gamesave_file_rw.cpp new file mode 100644 index 0000000..1606b50 --- /dev/null +++ b/src/mdx/mdx_gamesave_file_rw.cpp @@ -0,0 +1,341 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_gamesave_file_rw.cpp + * + * \brief Implements MDX functions for loading a gamesave object + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include "mdx/mdx_gamesave_file.h" +#include "mdx/common/mdx_exception.h" +#include "file_formats.h" +#include "pge_file_lib_private.h" +#include "pge_file_lib_globs.h" + +static void s_on_error(void* _FileData, FileFormatsError& err) +{ + GamesaveData& FileData = *reinterpret_cast(_FileData); + + static_cast(FileData.meta) = std::move(err); + FileData.meta.ReadFileValid = false; +} + +static bool s_load_head(void* _FileData, GamesaveHead& dest) +{ + GamesaveData& FileData = *reinterpret_cast(_FileData); + + FileData.lives = dest.lives; + FileData.hundreds = dest.hundreds; + FileData.coins = dest.coins; + FileData.points = dest.points; + FileData.totalStars = dest.totalStars; + FileData.worldPosX = dest.worldPosX; + FileData.worldPosY = dest.worldPosY; + FileData.last_hub_warp = dest.last_hub_warp; + FileData.last_hub_level_file = dest.last_hub_level_file; + FileData.musicID = dest.musicID; + FileData.musicFile = dest.musicFile; + FileData.gameCompleted = dest.gameCompleted; + FileData.lvl_path_count = dest.lvl_path_count; + + return true; +} + +static bool s_save_head(const void* _FileData, GamesaveHead& dest, pge_size_t index) +{ + if(index != 0) + return false; + + const GamesaveData& FileData = *reinterpret_cast(_FileData); + + dest = GamesaveHead(); + + dest.lives = FileData.lives; + dest.hundreds = FileData.hundreds; + dest.coins = FileData.coins; + dest.points = FileData.points; + dest.totalStars = FileData.totalStars; + dest.worldPosX = FileData.worldPosX; + dest.worldPosY = FileData.worldPosY; + dest.last_hub_warp = FileData.last_hub_warp; + dest.last_hub_level_file = FileData.last_hub_level_file; + dest.musicID = FileData.musicID; + dest.musicFile = FileData.musicFile; + dest.gameCompleted = FileData.gameCompleted; + dest.lvl_path_count = FileData.lvl_path_count; + + return true; +} + +static bool s_load_charstate(void* _FileData, saveCharState& obj) +{ + GamesaveData& FileData = *reinterpret_cast(_FileData); + + FileData.characterStates.push_back(obj); + + return true; +} + +static bool s_save_charstate(const void* _FileData, saveCharState& obj, pge_size_t index) +{ + const GamesaveData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.characterStates.size()) + return false; + + obj = FileData.characterStates[index]; + + return true; +} + +static bool s_load_selchar(void* _FileData, savePlayerState& obj) +{ + GamesaveData& FileData = *reinterpret_cast(_FileData); + + FileData.currentCharacter.push_back(obj.characterID); + + return true; +} + +static bool s_save_selchar(const void* _FileData, savePlayerState& obj, pge_size_t index) +{ + const GamesaveData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.currentCharacter.size()) + return false; + + obj.characterID = FileData.currentCharacter[index]; + + return true; +} + +static bool s_load_vis_level(void* _FileData, visibleItem& obj) +{ + GamesaveData& FileData = *reinterpret_cast(_FileData); + + FileData.visibleLevels.push_back(obj); + + return true; +} + +static bool s_save_vis_level(const void* _FileData, visibleItem& obj, pge_size_t index) +{ + const GamesaveData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.visibleLevels.size()) + return false; + + obj = FileData.visibleLevels[index]; + + return true; +} + +static bool s_load_vis_path(void* _FileData, visibleItem& obj) +{ + GamesaveData& FileData = *reinterpret_cast(_FileData); + + FileData.visiblePaths.push_back(obj); + + return true; +} + +static bool s_save_vis_path(const void* _FileData, visibleItem& obj, pge_size_t index) +{ + const GamesaveData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.visiblePaths.size()) + return false; + + obj = FileData.visiblePaths[index]; + + return true; +} + +static bool s_load_vis_scene(void* _FileData, visibleItem& obj) +{ + GamesaveData& FileData = *reinterpret_cast(_FileData); + + FileData.visibleScenery.push_back(obj); + + return true; +} + +static bool s_save_vis_scene(const void* _FileData, visibleItem& obj, pge_size_t index) +{ + const GamesaveData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.visibleScenery.size()) + return false; + + obj = FileData.visibleScenery[index]; + + return true; +} + +static bool s_load_star(void* _FileData, starOnLevel& obj) +{ + GamesaveData& FileData = *reinterpret_cast(_FileData); + + FileData.gottenStars.push_back(obj); + + return true; +} + +static bool s_save_star(const void* _FileData, starOnLevel& obj, pge_size_t index) +{ + const GamesaveData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.gottenStars.size()) + return false; + + obj = FileData.gottenStars[index]; + + return true; +} + +static bool s_load_saved_layer(void* _FileData, savedLayerSaveEntry& obj) +{ + GamesaveData& FileData = *reinterpret_cast(_FileData); + + FileData.savedLayers.push_back(obj); + + return true; +} + +static bool s_save_saved_layer(const void* _FileData, savedLayerSaveEntry& obj, pge_size_t index) +{ + const GamesaveData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.savedLayers.size()) + return false; + + obj = FileData.savedLayers[index]; + + return true; +} + +static bool s_load_level_info(void* _FileData, saveLevelInfo& obj) +{ + GamesaveData& FileData = *reinterpret_cast(_FileData); + + FileData.levelInfo.push_back(obj); + + return true; +} + +static bool s_save_level_info(const void* _FileData, saveLevelInfo& obj, pge_size_t index) +{ + const GamesaveData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.levelInfo.size()) + return false; + + obj = FileData.levelInfo[index]; + + return true; +} + +static bool s_load_userdata(void* _FileData, saveUserData::DataSection& obj) +{ + GamesaveData& FileData = *reinterpret_cast(_FileData); + + FileData.userData.store.push_back(obj); + + return true; +} + +static bool s_save_userdata(const void* _FileData, saveUserData::DataSection& obj, pge_size_t index) +{ + const GamesaveData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.userData.store.size()) + return false; + + obj = FileData.userData.store[index]; + + return true; +} + +bool MDX_load_gamesave(PGE_FileFormats_misc::TextInput &file, GamesaveData &FileData) +{ + FileData = GamesaveData(); + FileData.lives = 3; + + //Add path data + PGESTRING filePath = file.getFilePath(); + if(!IsEmpty(filePath)) + { + PGE_FileFormats_misc::FileInfo in_1(filePath); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); + } + + FileData.meta.untitled = false; + FileData.meta.modified = false; + FileData.meta.ReadFileValid = true; + + GamesaveLoadCallbacks callbacks; + + callbacks.on_error = s_on_error; + + callbacks.load_head = s_load_head; + callbacks.load_charstate = s_load_charstate; + callbacks.load_selchar = s_load_selchar; + callbacks.load_vis_level = s_load_vis_level; + callbacks.load_vis_path = s_load_vis_path; + callbacks.load_vis_scene = s_load_vis_scene; + callbacks.load_star = s_load_star; + callbacks.load_saved_layer = s_load_saved_layer; + callbacks.load_level_info = s_load_level_info; + callbacks.load_userdata = s_load_userdata; + + callbacks.userdata = reinterpret_cast(&FileData); + + return MDX_load_gamesave(file, callbacks); +} + +bool MDX_save_gamesave(PGE_FileFormats_misc::TextOutput &file, const GamesaveData &FileData) +{ + GamesaveSaveCallbacks callbacks; + + callbacks.save_head = s_save_head; + callbacks.save_charstate = s_save_charstate; + callbacks.save_selchar = s_save_selchar; + callbacks.save_vis_level = s_save_vis_level; + callbacks.save_vis_path = s_save_vis_path; + callbacks.save_vis_scene = s_save_vis_scene; + callbacks.save_star = s_save_star; + callbacks.save_saved_layer = s_save_saved_layer; + callbacks.save_level_info = s_save_level_info; + callbacks.save_userdata = s_save_userdata; + + callbacks.userdata = reinterpret_cast(&FileData); + + return MDX_save_gamesave(file, callbacks); +} diff --git a/src/mdx/mdx_level_file.cpp b/src/mdx/mdx_level_file.cpp new file mode 100644 index 0000000..9032f74 --- /dev/null +++ b/src/mdx/mdx_level_file.cpp @@ -0,0 +1,692 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_level_file.cpp + * + * \brief Implements defines MDX structures for the level objects and file + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include "lvl_filedata.h" +#include "pge_file_lib_private.h" + +#include "mdx/common/mdx_file.h" +#include "mdx/common/mdx_macros.h" +#include "mdx/mdx_meta_objects.hpp" + +#include "mdx/mdx_level_file.h" + +MDX_SETUP_OBJECT(LevelHead, + MDX_FIELD("TL", LevelName); //Level Title + MDX_FIELD_NONNEG("SZ", stars); //Starz number + MDX_FIELD("DL", open_level_on_fail); //Open level on fail + MDX_FIELD("DE", open_level_on_fail_warpID); //Open level's warpID on fail + MDX_FIELD("NO", player_names_overrides); //Overrides of player names + MDX_FIELD("XTRA", custom_params); //Level-wide Extra settings + MDX_FIELD("CPID", configPackId); //Config pack ID string + MDX_FIELD("EFL", engineFeatureLevel); //Engine feature level value + MDX_FIELD("MUS", music_files); // Level-wide list of external music files +); + +MDX_SETUP_OBJECT(LevelSection, + MDX_FIELD_NONNEG_NOT_ONLY("SC", id); //Section ID + MDX_FIELD("L", size_left); //Left side + MDX_FIELD("R", size_right);//Right side + MDX_FIELD("T", size_top); //Top side + MDX_FIELD("B", size_bottom);//Bottom side + MDX_FIELD("MZ", music_id);//Built-in music ID + MDX_FIELD("MF", music_file); //External music file path + MDX_FIELD("BG", background);//Built-in background ID + MDX_FIELD("LT", lighting_value);//Lighting value + MDX_FIELD("ME", music_file_idx); //External music entry from level list + MDX_FIELD("CS", wrap_h);//Connect sides horizontally + MDX_FIELD("CSV", wrap_v);//Connect sides vertically + MDX_FIELD("OE", OffScreenEn);//Offscreen exit + MDX_FIELD("SR", lock_left_scroll);//Right-way scroll only (No Turn-back) + MDX_FIELD("SL", lock_right_scroll);//Left-way scroll only (No Turn-forward) + MDX_FIELD("SD", lock_up_scroll);//Down-way scroll only (No Turn-forward) + MDX_FIELD("SU", lock_down_scroll);//Up-way scroll only (No Turn-forward) + MDX_FIELD("UW", underwater);//Underwater bit + MDX_FIELD("XTRA", custom_params);//Custom JSON data tree +); + +MDX_SETUP_OBJECT(PlayerPoint, + MDX_FIELD_NO_SKIP("ID", id); //ID of player point + MDX_FIELD("X", x); + MDX_FIELD("Y", y); + MDX_FIELD("D", direction); +); + +MDX_SETUP_OBJECT(LevelBlock, + MDX_FIELD_NO_SKIP("ID", id); //Block ID + MDX_FIELD("X", x); // Position X + MDX_FIELD("Y", y); //Position Y + MDX_FIELD_NONNEG("W", w); //Width + MDX_FIELD_NONNEG("H", h); //Height + MDX_FIELD("CN", npc_id); //Contains (coins/NPC) + MDX_FIELD("IV", invisible); //Invisible + MDX_FIELD("SL", slippery); //Slippery + MDX_FIELD("LR", layer); //Layer name + MDX_FIELD("ED", event_destroy); //Destroy event slot + MDX_FIELD("EH", event_hit); //Hit event slot + MDX_FIELD("EE", event_emptylayer); //Hit event slot + MDX_FIELD("S1", special_data); //Special value 1 + MDX_FIELD("S2", special_data2); //Special value 2 + MDX_FIELD("AS", autoscale);//Enable auto-Scaling + MDX_FIELD("GXN", gfx_name); //38A GFX-Name + MDX_FIELD("GXX", gfx_dx); //38A graphics extend x + MDX_FIELD("GXY", gfx_dy); //38A graphics extend y + MDX_FIELD("CS", npc_special_value); //Special value for contained NPC + MDX_FIELD("MA", motion_ai_id); //Motion AI type + MDX_FIELD_XTRA();//Custom JSON data tree +); + +MDX_SETUP_OBJECT(LevelBGO, + MDX_FIELD_NO_SKIP("ID", id); //BGO ID + MDX_FIELD("X", x); //X Position + MDX_FIELD("Y", y); //Y Position + MDX_FIELD("ZO", z_offset); //Z Offset + MDX_FIELD("ZP", z_mode); //Z Position + MDX_FIELD("SP", smbx64_sp); //SMBX64 Sorting priority + MDX_FIELD("LR", layer); //Layer name + MDX_FIELD("GXX", gfx_dx); //38A graphics extend x + MDX_FIELD("GXY", gfx_dy); //38A graphics extend y + MDX_FIELD_XTRA();//Custom JSON data tree +); + +MDX_SETUP_OBJECT(LevelNPC, + MDX_FIELD_NO_SKIP("ID", id); //NPC ID + MDX_FIELD("X", x); //X position + MDX_FIELD("Y", y); //Y position + MDX_FIELD("D", direct); //Direction + MDX_FIELD("CN", contents); //Contents of container-NPC + MDX_FIELD("S1", special_data); //Special value 1 + MDX_FIELD("S2", special_data2); //Special value 2 + MDX_FIELD("GE", generator); //Generator + MDX_FIELD("GT", generator_type); //Generator type + MDX_FIELD("GD", generator_direct); //Generator direction + MDX_FIELD_NONNEG("GM", generator_period); //Generator period + MDX_FIELD("MG", msg); //Message + MDX_FIELD("FD", friendly); //Friendly + MDX_FIELD("NM", nomove); //Don't move + MDX_FIELD("BS", is_boss); //Enable boss mode! + MDX_FIELD("LR", layer); //Layer + MDX_FIELD("LA", attach_layer); //Attach Layer + MDX_FIELD("EA", event_activate); //Event slot "Activated" + MDX_FIELD("ED", event_die); //Event slot "Death/Take/Destroy" + MDX_FIELD("ET", event_talk); //Event slot "Talk" + MDX_FIELD("EE", event_emptylayer); //Event slot "Layer is empty" + MDX_FIELD("EG", event_grab);//Event slot "On grab" + MDX_FIELD("EO", event_touch);//Event slot "On touch" + MDX_FIELD("EF", event_nextframe);//Evemt slot "Trigger every frame" + MDX_FIELD("SV", send_id_to_variable); //Send ID to variable + MDX_FIELD("GXN", gfx_name); //38A GFX-Name + MDX_FIELD("GXX", gfx_dx); //38A graphics extend x + MDX_FIELD("GXY", gfx_dy); //38A graphics extend y + MDX_FIELD("OW", override_width); //Override width + MDX_FIELD("OH", override_height); //Override height + MDX_FIELD("GAS", gfx_autoscale); //Autoscale GFX on size override + MDX_FIELD("WGT", wings_type); //38A: Wings type + MDX_FIELD("WGS", wings_style); //38A: Wings style + MDX_FIELD("GA", generator_custom_angle); //Generator custom angle + MDX_FIELD_NONNEG("GB", generator_branches); //Generator number of branches + MDX_FIELD("GR", generator_angle_range); //Generator angle range + MDX_FIELD("GS", generator_initial_speed); //Generator custom initial speed + MDX_FIELD_XTRA();//Custom JSON data tree +); + +MDX_SETUP_OBJECT(LevelPhysEnv, + MDX_FIELD_NONNEG("ET", env_type); //Environment type + MDX_FIELD_NO_SKIP("X", x); //X position + MDX_FIELD("Y", y); //Y position + MDX_FIELD_NONNEG("W", w); //Width + MDX_FIELD_NONNEG("H", h); //Height + MDX_FIELD("LR", layer); //Layer + MDX_FIELD("FR", friction); //Friction + MDX_FIELD("AD", accel_direct); //Custom acceleration direction + MDX_FIELD("AC", accel); //Custom acceleration + MDX_FIELD("MV", max_velocity); //Maximal velocity + MDX_FIELD("EO", touch_event); //Touch event/script + MDX_FIELD_XTRA();//Custom JSON data tree +); + +MDX_SETUP_OBJECT(LevelDoor, + MDX_FIELD_NO_SKIP("IX", ix); //Input point + MDX_FIELD("IY", iy); //Input point + MDX_FIELD("OX", ox); //Output point + MDX_FIELD("OY", oy); //Output point + MDX_FIELD("IL", length_i); //Length of entrance (input) point + MDX_FIELD("OL", length_o); //Length of exit (output) point + MDX_FIELD("IH", height_i); //Height of entrance (input) point + MDX_FIELD("OH", height_o); //Height of exit (output) point + MDX_FIELD_NONNEG("DT", type); //Input point + MDX_FIELD_NONNEG("ID", idirect); //Input direction + MDX_FIELD_NONNEG("OD", odirect); //Output direction + MDX_FIELD("WX", world_x); //Target world map point + MDX_FIELD("WY", world_y); //Target world map point + MDX_FIELD("LF", lname); //Target level file + MDX_FIELD_NONNEG("LI", warpto); //Target level file's input warp + MDX_FIELD("ET", lvl_i); //Level Entrance + MDX_FIELD("EX", lvl_o); //Level exit + MDX_FIELD_NONNEG("SL", stars); //Stars limit + MDX_FIELD("SM", stars_msg); //Message about stars/leeks + MDX_FIELD("NV", novehicles); //No Vehicles + MDX_FIELD("SH", star_num_hide); //Don't show stars number + MDX_FIELD("AI", allownpc); //Allow grabbed items + MDX_FIELD("LC", locked); //Door is locked + MDX_FIELD("LB", need_a_bomb); //Door is blocked, need bomb to unlock + MDX_FIELD("HS", hide_entering_scene); //Don't show entering scene + MDX_FIELD("AL", allownpc_interlevel); //Allow NPC's inter-level + MDX_FIELD("SR", special_state_required); //Required a special state to enter + MDX_FIELD("STR", stood_state_required); //Required a stood state to enter + MDX_FIELD("TE", transition_effect); //Transition effect + MDX_FIELD("PT", cannon_exit); //Cannon exit + MDX_FIELD("PS", cannon_exit_speed); //Cannon exit speed + MDX_FIELD("LR", layer); //Layer + MDX_FIELD("EE", event_enter); //On-Enter event slot + MDX_FIELD("EEX", event_exit); //On-Exit event slot + MDX_FIELD("TW", two_way); //Two-way warp + MDX_FIELD_XTRA();//Custom JSON data tree +); + +MDX_SETUP_OBJECT(LevelLayer, + MDX_FIELD_NO_SKIP("LR", name); //Layer name + MDX_FIELD("HD", hidden); //Hidden + MDX_FIELD("LC", locked); //Locked +); + +static const char* MDX_LevelEvent_load_controls(LevelSMBX64Event& event, const char* field_data) +{ + PGELIST controls; + + const char* next = MDX_load_value(controls, field_data); + +#ifndef PGE_FILES_QT + controls.resize(12); +#else + if(controls.size() > 12) + controls.erase(controls.begin() + 12, controls.end()); + while(controls.size() < 12) + controls.push_back(false); +#endif + + // SMBX64-only + event.ctrl_up = controls[0]; + event.ctrl_down = controls[1]; + event.ctrl_left = controls[2]; + event.ctrl_right = controls[3]; //-V112 + event.ctrl_run = controls[4]; + event.ctrl_jump = controls[5]; + event.ctrl_drop = controls[6]; + event.ctrl_start = controls[7]; + event.ctrl_altrun = controls[8]; + event.ctrl_altjump = controls[9]; + // SMBX64-only end + // SMBX-38A begin + event.ctrls_enable = controls[10]; + event.ctrl_lock_keyboard = controls[11]; + // SMBX-38A end + + return next; +} + +static bool MDX_LevelEvent_save_controls(std::string& out, const LevelSMBX64Event& event) +{ + PGELIST controls; + + controls.push_back(event.ctrl_up); + controls.push_back(event.ctrl_down); + controls.push_back(event.ctrl_left); + controls.push_back(event.ctrl_right); + controls.push_back(event.ctrl_run); + controls.push_back(event.ctrl_jump); + controls.push_back(event.ctrl_drop); + controls.push_back(event.ctrl_start); + controls.push_back(event.ctrl_altrun); + controls.push_back(event.ctrl_altjump); + controls.push_back(event.ctrls_enable); + controls.push_back(event.ctrl_lock_keyboard); + + bool addArray = false; + + for(bool control : controls) + { + if(control) + addArray = true; + } + + if(!addArray) + return false; + + return MDX_save_value(out, controls); +} + +static const char* MDX_LevelEvent_load_autoscroll_path(LevelEvent_Sets& set, const char* field_data) +{ + PGELIST arr; + + const char* next = MDX_load_value(arr, field_data); + + if(arr.size() % 4) + throw MDX_bad_term("Invalid Section Autoscroll path data contains non-multiple 4 entries"); + + set.autoscroll_path.clear(); + + for(pge_size_t pe = 0; pe < arr.size(); pe += 4) + { + LevelEvent_Sets::AutoScrollStopPoint stop; + stop.x = arr[pe + 0]; + stop.y = arr[pe + 1]; + stop.type = (int)arr[pe + 2]; + stop.speed = arr[pe + 3]; + set.autoscroll_path.push_back(stop); + } + + return next; +} + +static bool MDX_LevelEvent_save_autoscroll_path(std::string& out, const LevelEvent_Sets& set) +{ + PGELIST arr; + + for(const LevelEvent_Sets::AutoScrollStopPoint& stop : set.autoscroll_path) + { + arr.push_back(stop.x); + arr.push_back(stop.y); + arr.push_back((long)stop.type); + arr.push_back(stop.speed); + } + + return MDX_save_value(out, arr); +} + +MDX_SETUP_OBJECT(LevelEvent_Sets, + MDX_FIELD_NONNEG_NOT_ONLY("ID", id); + MDX_FIELD("SL", position_left); + MDX_FIELD("ST", position_top); + MDX_FIELD("SB", position_bottom); + MDX_FIELD("SR", position_right); + MDX_FIELD("SXX", expression_pos_x); + MDX_FIELD("SYX", expression_pos_y); + MDX_FIELD("SWX", expression_pos_w); + MDX_FIELD("SHX", expression_pos_h); + MDX_FIELD("MI", music_id); + MDX_FIELD("MF", music_file); + MDX_FIELD("ME", music_file_idx); + MDX_FIELD("BG", background_id); + MDX_FIELD("AS", autoscrol); + MDX_FIELD_NONNEG("AST", autoscroll_style); + MDX_UNIQUE_FIELD("ASP", MDX_LevelEvent_load_autoscroll_path, MDX_LevelEvent_save_autoscroll_path); + MDX_FIELD("AX", autoscrol_x); + MDX_FIELD("AY", autoscrol_y); + MDX_FIELD("AXX", expression_autoscrool_x); + MDX_FIELD("AYX", expression_autoscrool_y); +); +MDX_ENABLE_SUB_LIST(LevelEvent_Sets); + +MDX_SETUP_OBJECT(LevelEvent_MoveLayer, + // only write the move-layer if name is non-empty + MDX_FIELD("LN", name); + MDX_FIELD_NOT_ONLY("SX", speed_x); + MDX_FIELD_NOT_ONLY("SXX", expression_x); + MDX_FIELD_NOT_ONLY("SY", speed_y); + MDX_FIELD_NOT_ONLY("SYX", expression_y); + MDX_FIELD_NONNEG_NOT_ONLY("MW", way); +); +MDX_ENABLE_SUB_STRUCT(LevelEvent_MoveLayer); + +MDX_SETUP_OBJECT(LevelEvent_SpawnNPC, + MDX_FIELD_NONNEG_NO_SKIP("ID", id); + MDX_FIELD("SX", x); + MDX_FIELD("SXX", expression_x); + MDX_FIELD("SY", y); + MDX_FIELD("SYX", expression_y); + MDX_FIELD("SSX", speed_x); + MDX_FIELD("SSXX", expression_sx); + MDX_FIELD("SSY", speed_y); + MDX_FIELD("SSYX", expression_sy); + MDX_FIELD_NONNEG("SSS", special); +); +MDX_ENABLE_SUB_LIST(LevelEvent_SpawnNPC); + +MDX_SETUP_OBJECT(LevelEvent_SpawnEffect, + MDX_FIELD_NONNEG_NO_SKIP("ID", id); + MDX_FIELD("SX", x); + MDX_FIELD("SXX", expression_x); + MDX_FIELD("SY", y); + MDX_FIELD("SYX", expression_y); + MDX_FIELD("SSX", speed_x); + MDX_FIELD("SSXX", expression_sx); + MDX_FIELD("SSY", speed_y); + MDX_FIELD("SSYX", expression_sy); + MDX_FIELD("FP", fps); + MDX_FIELD("TTL", max_life_time); + MDX_FIELD("GT", gravity); +); +MDX_ENABLE_SUB_LIST(LevelEvent_SpawnEffect); + +MDX_SETUP_OBJECT(LevelEvent_UpdateVariable, + MDX_FIELD_NO_SKIP("N", name); + MDX_FIELD_NO_SKIP("V", newval); +); +MDX_ENABLE_SUB_LIST(LevelEvent_UpdateVariable); + +static inline const char* MDX_LevelEvent_load_legacy_SM_SB(LevelSMBX64Event& event, const char* field_data, long LevelEvent_Sets::* field) +{ + PGELIST arr; + + const char* next = MDX_load_value(arr, field_data); + + bool ignore_me = (event.sets.size() >= 1 && !event.sets[0]._pgefl_mdx_priv_legacy_field); + + for(pge_size_t q = 0; q < arr.size(); q++) + { + // validate input even if it will be ignored + long got; + const char* end = MDX_load_value(got, arr[q].c_str()); + if(end != arr[q].c_str() + arr[q].size()) + throw MDX_unexpected_character(*end); + + // goes up to 21 because this is the legacy PGE-X parser initializes the event with that many section settings, then bounds at event.sets.size() + if(ignore_me || q >= 21) + continue; + + // q cannot be greater than event.sets.size() + if(q == event.sets.size()) + event.sets.push_back(LevelEvent_Sets()); + + auto &s = event.sets[q]; + s.id = static_cast(q); + s._pgefl_mdx_priv_legacy_field = true; + s.*field = got; + } + + if(!ignore_me) + { + // reset all higher sections + for(pge_size_t q = arr.size(); q < event.sets.size(); q++) + event.sets[q].*field = LevelEvent_Sets::LESet_Nothing; + } + + return next; +} + +static const char* MDX_LevelEvent_load_legacy_SM(LevelSMBX64Event& event, const char* field_data) +{ + return MDX_LevelEvent_load_legacy_SM_SB(event, field_data, &LevelEvent_Sets::music_id); +} + +static const char* MDX_LevelEvent_load_legacy_SB(LevelSMBX64Event& event, const char* field_data) +{ + return MDX_LevelEvent_load_legacy_SM_SB(event, field_data, &LevelEvent_Sets::background_id); +} + +static const char* MDX_LevelEvent_load_legacy_SS(LevelSMBX64Event& event, const char* field_data) +{ + PGELIST arr; + + const char* next = MDX_load_value(arr, field_data); + + bool ignore_me = (event.sets.size() >= 1 && !event.sets[0]._pgefl_mdx_priv_legacy_field); + + for(pge_size_t q = 0; q < arr.size(); q++) + { + // validate input even if it will be ignored + long got[4]; + const char* next = arr[q].c_str(); + + for(int i = 0; i < 4; i++) + { + next = MDX_load_value(got[i], next); + + if(i == 3) + continue; + + if(*next != ',') + throw MDX_missing_delimiter(','); + + ++next; + } + + if(next != arr[q].c_str() + arr[q].size()) + throw MDX_unexpected_character(*next); + + // goes up to 21 because this is the legacy PGE-X parser initializes the event with that many section settings, then bounds at event.sets.size() + if(ignore_me || q >= 21) + continue; + + // q cannot be greater than event.sets.size() + if(q == event.sets.size()) + event.sets.push_back(LevelEvent_Sets()); + + auto &s = event.sets[q]; + s.id = static_cast(q); + s._pgefl_mdx_priv_legacy_field = true; + + s.position_left = got[0]; + s.position_top = got[1]; + s.position_bottom = got[2]; + s.position_right = got[3]; + } + + if(!ignore_me) + { + // reset all higher sections + for(pge_size_t q = arr.size(); q < event.sets.size(); q++) + { + event.sets[q].position_left = LevelEvent_Sets::LESet_Nothing; + event.sets[q].position_top = 0; + event.sets[q].position_bottom = 0; + event.sets[q].position_right = 0; + } + } + + return next; +} + +MDX_SETUP_OBJECT(LevelSMBX64Event, + MDX_FIELD_NO_SKIP("ET", name); //Event Title + MDX_FIELD("MG", msg); //Event Message + MDX_FIELD_NONNEG("SD", sound_id); //Play Sound ID + MDX_FIELD_NONNEG("EG", end_game); //End game algorithm + MDX_FIELD("LH", layers_hide); //Hide layers + MDX_FIELD("LS", layers_show); //Show layers + MDX_FIELD("LT", layers_toggle); //Toggle layers + //Legacy values (without SMBX-38A values support) + MDX_UNIQUE_FIELD("SM", MDX_LevelEvent_load_legacy_SM, nullptr); //Switch music + MDX_UNIQUE_FIELD("SB", MDX_LevelEvent_load_legacy_SB, nullptr); //Switch background + MDX_UNIQUE_FIELD("SS", MDX_LevelEvent_load_legacy_SS, nullptr); //Section Size + //------------------- + //New values (with SMBX-38A values support) + MDX_FIELD("SSS", sets); //Section settings in new format + //------------------- + //---SMBX-38A entries----- + MDX_FIELD("MLA", moving_layers); //NPC's to spawn + MDX_FIELD("SNPC", spawn_npc); //NPC's to spawn + MDX_FIELD("SEF", spawn_effects); //Effects to spawn + MDX_FIELD("UV", update_variable); //Variables to update + MDX_FIELD("TSCR", trigger_script); //Trigger script + MDX_FIELD_NONNEG("TAPI", trigger_api_id); //Trigger script + MDX_NESTED_FIELD("TMR", timer_def, enable); //Enable timer + MDX_NESTED_FIELD_NONNEG("TMC", timer_def, count); //Count of timer units + MDX_NESTED_FIELD("TMI", timer_def, interval); //Interval of timer tick + MDX_NESTED_FIELD_NONNEG("TMD", timer_def, count_dir); //Direction of count + MDX_NESTED_FIELD("TMV", timer_def, show); //Show timer on screen + //------------------- + MDX_FIELD("TE", trigger); //Trigger event + MDX_FIELD_NONNEG("TD", trigger_timer); //Trigger delay + MDX_FIELD("DS", nosmoke); //Disable smoke + MDX_FIELD_NONNEG("AU", autostart); //Auto start + MDX_FIELD("AUC", autostart_condition); //Auto start condition + MDX_UNIQUE_FIELD("PC", MDX_LevelEvent_load_controls, MDX_LevelEvent_save_controls); + MDX_FIELD("ML", movelayer); //Move layer + MDX_FIELD("MX", layer_speed_x); //Layer motion speed X + MDX_FIELD("MY", layer_speed_y); //Layer motion speed Y + MDX_FIELD("AS", scroll_section); //Autoscroll section ID + MDX_FIELD("AX", move_camera_x); //Autoscroll speed X + MDX_FIELD("AY", move_camera_y); //Autoscroll speed Y +); + +MDX_SETUP_OBJECT(LevelVariable, + MDX_FIELD_NO_SKIP("N", name); //Variable name + MDX_FIELD("V", value); //Variable value + MDX_FIELD("G", is_global); //Is global variable +); + +MDX_SETUP_OBJECT(LevelArray, + MDX_FIELD_NO_SKIP("N", name); //Array name +); + +MDX_SETUP_OBJECT(LevelScript, + MDX_FIELD_NO_SKIP("N", name); //Variable name + MDX_FIELD("L", language); //Variable name + MDX_FIELD("S", script); //Script text +); + +template<> +const char* MDX_Value::load(LevelItemSetup38A::ItemType& dest, const char* field_data) +{ + int got = 0; + const char* ret = MDX_load_value(got, field_data); + + if(got < LevelItemSetup38A::UNKNOWN || got >= LevelItemSetup38A::ITEM_TYPE_MAX) + throw MDX_bad_term("Bad type"); + + dest = (LevelItemSetup38A::ItemType)got; + + return ret; +} + +template<> +bool MDX_Value::save(std::string& out, const LevelItemSetup38A::ItemType& src) +{ + return MDX_save_value(out, (int)src); +} + +template<> +const char* MDX_Value::load(LevelItemSetup38A::Entry& e, const char* field_data) +{ + std::string got; + const char* ret = MDX_load_value(got, field_data); + + const char* const str_data_start = got.c_str(); + const char* str_data = str_data_start; + + str_data = MDX_load_value(e.key, str_data); + + if(e.key < 0 || *str_data_start == '-') + throw MDX_bad_term("Negative value"); + + if(*str_data != '=') + throw MDX_missing_delimiter('='); + + str_data++; + + str_data = MDX_load_value(e.value, str_data); + + if((size_t)(str_data - str_data_start) != got.size()) + throw MDX_missing_delimiter('"'); + + return ret; +} + +template<> +bool MDX_Value::save(std::string& out, const LevelItemSetup38A::Entry& src) +{ + // For total safety, we'd make a substring, store to those, then output that. But we know this won't include any escape chars, so we'll do it manually to avoid extra allocs. +#if 0 + std::string sub; +#else + out += '"'; + std::string& sub = out; +#endif + + MDX_save_value(sub, src.key); + sub += '='; + MDX_save_value(sub, src.value); + +#if 0 + return MDX_save_value(out, sub); +#else + out += '"'; + return true; +#endif +} + +MDX_SETUP_OBJECT(LevelItemSetup38A, + MDX_FIELD_NONNEG_NO_SKIP("T", type); //Type of item + MDX_FIELD_NONNEG("ID", id); + MDX_FIELD("D", data); //Variable value +); + +struct MDX_LevelFile : MDX_File +{ + MDX_SECTION_SINGLE("HEAD", LevelHead, head); + + MDX_SECTION_SINGLE("META_SYS_CRASH", CrashData, crash_data); + + MDX_SECTION("META_BOOKMARKS", Bookmark, bookmark); + + MDX_SECTION("SECTION", LevelSection, section); + + MDX_SECTION("STARTPOINT", PlayerPoint, startpoint); + + MDX_SECTION("BLOCK", LevelBlock, block); + + MDX_SECTION("BGO", LevelBGO, bgo); + + MDX_SECTION("NPC", LevelNPC, npc); + + MDX_SECTION("PHYSICS", LevelPhysEnv, phys); + + MDX_SECTION("DOORS", LevelDoor, warp); + + MDX_SECTION("LAYERS", LevelLayer, layer); + + MDX_SECTION("EVENTS_CLASSIC", LevelSMBX64Event, event); + + MDX_SECTION("VARIABLES", LevelVariable, var); + + MDX_SECTION("ARRAYS", LevelArray, arr); + + MDX_SECTION("SCRIPTS", LevelScript, script); + + MDX_SECTION("CUSTOM_ITEMS_38A", LevelItemSetup38A, levelitem38a); +}; + +bool MDX_load_level(PGE_FileFormats_misc::TextInput& input, const LevelLoadCallbacks& callbacks) +{ + std::unique_ptr f(new MDX_LevelFile()); + return f->load_file(input, callbacks); +} + +bool MDX_save_level(PGE_FileFormats_misc::TextOutput& output, const LevelSaveCallbacks& callbacks) +{ + std::unique_ptr f(new MDX_LevelFile()); + return f->save_file(output, callbacks); +} diff --git a/src/mdx/mdx_level_file.h b/src/mdx/mdx_level_file.h new file mode 100644 index 0000000..0e08e63 --- /dev/null +++ b/src/mdx/mdx_level_file.h @@ -0,0 +1,50 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! + * \file mdx_level_file.h + * \brief Contains data structure definitions for the MDX level loader + */ + +#pragma once +#ifndef MDX_LEVEL_FILE_H +#define MDX_LEVEL_FILE_H + +#include "pge_file_lib_globs.h" +#include "lvl_filedata.h" + +bool MDX_load_level(PGE_FileFormats_misc::TextInput& input, const LevelLoadCallbacks& callbacks); +bool MDX_load_level(PGE_FileFormats_misc::TextInput &file, LevelData &FileData); +bool MDX_load_level_header(PGE_FileFormats_misc::TextInput &file, LevelData &FileData); + +bool MDX_save_level(PGE_FileFormats_misc::TextOutput& output, const LevelSaveCallbacks& callbacks); +bool MDX_save_level(PGE_FileFormats_misc::TextOutput &file, const LevelData &FileData); + +LevelLoadCallbacks PGEFL_make_load_callbacks(LevelData& target); +LevelLoadCallbacks PGEFL_make_header_load_callbacks(LevelData& target); +LevelSaveCallbacks PGEFL_make_save_callbacks(const LevelData& target); + +#endif // #ifndef MDX_LEVEL_FILE_H diff --git a/src/mdx/mdx_level_file_rw.cpp b/src/mdx/mdx_level_file_rw.cpp new file mode 100644 index 0000000..ce9eb51 --- /dev/null +++ b/src/mdx/mdx_level_file_rw.cpp @@ -0,0 +1,712 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_level_file_rw.cpp + * + * \brief Implements defines MDX functions for loading a level object + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include "mdx/mdx_level_file.h" +#include "mdx/common/mdx_exception.h" +#include "file_formats.h" +#include "pge_file_lib_private.h" +#include "pge_file_lib_globs.h" + +static void s_on_error(void* _FileData, FileFormatsError& err) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + + static_cast(FileData.meta) = std::move(err); + FileData.meta.ReadFileValid = false; +} + +static bool s_load_head(void* _FileData, LevelHead& dest) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + + FileData.LevelName = dest.LevelName; + FileData.stars = dest.stars; + FileData.open_level_on_fail = dest.open_level_on_fail; + FileData.open_level_on_fail_warpID = dest.open_level_on_fail_warpID; + FileData.player_names_overrides = dest.player_names_overrides; + FileData.custom_params = dest.custom_params; + FileData.meta.configPackId = dest.configPackId; + FileData.meta.engineFeatureLevel = dest.engineFeatureLevel; + FileData.music_files = dest.music_files; + + FileData.music_overrides = dest.music_overrides; + + FileData.meta.RecentFormat = dest.RecentFormat; + FileData.meta.RecentFormatVersion = dest.RecentFormatVersion; + + if(FileData.meta.RecentFormat == LevelData::SMBX38A) + FileData.meta.smbx64strict = false; + else if(FileData.meta.RecentFormat == LevelData::SMBX64) + FileData.meta.smbx64strict = true; + + return true; +} + +static bool s_load_head_only(void* _FileData, LevelHead& dest) +{ + s_load_head(_FileData, dest); + throw PGE_FileFormats_misc::callback_interrupt(); +} + +static bool s_save_head(const void* _FileData, LevelHead& dest, pge_size_t index) +{ + if(index != 0) + return false; + + const LevelData& FileData = *reinterpret_cast(_FileData); + + dest = LevelHead(); + + dest.LevelName = FileData.LevelName; + dest.stars = FileData.stars; + dest.open_level_on_fail = FileData.open_level_on_fail; + dest.open_level_on_fail_warpID = FileData.open_level_on_fail_warpID; + dest.player_names_overrides = FileData.player_names_overrides; + dest.custom_params = FileData.custom_params; + dest.configPackId = FileData.meta.configPackId; + dest.engineFeatureLevel = FileData.meta.engineFeatureLevel; + dest.music_files = FileData.music_files; + + return true; +} + +static bool s_load_bookmark(void* _FileData, Bookmark& dest) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + FileData.metaData.bookmarks.push_back(std::move(dest)); + + return true; +} + +static bool s_save_bookmark(const void* _FileData, Bookmark& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.metaData.bookmarks.size()) + return false; + + obj = FileData.metaData.bookmarks[index]; + + return true; +} + +static bool s_load_crash_data(void* _FileData, CrashData& dest) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + FileData.metaData.crash = dest; + FileData.metaData.crash.used = true; + + return true; +} + +static bool s_save_crash_data(const void* _FileData, CrashData& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(!FileData.metaData.crash.used || index != 0) + return false; + + obj = FileData.metaData.crash; + + return true; +} + +static bool s_load_section(void* _FileData, LevelSection& dest) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + + dest.PositionX = dest.size_left - 10; + dest.PositionY = dest.size_top - 10; + + //add captured value into array + pge_size_t sections_count = FileData.sections.size(); + + if(dest.id < 0 || dest.id > 1000) + throw PGE_FileFormats_misc::callback_error("Invalid section ID"); + + if(dest.id >= static_cast(sections_count)) + { + pge_size_t needToAdd = static_cast(dest.id) - (FileData.sections.size() - 1); + while(needToAdd > 0) + { + LevelSection dummySct = FileFormats::CreateLvlSection(); + dummySct.id = (int)FileData.sections.size(); + FileData.sections.push_back(std::move(dummySct)); + needToAdd--; + } + } + + FileData.sections[static_cast(dest.id)] = std::move(dest); + + return true; +} + +static bool s_save_section(const void* _FileData, LevelSection& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.sections.size()) + return false; + + obj = FileData.sections[index]; + + return true; +} + +static bool s_load_startpoint(void* _FileData, PlayerPoint& player) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + + //add captured value into array + bool found = false; + pge_size_t q = 0; + pge_size_t playersCount = FileData.players.size(); + for(q = 0; q < playersCount; q++) + { + if(FileData.players[q].id == player.id) + { + found = true; + break; + } + } + + PlayerPoint sz = FileFormats::CreateLvlPlayerPoint(player.id); + if(player.w == 0) + player.w = sz.w; + if(player.h == 0) + player.h = sz.h; + + if(found) + FileData.players[q] = std::move(player); + else + FileData.players.push_back(std::move(player)); + + return true; +} + +static bool s_save_startpoint(const void* _FileData, PlayerPoint& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.players.size()) + return false; + + obj = FileData.players[index]; + + return true; +} + +static bool s_load_block(void* _FileData, LevelBlock& block) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + + block.meta.array_id = FileData.blocks_array_id++; + block.meta.index = static_cast(FileData.blocks.size()); + FileData.blocks.push_back(std::move(block)); + + return true; +} + +static bool s_save_block(const void* _FileData, LevelBlock& block, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.blocks.size()) + return false; + + block = FileData.blocks[index]; + + return true; +} + +static bool s_load_bgo(void* _FileData, LevelBGO& bgodata) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + + bgodata.meta.array_id = FileData.bgo_array_id++; + bgodata.meta.index = static_cast(FileData.bgo.size()); + FileData.bgo.push_back(std::move(bgodata)); + + return true; +} + +static bool s_save_bgo(const void* _FileData, LevelBGO& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.bgo.size()) + return false; + + obj = FileData.bgo[index]; + + return true; +} + +static bool s_load_npc(void* _FileData, LevelNPC& npcdata) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + + npcdata.meta.array_id = FileData.npc_array_id++; + npcdata.meta.index = static_cast(FileData.npc.size()); + FileData.npc.push_back(std::move(npcdata)); + + return true; +} + +static bool s_save_npc(const void* _FileData, LevelNPC& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.npc.size()) + return false; + + obj = FileData.npc[index]; + + return true; +} + +static bool s_load_phys(void* _FileData, LevelPhysEnv& physiczone) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + + physiczone.meta.array_id = FileData.physenv_array_id++; + physiczone.meta.index = static_cast(FileData.physez.size()); + FileData.physez.push_back(std::move(physiczone)); + + return true; +} + +static bool s_save_phys(const void* _FileData, LevelPhysEnv& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.physez.size()) + return false; + + obj = FileData.physez[index]; + + return true; +} + +static bool s_load_warp(void* _FileData, LevelDoor& door) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + + door.isSetIn = (!door.lvl_i); + door.isSetOut = (!door.lvl_o || (door.lvl_i)); + + if(!door.isSetIn && door.isSetOut) + { + door.ix = door.ox; + door.iy = door.oy; + } + + if(!door.isSetOut && door.isSetIn) + { + door.ox = door.ix; + door.oy = door.iy; + } + + door.meta.array_id = FileData.doors_array_id++; + door.meta.index = static_cast(FileData.doors.size()); + FileData.doors.push_back(std::move(door)); + + return true; +} + +static bool s_save_warp(const void* _FileData, LevelDoor& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.doors.size()) + return false; + + obj = FileData.doors[index]; + + return true; +} + +static bool s_load_layer(void* _FileData, LevelLayer& layer) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + + //add captured value into array + bool found = false; + pge_size_t q = 0; + for(q = 0; q < FileData.layers.size(); q++) + { + if(FileData.layers[q].name == layer.name) + { + found = true; + break; + } + } + + if(found) + { + layer.meta.array_id = FileData.layers[q].meta.array_id; + FileData.layers[q] = std::move(layer); + } + else + { + layer.meta.array_id = FileData.layers_array_id++; + FileData.layers.push_back(std::move(layer)); + } + + return true; +} + +static bool s_save_layer(const void* _FileData, LevelLayer& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.layers.size()) + return false; + + obj = FileData.layers[index]; + + return true; +} + +static bool s_load_event(void* _FileData, LevelSMBX64Event& event) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + + // create a new padded list of the event sets + PGELIST padded_sets; + for(LevelEvent_Sets& sectionSet : event.sets) + { + if( + ((sectionSet.id < 0) || (sectionSet.id >= static_cast(padded_sets.size()))) + )//Append sections + { + if(sectionSet.id < 0 || sectionSet.id > 1000) + throw PGE_FileFormats_misc::callback_error("Invalid section ID"); + + long last = static_cast(padded_sets.size() - 1); + + while(sectionSet.id >= static_cast(padded_sets.size())) + { + LevelEvent_Sets set; + set.id = last; + padded_sets.push_back(set); + last++; + } + } + + padded_sets[static_cast(sectionSet.id)] = std::move(sectionSet); + } + + event.sets = std::move(padded_sets); + + //add captured value into array + bool found = false; + pge_size_t q = 0; + + for(q = 0; q < FileData.events.size(); q++) + { + if(FileData.events[q].name == event.name) + { + found = true; + break; + } + } + + if(found) + { + event.meta.array_id = FileData.events[q].meta.array_id; + FileData.events[q] = std::move(event); + } + else + { + event.meta.array_id = FileData.events_array_id++; + FileData.events.push_back(std::move(event)); + } + + return true; +} + +static bool s_save_event(const void* _FileData, LevelSMBX64Event& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.events.size()) + return false; + + obj = FileData.events[index]; + + return true; +} + +static bool s_load_var(void* _FileData, LevelVariable& v) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + FileData.variables.push_back(std::move(v)); + + return true; +} + +static bool s_save_var(const void* _FileData, LevelVariable& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.variables.size()) + return false; + + obj = FileData.variables[index]; + + return true; +} + +static bool s_load_arr(void* _FileData, LevelArray& a) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + FileData.arrays.push_back(std::move(a)); + + return true; +} + +static bool s_save_arr(const void* _FileData, LevelArray& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.arrays.size()) + return false; + + obj = FileData.arrays[index]; + + return true; +} + +static bool s_load_script(void* _FileData, LevelScript& s) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + FileData.scripts.push_back(std::move(s)); + + return true; +} + +static bool s_save_script(const void* _FileData, LevelScript& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.scripts.size()) + return false; + + obj = FileData.scripts[index]; + + return true; +} + +static bool s_load_levelitem38a(void* _FileData, LevelItemSetup38A& customcfg38A) +{ + if(customcfg38A.type < 0) + throw PGE_FileFormats_misc::callback_error("Invalid 38A ID"); + + LevelData& FileData = *reinterpret_cast(_FileData); + FileData.custom38A_configs.push_back(std::move(customcfg38A)); + + return true; +} + +static bool s_save_levelitem38a(const void* _FileData, LevelItemSetup38A& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.custom38A_configs.size()) + return false; + + obj = FileData.custom38A_configs[index]; + + return true; +} + +static bool s_load_music_override(void* _FileData, LevelData::MusicOverrider& src) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + FileData.music_overrides.push_back(std::move(src)); + + return true; +} + +static bool s_save_music_override(const void* _FileData, LevelData::MusicOverrider& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.music_overrides.size()) + return false; + + obj = FileData.music_overrides[index]; + + return true; +} + +static bool s_load_junk_line(void* _FileData, PGESTRING& src) +{ + LevelData& FileData = *reinterpret_cast(_FileData); + FileData.unsupported_38a_lines.push_back(std::move(src)); + + return true; +} + +static bool s_save_junk_line(const void* _FileData, PGESTRING& obj, pge_size_t index) +{ + const LevelData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.unsupported_38a_lines.size()) + return false; + + obj = FileData.unsupported_38a_lines[index]; + + return true; +} + +bool MDX_load_level(PGE_FileFormats_misc::TextInput &file, LevelData &FileData) +{ + FileFormats::CreateLevelData(FileData); + FileData.meta.RecentFormat = LevelData::PGEX; + + //Add path data + PGESTRING filePath = file.getFilePath(); + if(!IsEmpty(filePath)) + { + PGE_FileFormats_misc::FileInfo in_1(filePath); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); + } + + FileData.meta.untitled = false; + FileData.meta.modified = false; + FileData.meta.ReadFileValid = true; + + return MDX_load_level(file, PGEFL_make_load_callbacks(FileData)); +} + +LevelLoadCallbacks PGEFL_make_load_callbacks(LevelData& target) +{ + LevelLoadCallbacks callbacks; + + callbacks.on_error = s_on_error; + + callbacks.load_head = s_load_head; + callbacks.load_bookmark = s_load_bookmark; + callbacks.load_crash_data = s_load_crash_data; + callbacks.load_section = s_load_section; + callbacks.load_startpoint = s_load_startpoint; + callbacks.load_block = s_load_block; + callbacks.load_bgo = s_load_bgo; + callbacks.load_npc = s_load_npc; + callbacks.load_phys = s_load_phys; + callbacks.load_warp = s_load_warp; + callbacks.load_layer = s_load_layer; + callbacks.load_event = s_load_event; + callbacks.load_var = s_load_var; + callbacks.load_arr = s_load_arr; + callbacks.load_script = s_load_script; + callbacks.load_levelitem38a = s_load_levelitem38a; + callbacks.load_music_override = s_load_music_override; + callbacks.load_junk_line = s_load_junk_line; + + callbacks.userdata = reinterpret_cast(&target); + + return callbacks; +} + +bool MDX_load_level_header(PGE_FileFormats_misc::TextInput &file, LevelData &FileData) +{ + LevelHead h; + s_load_head(&FileData, h); + FileData.meta.RecentFormat = LevelData::PGEX; + + //Add path data + PGESTRING filePath = file.getFilePath(); + if(!IsEmpty(filePath)) + { + PGE_FileFormats_misc::FileInfo in_1(filePath); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); + } + + FileData.meta.untitled = false; + FileData.meta.modified = false; + FileData.meta.ReadFileValid = true; + + return MDX_load_level(file, PGEFL_make_header_load_callbacks(FileData)); +} + +LevelLoadCallbacks PGEFL_make_header_load_callbacks(LevelData& target) +{ + LevelLoadCallbacks callbacks; + + callbacks.on_error = s_on_error; + + callbacks.load_head = s_load_head_only; + + callbacks.userdata = reinterpret_cast(&target); + + return callbacks; +} + +LevelSaveCallbacks PGEFL_make_save_callbacks(const LevelData& target) +{ + LevelSaveCallbacks callbacks; + + callbacks.save_head = s_save_head; + callbacks.save_bookmark = s_save_bookmark; + callbacks.save_crash_data = s_save_crash_data; + callbacks.save_section = s_save_section; + callbacks.save_startpoint = s_save_startpoint; + callbacks.save_block = s_save_block; + callbacks.save_bgo = s_save_bgo; + callbacks.save_npc = s_save_npc; + callbacks.save_phys = s_save_phys; + callbacks.save_warp = s_save_warp; + callbacks.save_layer = s_save_layer; + callbacks.save_event = s_save_event; + callbacks.save_var = s_save_var; + callbacks.save_arr = s_save_arr; + callbacks.save_script = s_save_script; + callbacks.save_levelitem38a = s_save_levelitem38a; + callbacks.save_music_override = s_save_music_override; + callbacks.save_junk_line = s_save_junk_line; + + callbacks.userdata = reinterpret_cast(&target); + + return callbacks; +} + +bool MDX_save_level(PGE_FileFormats_misc::TextOutput &file, const LevelData &FileData) +{ + return MDX_save_level(file, PGEFL_make_save_callbacks(FileData)); +} diff --git a/src/mdx/mdx_meta_file.cpp b/src/mdx/mdx_meta_file.cpp new file mode 100644 index 0000000..ab4a038 --- /dev/null +++ b/src/mdx/mdx_meta_file.cpp @@ -0,0 +1,59 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_meta_file.cpp + * + * \brief Implements defines MDX structures for the meta objects and file + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include "meta_filedata.h" +#include "pge_file_lib_private.h" + +#include "mdx/common/mdx_file.h" +#include "mdx/common/mdx_macros.h" +#include "mdx/mdx_meta_objects.hpp" + +#include "mdx/mdx_meta_file.h" + +struct MDX_MetaFile : MDX_File +{ + MDX_SECTION("META_BOOKMARKS", Bookmark, bookmark); +}; + +bool MDX_load_meta(PGE_FileFormats_misc::TextInput& input, const MetaLoadCallbacks& callbacks) +{ + std::unique_ptr f(new MDX_MetaFile()); + return f->load_file(input, callbacks); +} + +bool MDX_save_meta(PGE_FileFormats_misc::TextOutput& output, const MetaSaveCallbacks& callbacks) +{ + std::unique_ptr f(new MDX_MetaFile()); + return f->save_file(output, callbacks); +} diff --git a/src/mdx/mdx_meta_file.h b/src/mdx/mdx_meta_file.h new file mode 100644 index 0000000..ace6261 --- /dev/null +++ b/src/mdx/mdx_meta_file.h @@ -0,0 +1,46 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! + * \file mdx_meta_file.h + * \brief Contains data structure definitions for the MDX meta (bookmarks) loader + */ + +#pragma once +#ifndef MDX_META_FILE_H +#define MDX_META_FILE_H + +#include "pge_file_lib_globs.h" +#include "meta_filedata.h" + +bool MDX_load_meta(PGE_FileFormats_misc::TextInput& input, const MetaLoadCallbacks& callbacks); +bool MDX_load_meta(PGE_FileFormats_misc::TextInput &file, MetaData &FileData); +bool MDX_load_meta_header(PGE_FileFormats_misc::TextInput &file, MetaData &FileData); + +bool MDX_save_meta(PGE_FileFormats_misc::TextOutput& output, const MetaSaveCallbacks& callbacks); +bool MDX_save_meta(PGE_FileFormats_misc::TextOutput &file, const MetaData &FileData); + +#endif // #ifndef MDX_META_FILE_H diff --git a/src/mdx/mdx_meta_file_rw.cpp b/src/mdx/mdx_meta_file_rw.cpp new file mode 100644 index 0000000..7d89999 --- /dev/null +++ b/src/mdx/mdx_meta_file_rw.cpp @@ -0,0 +1,93 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_level_file_rw.cpp + * + * \brief Implements defines MDX functions for loading a level object + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include "mdx/mdx_meta_file.h" +#include "mdx/common/mdx_exception.h" +#include "file_formats.h" +#include "pge_file_lib_private.h" +#include "pge_file_lib_globs.h" + +static void s_on_error(void* _FileData, FileFormatsError& err) +{ + MetaData& FileData = *reinterpret_cast(_FileData); + + static_cast(FileData.meta) = std::move(err); + FileData.meta.ReadFileValid = false; +} + +static bool s_load_bookmark(void* _FileData, Bookmark& dest) +{ + MetaData& FileData = *reinterpret_cast(_FileData); + FileData.bookmarks.push_back(std::move(dest)); + + return true; +} + +static bool s_save_bookmark(const void* _FileData, Bookmark& obj, pge_size_t index) +{ + const MetaData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.bookmarks.size()) + return false; + + obj = FileData.bookmarks[index]; + + return true; +} + +bool MDX_load_meta(PGE_FileFormats_misc::TextInput &file, MetaData &FileData) +{ + FileData = MetaData(); + + MetaLoadCallbacks callbacks; + + callbacks.on_error = s_on_error; + + callbacks.load_bookmark = s_load_bookmark; + + callbacks.userdata = reinterpret_cast(&FileData); + + return MDX_load_meta(file, callbacks); +} + +bool MDX_save_meta(PGE_FileFormats_misc::TextOutput &file, const MetaData &FileData) +{ + MetaSaveCallbacks callbacks; + + callbacks.save_bookmark = s_save_bookmark; + + callbacks.userdata = reinterpret_cast(&FileData); + + return MDX_save_meta(file, callbacks); +} diff --git a/src/mdx/mdx_meta_objects.hpp b/src/mdx/mdx_meta_objects.hpp new file mode 100644 index 0000000..0975aa5 --- /dev/null +++ b/src/mdx/mdx_meta_objects.hpp @@ -0,0 +1,61 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +#pragma once +#ifndef MDX_META_OBJECTS_HPP +#define MDX_META_OBJECTS_HPP + +/*! \file mdx_meta_objects.hpp + * + * \brief Implements shared MDX structures for all types of files + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include "meta_filedata.h" +#include "pge_file_lib_private.h" + +#include "mdx/common/mdx_file.h" +#include "mdx/common/mdx_macros.h" + +MDX_SETUP_OBJECT(Bookmark, + MDX_FIELD("BM", bookmarkName); //Bookmark name + MDX_FIELD_NO_SKIP("X", x); // Position X + MDX_FIELD("Y", y); // Position Y +); + +MDX_SETUP_OBJECT(CrashData, + MDX_FIELD("UT", untitled); //Untitled + MDX_FIELD("MD", modifyed); //Modyfied + MDX_FIELD("FF", fmtID); //Recent File format + MDX_FIELD("FV", fmtVer); //Recent File format version + MDX_FIELD("N", filename); //Filename + MDX_FIELD("P", path); //Path + MDX_FIELD("FP", fullPath); //Full file Path +); + +#endif // MDX_META_OBJECTS_HPP diff --git a/src/mdx/mdx_utils.h b/src/mdx/mdx_utils.h new file mode 100644 index 0000000..2ba0054 --- /dev/null +++ b/src/mdx/mdx_utils.h @@ -0,0 +1,48 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! + * \file mdx_utils.h + * \brief Contains save and load utility functions + */ + +#pragma once +#ifndef MDX_UTILS_H +#define MDX_UTILS_H + +const char* MDX_load_int(int& dest, const char* field_data); +const char* MDX_load_long(long& dest, const char* field_data); +const char* MDX_load_longlong(long long& dest, const char* field_data); + +const char* MDX_load_uint(int& dest, const char* field_data); +const char* MDX_load_ulong(long& dest, const char* field_data); +const char* MDX_load_ulonglong(long long& dest, const char* field_data); + +const char* MDX_load_uint(unsigned int& dest, const char* field_data); +const char* MDX_load_ulong(unsigned long& dest, const char* field_data); +const char* MDX_load_ulonglong(unsigned long long& dest, const char* field_data); + +#endif // #ifndef MDX_UTILS_H diff --git a/src/mdx/mdx_world_file.cpp b/src/mdx/mdx_world_file.cpp new file mode 100644 index 0000000..5933969 --- /dev/null +++ b/src/mdx/mdx_world_file.cpp @@ -0,0 +1,160 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_world_file.cpp + * + * \brief Implements defines MDX structures for the world objects and file + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include "wld_filedata.h" +#include "pge_file_lib_private.h" + +#include "mdx/common/mdx_file.h" +#include "mdx/common/mdx_macros.h" +#include "mdx/mdx_meta_objects.hpp" + +#include "mdx/mdx_world_file.h" + +MDX_SETUP_OBJECT(WorldHead, + MDX_FIELD("TL", EpisodeTitle); //Episode Title + MDX_FIELD("DC", nocharacter); //Disabled characters + MDX_FIELD("IT", IntroLevel_file); //Intro level + MDX_FIELD("GO", GameOverLevel_file); //Game Over level + MDX_FIELD("HB", HubStyledWorld); //Hub Styled + MDX_FIELD("RL", restartlevel); //Restart level on fail + MDX_FIELD("SZ", stars); //Starz number + MDX_FIELD("CD", authors); //Credits list + MDX_FIELD("CM", authors_music); //Credits scene background music + MDX_FIELD("SSS", starsShowPolicy); //Per-level stars count showing policy + MDX_FIELD("XTRA", custom_params); //World-wide Extra settings + MDX_FIELD("CPID", configPackId);//Config pack ID string + MDX_FIELD("EFL", engineFeatureLevel); //Engine feature level value +); + +MDX_SETUP_OBJECT(WorldTerrainTile, + MDX_FIELD_NO_SKIP("ID", id); //Tile ID + MDX_FIELD("X", x); //X Position + MDX_FIELD("Y", y); //Y Position + MDX_FIELD_XTRA();//Custom JSON data tree +); + +MDX_SETUP_OBJECT(WorldScenery, + MDX_FIELD_NO_SKIP("ID", id); //Scenery ID + MDX_FIELD("X", x); //X Position + MDX_FIELD("Y", y); //Y Position + MDX_FIELD_XTRA();//Custom JSON data tree +); + +MDX_SETUP_OBJECT(WorldPathTile, + MDX_FIELD_NO_SKIP("ID", id); //Path ID + MDX_FIELD("X", x); //X Position + MDX_FIELD("Y", y); //Y Position + MDX_FIELD_XTRA();//Custom JSON data tree +); + +MDX_SETUP_OBJECT(WorldMusicBox, + MDX_FIELD_NO_SKIP("ID", id); //MISICBOX ID + MDX_FIELD("X", x); //X Position + MDX_FIELD("Y", y); //X Position + MDX_FIELD("MF", music_file); //Custom music file + MDX_FIELD_XTRA();//Custom JSON data tree +); + +MDX_SETUP_OBJECT(WorldAreaRect, + MDX_FIELD_NO_SKIP("F", flags); //Flags + MDX_FIELD("X", x); //X Position + MDX_FIELD("Y", y); //X Position + MDX_FIELD_NONNEG("W", w); //Width + MDX_FIELD_NONNEG("H", h); //Height + + // unused stuff + MDX_FIELD("MI", music_id); //MUSICBOX ID + MDX_FIELD("MF", music_file); //Custom music file + MDX_FIELD("LR", layer); + MDX_FIELD("EB", eventBreak); + MDX_FIELD("EW", eventWarp); + MDX_FIELD("EA", eventAnchor); + MDX_FIELD("ET", eventTouch); + MDX_FIELD("TP", eventTouchPolicy); + MDX_FIELD_XTRA();//Custom JSON data tree +); + +MDX_SETUP_OBJECT(WorldLevelTile, + MDX_FIELD_NO_SKIP("ID", id); //LEVEL IMAGE ID + MDX_FIELD("X", x); //X Position + MDX_FIELD("Y", y); //X Position + MDX_FIELD("LF", lvlfile); //Target level file + MDX_FIELD("LT", title); //Level title + MDX_FIELD("EI", entertowarp); //Entrance Warp ID (if 0 - start level from default points) + MDX_FIELD("ET", top_exit); //Open top path on exit type + MDX_FIELD("EL", left_exit); //Open left path on exit type + MDX_FIELD("ER", right_exit); //Open right path on exit type + MDX_FIELD("EB", bottom_exit); //Open bottom path on exit type + MDX_FIELD("WX", gotox); //Goto world map X + MDX_FIELD("WY", gotoy); //Goto world map Y + MDX_FIELD("AV", alwaysVisible); //Always visible + MDX_FIELD("SP", gamestart); //Is Game start point + MDX_FIELD("BP", pathbg); //Path background + MDX_FIELD("BG", bigpathbg); //Big path background + MDX_FIELD("SSS", starsShowPolicy); // Stars count showing policy + MDX_FIELD_XTRA();//Custom JSON data tree +); + +struct MDX_WorldFile : MDX_File +{ + MDX_SECTION_SINGLE("HEAD", WorldHead, head); + + MDX_SECTION_SINGLE("META_SYS_CRASH", CrashData, crash_data); + + MDX_SECTION("META_BOOKMARKS", Bookmark, bookmark); + + MDX_SECTION("TILES", WorldTerrainTile, tile); + + MDX_SECTION("SCENERY", WorldScenery, scene); + + MDX_SECTION("PATHS", WorldPathTile, path); + + MDX_SECTION("MUSICBOXES", WorldMusicBox, music); + + MDX_SECTION("AREARECTS", WorldAreaRect, arearect); + + MDX_SECTION("LEVELS", WorldLevelTile, level); +}; + +bool MDX_load_world(PGE_FileFormats_misc::TextInput& input, const WorldLoadCallbacks& callbacks) +{ + std::unique_ptr f(new MDX_WorldFile()); + return f->load_file(input, callbacks); +} + +bool MDX_save_world(PGE_FileFormats_misc::TextOutput& output, const WorldSaveCallbacks& callbacks) +{ + std::unique_ptr f(new MDX_WorldFile()); + return f->save_file(output, callbacks); +} diff --git a/src/mdx/mdx_world_file.h b/src/mdx/mdx_world_file.h new file mode 100644 index 0000000..a7e23d2 --- /dev/null +++ b/src/mdx/mdx_world_file.h @@ -0,0 +1,50 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! + * \file mdx_world_file.h + * \brief Contains data structure definitions for the MDX level loader + */ + +#pragma once +#ifndef MDX_WORLD_FILE_H +#define MDX_WORLD_FILE_H + +#include "pge_file_lib_globs.h" +#include "wld_filedata.h" + +bool MDX_load_world(PGE_FileFormats_misc::TextInput& input, const WorldLoadCallbacks& callbacks); +bool MDX_load_world(PGE_FileFormats_misc::TextInput &file, WorldData &FileData); +bool MDX_load_world_header(PGE_FileFormats_misc::TextInput &file, WorldData &FileData); + +bool MDX_save_world(PGE_FileFormats_misc::TextOutput& output, const WorldSaveCallbacks& callbacks); +bool MDX_save_world(PGE_FileFormats_misc::TextOutput &file, const WorldData &FileData); + +WorldLoadCallbacks PGEFL_make_load_callbacks(WorldData& target); +WorldLoadCallbacks PGEFL_make_header_load_callbacks(WorldData& target); +WorldSaveCallbacks PGEFL_make_save_callbacks(const WorldData& target); + +#endif // #ifndef MDX_LEVEL_FILE_H diff --git a/src/mdx/mdx_world_file_rw.cpp b/src/mdx/mdx_world_file_rw.cpp new file mode 100644 index 0000000..49e3d67 --- /dev/null +++ b/src/mdx/mdx_world_file_rw.cpp @@ -0,0 +1,524 @@ +/* + * PGE File Library - a library to process file formats, part of Moondust project + * + * Copyright (c) 2014-2025 Vitaly Novichkov + * + * The MIT License (MIT) + * + * 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. + */ + +/*! \file mdx_world_file_rw.cpp + * + * \brief Implements MDX functions for loading a world object + * + * This is a new implementation but supports precisely the same format as PGE-X + * + */ + +#include "mdx/mdx_world_file.h" +#include "mdx/common/mdx_exception.h" +#include "file_formats.h" +#include "pge_file_lib_private.h" +#include "pge_file_lib_globs.h" + +static void s_on_error(void* _FileData, FileFormatsError& err) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + + static_cast(FileData.meta) = std::move(err); + FileData.meta.ReadFileValid = false; +} + +static bool s_load_head(void* _FileData, WorldHead& dest) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + + FileData.EpisodeTitle = dest.EpisodeTitle; + FileData.nocharacter = dest.nocharacter; + FileData.IntroLevel_file = dest.IntroLevel_file; + FileData.GameOverLevel_file = dest.GameOverLevel_file; + FileData.HubStyledWorld = dest.HubStyledWorld; + FileData.restartlevel = dest.restartlevel; + FileData.stars = dest.stars; + FileData.authors = dest.authors; + FileData.authors_music = dest.authors_music; + FileData.starsShowPolicy = dest.starsShowPolicy; + FileData.custom_params = dest.custom_params; + FileData.meta.configPackId = dest.configPackId; + FileData.meta.engineFeatureLevel = dest.engineFeatureLevel; + FileData.meta.RecentFormat = dest.RecentFormat; + FileData.meta.RecentFormatVersion = dest.RecentFormatVersion; + + // unpack nocharacter array for some loaders + FileData.charactersToS64(); + + if(FileData.meta.RecentFormat == LevelData::SMBX38A) + FileData.meta.smbx64strict = false; + else if(FileData.meta.RecentFormat == LevelData::SMBX64) + FileData.meta.smbx64strict = true; + + return true; +} + +static bool s_load_head_only(void* _FileData, WorldHead& dest) +{ + s_load_head(_FileData, dest); + throw PGE_FileFormats_misc::callback_interrupt(); +} + +static bool s_save_head(const void* _FileData, WorldHead& dest, pge_size_t index) +{ + if(index != 0) + return false; + + const WorldData& FileData = *reinterpret_cast(_FileData); + + dest = WorldHead(); + + dest.EpisodeTitle = FileData.EpisodeTitle; + dest.nocharacter = FileData.nocharacter; + dest.IntroLevel_file = FileData.IntroLevel_file; + dest.GameOverLevel_file = FileData.GameOverLevel_file; + dest.HubStyledWorld = FileData.HubStyledWorld; + dest.restartlevel = FileData.restartlevel; + dest.stars = FileData.stars; + dest.authors = FileData.authors; + dest.authors_music = FileData.authors_music; + dest.starsShowPolicy = FileData.starsShowPolicy; + dest.custom_params = FileData.custom_params; + dest.configPackId = FileData.meta.configPackId; + dest.engineFeatureLevel = FileData.meta.engineFeatureLevel; + + // clear no-character array if unnecessary + bool any_no_character = false; + for(bool b : dest.nocharacter) + { + if(b) + { + any_no_character = true; + break; + } + } + + if(!any_no_character) + dest.nocharacter.clear(); + + return true; +} + +static bool s_load_bookmark(void* _FileData, Bookmark& dest) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + FileData.metaData.bookmarks.push_back(std::move(dest)); + + return true; +} + +static bool s_save_bookmark(const void* _FileData, Bookmark& obj, pge_size_t index) +{ + const WorldData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.metaData.bookmarks.size()) + return false; + + obj = FileData.metaData.bookmarks[index]; + + return true; +} + +static bool s_load_crash_data(void* _FileData, CrashData& dest) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + FileData.metaData.crash = dest; + FileData.metaData.crash.used = true; + + return true; +} + +static bool s_save_crash_data(const void* _FileData, CrashData& obj, pge_size_t index) +{ + const WorldData& FileData = *reinterpret_cast(_FileData); + + if(!FileData.metaData.crash.used || index != 0) + return false; + + obj = FileData.metaData.crash; + + return true; +} + +static bool s_load_tile(void* _FileData, WorldTerrainTile& obj) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + + obj.meta.array_id = FileData.tile_array_id++; + obj.meta.index = static_cast(FileData.tiles.size()); + FileData.tiles.push_back(obj); + + return true; +} + +static bool s_save_tile(const void* _FileData, WorldTerrainTile& obj, pge_size_t index) +{ + const WorldData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.tiles.size()) + return false; + + obj = FileData.tiles[index]; + + return true; +} + +static bool s_load_scene(void* _FileData, WorldScenery& obj) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + + obj.meta.array_id = FileData.scene_array_id++; + obj.meta.index = static_cast(FileData.scenery.size()); + FileData.scenery.push_back(obj); + + return true; +} + +static bool s_save_scene(const void* _FileData, WorldScenery& obj, pge_size_t index) +{ + const WorldData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.scenery.size()) + return false; + + obj = FileData.scenery[index]; + + return true; +} + +static bool s_load_path(void* _FileData, WorldPathTile& obj) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + + obj.meta.array_id = FileData.path_array_id++; + obj.meta.index = static_cast(FileData.paths.size()); + FileData.paths.push_back(obj); + + return true; +} + +static bool s_save_path(const void* _FileData, WorldPathTile& obj, pge_size_t index) +{ + const WorldData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.paths.size()) + return false; + + obj = FileData.paths[index]; + + return true; +} + +static bool s_load_music(void* _FileData, WorldMusicBox& obj) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + + obj.meta.array_id = FileData.musicbox_array_id++; + obj.meta.index = static_cast(FileData.music.size()); + FileData.music.push_back(obj); + + return true; +} + +static bool s_save_music(const void* _FileData, WorldMusicBox& obj, pge_size_t index) +{ + const WorldData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.music.size()) + return false; + + obj = FileData.music[index]; + + return true; +} + +static bool s_load_arearect(void* _FileData, WorldAreaRect& obj) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + + obj.meta.array_id = FileData.arearect_array_id++; + obj.meta.index = static_cast(FileData.arearects.size()); + FileData.arearects.push_back(obj); + + return true; +} + +static bool s_save_arearect(const void* _FileData, WorldAreaRect& obj, pge_size_t index) +{ + const WorldData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.arearects.size()) + return false; + + obj = FileData.arearects[index]; + + return true; +} + +static bool s_load_level(void* _FileData, WorldLevelTile& obj) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + + obj.meta.array_id = FileData.level_array_id++; + obj.meta.index = static_cast(FileData.levels.size()); + FileData.levels.push_back(obj); + + return true; +} + +static bool s_save_level(const void* _FileData, WorldLevelTile& obj, pge_size_t index) +{ + const WorldData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.levels.size()) + return false; + + obj = FileData.levels[index]; + + return true; +} + +static bool s_load_layer38a(void* _FileData, WorldLayer& obj) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + + obj.meta.array_id = FileData.layers_array_id++; + obj.meta.index = static_cast(FileData.layers.size()); + FileData.layers.push_back(obj); + + return true; +} + +static bool s_save_layer38a(const void* _FileData, WorldLayer& obj, pge_size_t index) +{ + const WorldData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.layers.size()) + return false; + + obj = FileData.layers[index]; + + return true; +} + +static bool s_load_event38a(void* _FileData, WorldEvent38A& obj) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + + obj.meta.array_id = FileData.events38A_array_id++; + obj.meta.index = static_cast(FileData.events38A.size()); + FileData.events38A.push_back(obj); + + return true; +} + +static bool s_save_event38a(const void* _FileData, WorldEvent38A& obj, pge_size_t index) +{ + const WorldData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.events38A.size()) + return false; + + obj = FileData.events38A[index]; + + return true; +} + +static bool s_load_config38a(void* _FileData, WorldItemSetup38A& obj) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + + FileData.custom38A_configs.push_back(obj); + + return true; +} + +static bool s_save_config38a(const void* _FileData, WorldItemSetup38A& obj, pge_size_t index) +{ + const WorldData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.custom38A_configs.size()) + return false; + + obj = FileData.custom38A_configs[index]; + + return true; +} + +static bool s_load_junk_line(void* _FileData, PGESTRING& src) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + FileData.unsupported_38a_lines.push_back(std::move(src)); + + return true; +} + +static bool s_save_junk_line(const void* _FileData, PGESTRING& obj, pge_size_t index) +{ + const WorldData& FileData = *reinterpret_cast(_FileData); + + if(index >= FileData.unsupported_38a_lines.size()) + return false; + + obj = FileData.unsupported_38a_lines[index]; + + return true; +} + +static bool s_load_head38a(void* _FileData, WorldHead38A& src) +{ + WorldData& FileData = *reinterpret_cast(_FileData); + + static_cast(FileData) = src; + + return true; +} + +static bool s_save_head38a(const void* _FileData, WorldHead38A& obj, pge_size_t index) +{ + if(index != 0) + return false; + + const WorldData& FileData = *reinterpret_cast(_FileData); + + obj = static_cast(FileData); + + return true; +} + +bool MDX_load_world(PGE_FileFormats_misc::TextInput &file, WorldData &FileData) +{ + FileFormats::CreateWorldData(FileData); + FileData.meta.RecentFormat = WorldData::PGEX; + + //Add path data + PGESTRING filePath = file.getFilePath(); + if(!IsEmpty(filePath)) + { + PGE_FileFormats_misc::FileInfo in_1(filePath); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); + } + + FileData.meta.untitled = false; + FileData.meta.modified = false; + FileData.meta.ReadFileValid = true; + + return MDX_load_world(file, PGEFL_make_load_callbacks(FileData)); +} + +WorldLoadCallbacks PGEFL_make_load_callbacks(WorldData& target) +{ + WorldLoadCallbacks callbacks; + + callbacks.on_error = s_on_error; + + callbacks.load_head = s_load_head; + callbacks.load_bookmark = s_load_bookmark; + callbacks.load_crash_data = s_load_crash_data; + callbacks.load_tile = s_load_tile; + callbacks.load_scene = s_load_scene; + callbacks.load_path = s_load_path; + callbacks.load_music = s_load_music; + callbacks.load_arearect = s_load_arearect; + callbacks.load_level = s_load_level; + + callbacks.load_head38a = s_load_head38a; + callbacks.load_layer38a = s_load_layer38a; + callbacks.load_event38a = s_load_event38a; + callbacks.load_config38a = s_load_config38a; + callbacks.load_junk_line = s_load_junk_line; // but aren't they all junk lines? + + callbacks.userdata = reinterpret_cast(&target); + + return callbacks; +} + +bool MDX_load_world_header(PGE_FileFormats_misc::TextInput &file, WorldData &FileData) +{ + WorldHead h; + s_load_head(&FileData, h); + FileData.meta.RecentFormat = LevelData::PGEX; + + //Add path data + PGESTRING filePath = file.getFilePath(); + if(!IsEmpty(filePath)) + { + PGE_FileFormats_misc::FileInfo in_1(filePath); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); + } + + FileData.meta.untitled = false; + FileData.meta.modified = false; + FileData.meta.ReadFileValid = true; + + return MDX_load_world(file, PGEFL_make_header_load_callbacks(FileData)); +} + +WorldLoadCallbacks PGEFL_make_header_load_callbacks(WorldData& target) +{ + WorldLoadCallbacks callbacks; + + callbacks.on_error = s_on_error; + + callbacks.load_head = s_load_head_only; + + callbacks.userdata = reinterpret_cast(&target); + + return callbacks; +} + +bool MDX_save_world(PGE_FileFormats_misc::TextOutput &file, const WorldData &FileData) +{ + return MDX_save_world(file, PGEFL_make_save_callbacks(FileData)); +} + +WorldSaveCallbacks PGEFL_make_save_callbacks(const WorldData& target) +{ + WorldSaveCallbacks callbacks; + + callbacks.save_head = s_save_head; + callbacks.save_bookmark = s_save_bookmark; + callbacks.save_crash_data = s_save_crash_data; + callbacks.save_tile = s_save_tile; + callbacks.save_scene = s_save_scene; + callbacks.save_path = s_save_path; + callbacks.save_music = s_save_music; + callbacks.save_arearect = s_save_arearect; + callbacks.save_level = s_save_level; + + callbacks.save_head38a = s_save_head38a; + callbacks.save_layer38a = s_save_layer38a; + callbacks.save_event38a = s_save_event38a; + callbacks.save_config38a = s_save_config38a; + callbacks.save_junk_line = s_save_junk_line; + + callbacks.userdata = reinterpret_cast(&target); + + return callbacks; +} diff --git a/src/pge_file_lib_globs_rwops.cpp b/src/pge_file_lib_globs_rwops.cpp index 2007abf..4976d7c 100644 --- a/src/pge_file_lib_globs_rwops.cpp +++ b/src/pge_file_lib_globs_rwops.cpp @@ -28,12 +28,6 @@ #include "pge_file_lib_private.h" #include -#if __cplusplus >= 202002L -# define ATTR_LIKELY [[likely]] -#else -# define ATTR_LIKELY -#endif - namespace PGE_FileFormats_misc { @@ -308,7 +302,7 @@ void RWopsTextInput::readCVSLine(PGESTRING &out_utf16) goto skip_char; case '\r': // special fast path for \r\n - if(!quoteIsOpen && (byte + 1) != end && *(byte + 1) == '\n') ATTR_LIKELY // likely + if(!quoteIsOpen && (byte + 1) != end && *(byte + 1) == '\n') PGE_ATTR_LIKELY { m_lineNumber++; diff --git a/src/pge_file_lib_sys.h b/src/pge_file_lib_sys.h index b8235a9..4903f4c 100644 --- a/src/pge_file_lib_sys.h +++ b/src/pge_file_lib_sys.h @@ -52,8 +52,6 @@ #include #include #else -#include -#include #include #include /* PATH_MAX */ #endif diff --git a/src/pgex/file_rw_lvlx.cpp b/src/pgex/file_rw_lvlx.cpp index 3e75292..a518366 100644 --- a/src/pgex/file_rw_lvlx.cpp +++ b/src/pgex/file_rw_lvlx.cpp @@ -29,6 +29,7 @@ #include "pgex/file_strlist.h" #include "pge_x.h" #include "pgex/pge_x_macro.h" +#include "mdx/mdx_level_file.h" #include //********************************************************* @@ -66,148 +67,185 @@ bool FileFormats::ReadExtendedLvlFileHeaderRaw(PGESTRING &rawdata, const PGESTRI return ReadExtendedLvlFileHeaderT(inf, FileData); } -bool FileFormats::ReadExtendedLvlFileHeaderT(PGE_FileFormats_misc::TextInput &inf, LevelData &FileData) +bool FileFormats::ReadExtendedLvlFileHeaderT(PGE_FileFormats_misc::TextInput &inf, const LevelLoadCallbacks &cb) { - // indented 2 spaces to avoid large diff hunk - try - { - PGESTRING line; - int str_count = 0; - bool valid = false; - PGE_FileFormats_misc::FileInfo in_1(inf.getFilePath()); - FileData.meta.filename = in_1.basename(); - FileData.meta.path = in_1.dirpath(); - FileData.meta.RecentFormat = LevelData::PGEX; -#define NextLine(line) str_count++; inf.readLine(line); + if(!g_use_legacy_pgex_parser) + return MDX_load_level(inf, cb); - //Find level header part - do + PGESTRING line; + // BEFORE: indented 2 spaces to avoid large diff hunk + // REPLY: Spit on diff hung, do that just in next commit after :) + // Don't make "zoo" of code styles in the same file. + try { - str_count++; - NextLine(line) - } - while((line != "HEAD") && (!IsNULL(line))); + // int str_count = 0; + bool valid = false; + LevelHead head; + head.RecentFormat = LevelData::PGEX; - PGESTRINGList header; - bool closed = false; +#define NextLine(line) /*str_count++;*/ inf.readLine(line); - if(line != "HEAD")//Header not found, this level is head-less - goto skipHeaderParse; + //Find level header part + do + { + // str_count++; + NextLine(line) + } + while((line != "HEAD") && (!IsNULL(line))); - NextLine(line) - while((line != "HEAD_END") && (!IsNULL(line))) - { - header.push_back(line); - str_count++; - NextLine(line) - if(line == "HEAD_END") - closed = true; - } + PGESTRINGList header; + bool closed = false; - if(!closed) - goto bad_file; + if(line != "HEAD")//Header not found, this level is head-less + goto skipHeaderParse; - for(const auto &header_line : header) - { - PGELISTdata = PGEFile::splitDataLine(header_line, &valid); + NextLine(line) + while((line != "HEAD_END") && (!IsNULL(line))) + { + header.push_back(line); + // str_count++; + NextLine(line) + if(line == "HEAD_END") + closed = true; + } + + if(!closed) + goto bad_file; - for(const auto &val : data) + for(const auto &header_line : header) { - if(val.size() != 2) - goto bad_file; + PGELISTdata = PGEFile::splitDataLine(header_line, &valid); - if(val[0] == "TL") //Level Title - { - if(PGEFile::IsQoutedString(val[1])) - FileData.LevelName = PGEFile::X2STRING(val[1]); - else - goto bad_file; - } - else if(val[0] == "SZ") //Starz number - { - if(PGEFile::IsIntU(val[1])) - FileData.stars = toInt(val[1]); - else - goto bad_file; - } - else if(val[0] == "DL") //Open Level on player's fail + for(const auto &val : data) { - if(PGEFile::IsQoutedString(val[1])) - FileData.open_level_on_fail = PGEFile::X2STRING(val[1]); - else - goto bad_file; - } - else if(val[0] == "DE") //Target WarpID of fail-level entrace - { - if(PGEFile::IsIntU(val[1])) - FileData.open_level_on_fail_warpID = toUInt(val[1]); - else - goto bad_file; - } - else if(val[0] == "NO") //Overrides of player names - { - if(PGEFile::IsStringArray(val[1])) - FileData.player_names_overrides = PGEFile::X2STRArr(val[1]); - else - goto bad_file; - } - else if(val[0] == "XTRA") //Extra settings - { - if(PGEFile::IsQoutedString(val[1])) - FileData.custom_params = PGEFile::X2STRING(val[1]); - else - goto bad_file; - } - else if(val[0] == "CPID") //Config pack ID string - { - if(PGEFile::IsQoutedString(val[1])) - FileData.meta.configPackId = PGEFile::X2STRING(val[1]); - else - goto bad_file; - } - else if(val[0] == "EFL") //Engine feature level - { - if(PGEFile::IsIntU(val[1])) - FileData.meta.engineFeatureLevel = toUInt(val[1]); - else - goto bad_file; - } - else if(val[0] == "MUS") // Level-wide list of external music files - { - if(PGEFile::IsStringArray(val[1])) - FileData.music_files = PGEFile::X2STRArr(val[1]); - else + if(val.size() != 2) goto bad_file; + + if(val[0] == "TL") //Level Title + { + if(PGEFile::IsQoutedString(val[1])) + head.LevelName = PGEFile::X2STRING(val[1]); + else + goto bad_file; + } + else if(val[0] == "SZ") //Starz number + { + if(PGEFile::IsIntU(val[1])) + head.stars = toInt(val[1]); + else + goto bad_file; + } + else if(val[0] == "DL") //Open Level on player's fail + { + if(PGEFile::IsQoutedString(val[1])) + head.open_level_on_fail = PGEFile::X2STRING(val[1]); + else + goto bad_file; + } + else if(val[0] == "DE") //Target WarpID of fail-level entrace + { + if(PGEFile::IsIntU(val[1])) + head.open_level_on_fail_warpID = toUInt(val[1]); + else + goto bad_file; + } + else if(val[0] == "NO") //Overrides of player names + { + if(PGEFile::IsStringArray(val[1])) + head.player_names_overrides = PGEFile::X2STRArr(val[1]); + else + goto bad_file; + } + else if(val[0] == "XTRA") //Extra settings + { + if(PGEFile::IsQoutedString(val[1])) + head.custom_params = PGEFile::X2STRING(val[1]); + else + goto bad_file; + } + else if(val[0] == "CPID") //Config pack ID string + { + if(PGEFile::IsQoutedString(val[1])) + head.configPackId = PGEFile::X2STRING(val[1]); + else + goto bad_file; + } + else if(val[0] == "EFL") //Engine feature level + { + if(PGEFile::IsIntU(val[1])) + head.engineFeatureLevel = toUInt(val[1]); + else + goto bad_file; + } + else if(val[0] == "MUS") // Level-wide list of external music files + { + if(PGEFile::IsStringArray(val[1])) + head.music_files = PGEFile::X2STRArr(val[1]); + else + goto bad_file; + } } } - } skipHeaderParse: - FileData.CurSection = 0; - FileData.playmusic = false; - FileData.meta.ReadFileValid = true; - return true; + if(cb.load_head) + cb.load_head(cb.userdata, head); + + return true; bad_file: - FileData.meta.ERROR_info = "Invalid file format"; - FileData.meta.ERROR_linenum = static_cast(str_count); - FileData.meta.ERROR_linedata = line; - FileData.meta.ReadFileValid = false; - PGE_CutLength(FileData.meta.ERROR_linedata, 50); - PGE_FilterBinary(FileData.meta.ERROR_linedata); - return false; - } - catch(const std::exception& e) - { - FileData.meta.ERROR_info = e.what(); - FileData.meta.ERROR_linedata.clear(); - FileData.meta.ERROR_linenum = -1; - FileData.meta.ReadFileValid = false; - return false; - } + if(cb.on_error) + { + FileFormatsError error; + error.ERROR_info = "Invalid file format"; + error.ERROR_linenum = inf.getCurrentLineNumber(); + error.ERROR_linedata = std::move(line); + PGE_CutLength(error.ERROR_linedata, 50); + PGE_FilterBinary(error.ERROR_linedata); + cb.on_error(cb.userdata, error); + } + return false; + } + catch(const PGE_FileFormats_misc::callback_interrupt& e) + { + return true; + } + catch(const std::exception& e) + { + if(cb.on_error) + { + FileFormatsError error; + error.ERROR_info.clear(); + error.add_exc_info(e, inf.getCurrentLineNumber(), std::move(line)); + cb.on_error(cb.userdata, error); + } + + return false; + } } +bool FileFormats::ReadExtendedLvlFileHeaderT(PGE_FileFormats_misc::TextInput &inf, LevelData &FileData) +{ + CreateLevelData(FileData); + + //Add path data + PGESTRING filePath = inf.getFilePath(); + if(!IsEmpty(filePath)) + { + PGE_FileFormats_misc::FileInfo in_1(filePath); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); + } + + FileData.meta.untitled = false; + FileData.meta.modified = false; + FileData.meta.ReadFileValid = true; + FileData.CurSection = 0; + FileData.playmusic = false; + + return ReadExtendedLvlFileHeaderT(inf, PGEFL_make_header_load_callbacks(FileData)); +} bool FileFormats::ReadExtendedLvlFileF(const PGESTRING &filePath, LevelData &FileData) { @@ -243,1468 +281,1418 @@ bool FileFormats::ReadExtendedLvlFileRaw(PGESTRING &rawdata, const PGESTRING &fi return ReadExtendedLvlFile(file, FileData); } -bool FileFormats::ReadExtendedLvlFile(PGE_FileFormats_misc::TextInput &in, LevelData &FileData) +bool FileFormats::ReadExtendedLvlFile(PGE_FileFormats_misc::TextInput &in, const LevelLoadCallbacks &cb) { - // indented 2 spaces to avoid large diff hunk - try - { - PGESTRING errorString; - PGESTRING filePath = in.getFilePath(); - PGESTRING line; /*Current Line data*/ - //LevelData FileData; - CreateLevelData(FileData); - FileData.meta.RecentFormat = LevelData::PGEX; + if(!g_use_legacy_pgex_parser) + return MDX_load_level(in, cb); - //Add path data - if(!IsEmpty(filePath)) - { - PGE_FileFormats_misc::FileInfo in_1(filePath); - FileData.meta.filename = in_1.basename(); - FileData.meta.path = in_1.dirpath(); - } - - FileData.meta.untitled = false; - FileData.meta.modified = false; - LevelSection lvl_section; - PlayerPoint player; - LevelBlock block; - LevelBGO bgodata; - LevelNPC npcdata; - LevelDoor door; - LevelPhysEnv physiczone; - LevelLayer layer; - LevelSMBX64Event event; - LevelVariable variable; - LevelArray array_field; - LevelScript script; - LevelItemSetup38A customcfg38A; - ///////////////////////////////////////Begin file/////////////////////////////////////// - PGEX_FileParseTree(in.readAll()) - PGEX_FetchSection() //look sections + PGESTRING line; /*Current Line data*/ + // BEFORE: indented 2 spaces to avoid large diff hunk + // REPLY: Spit on diff hung, do that just in next commit after :) + // Don't make "zoo" of code styles in the same file. + try { - PGEX_FetchSection_begin() - ///////////////////HEADER////////////////////// - PGEX_Section("HEAD") + PGESTRING errorString; + PGESTRING filePath = in.getFilePath(); + + LevelHead head; + CrashData crash; + LevelSection lvl_section; + PlayerPoint player; + LevelBlock block; + LevelBGO bgodata; + LevelNPC npcdata; + LevelDoor door; + LevelPhysEnv physiczone; + LevelLayer layer; + LevelSMBX64Event event; + LevelVariable variable; + LevelArray array_field; + LevelScript script; + LevelItemSetup38A customcfg38A; + ///////////////////////////////////////Begin file/////////////////////////////////////// + PGEX_FileParseTree(in.readAll()) + PGEX_FetchSection() //look sections { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + PGEX_FetchSection_begin() + ///////////////////HEADER////////////////////// + PGEX_Section("HEAD") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_StrVal("TL", FileData.LevelName) //Level Title - PGEX_USIntVal("SZ", FileData.stars) //Starz number - PGEX_StrVal("DL", FileData.open_level_on_fail) //Open level on fail - PGEX_UIntVal("DE", FileData.open_level_on_fail_warpID) //Open level's warpID on fail - PGEX_StrArrVal("NO", FileData.player_names_overrides) //Overrides of player names - PGEX_StrVal("XTRA", FileData.custom_params) //Level-wide Extra settings - PGEX_StrVal("CPID", FileData.meta.configPackId)//Config pack ID string - PGEX_UIntVal("EFL", FileData.meta.engineFeatureLevel) //Target engine version - PGEX_StrArrVal("MUS", FileData.music_files)// Level-wide list of external music files + PGEX_ItemBegin(PGEFile::PGEX_Struct) + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_StrVal("TL", head.LevelName) //Level Title + PGEX_USIntVal("SZ", head.stars) //Starz number + PGEX_StrVal("DL", head.open_level_on_fail) //Open level on fail + PGEX_UIntVal("DE", head.open_level_on_fail_warpID) //Open level's warpID on fail + PGEX_StrArrVal("NO", head.player_names_overrides) //Overrides of player names + PGEX_StrVal("XTRA", head.custom_params) //Level-wide Extra settings + PGEX_StrVal("CPID", head.configPackId)//Config pack ID string + PGEX_UIntVal("EFL", head.engineFeatureLevel) //Target engine version + PGEX_StrArrVal("MUS", head.music_files)// Level-wide list of external music files + } } - } - }//HEADER - ///////////////////////////////MetaDATA///////////////////////////////////////////// - PGEX_Section("META_BOOKMARKS") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + + if(cb.load_head) + cb.load_head(cb.userdata, head); + }//HEADER + ///////////////////////////////MetaDATA///////////////////////////////////////////// + PGEX_Section("META_BOOKMARKS") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - Bookmark meta_bookmark; - meta_bookmark.bookmarkName.clear(); - meta_bookmark.x = 0; - meta_bookmark.y = 0; - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_StrVal("BM", meta_bookmark.bookmarkName) //Bookmark name - PGEX_FloatVal("X", meta_bookmark.x) // Position X - PGEX_FloatVal("Y", meta_bookmark.y) // Position Y + PGEX_ItemBegin(PGEFile::PGEX_Struct) + Bookmark meta_bookmark; + meta_bookmark.bookmarkName.clear(); + meta_bookmark.x = 0; + meta_bookmark.y = 0; + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_StrVal("BM", meta_bookmark.bookmarkName) //Bookmark name + PGEX_FloatVal("X", meta_bookmark.x) // Position X + PGEX_FloatVal("Y", meta_bookmark.y) // Position Y + } + + if(cb.load_bookmark) + cb.load_bookmark(cb.userdata, meta_bookmark); } - FileData.metaData.bookmarks.push_back(meta_bookmark); } - } - ////////////////////////meta bookmarks//////////////////////// - PGEX_Section("META_SYS_CRASH") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + ////////////////////////meta bookmarks//////////////////////// + PGEX_Section("META_SYS_CRASH") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - FileData.metaData.crash.used = true; - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_BoolVal("UT", FileData.metaData.crash.untitled) //Untitled - PGEX_BoolVal("MD", FileData.metaData.crash.modifyed) //Modyfied - PGEX_SIntVal("FF", FileData.metaData.crash.fmtID) //Recent File format - PGEX_UIntVal("FV", FileData.metaData.crash.fmtVer) //Recent File format version - PGEX_StrVal("N", FileData.metaData.crash.filename) //Filename - PGEX_StrVal("P", FileData.metaData.crash.path) //Path - PGEX_StrVal("FP", FileData.metaData.crash.fullPath) //Full file Path + PGEX_ItemBegin(PGEFile::PGEX_Struct) + crash.used = true; + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_BoolVal("UT", crash.untitled) //Untitled + PGEX_BoolVal("MD", crash.modifyed) //Modyfied + PGEX_SIntVal("FF", crash.fmtID) //Recent File format + PGEX_UIntVal("FV", crash.fmtVer) //Recent File format version + PGEX_StrVal("N", crash.filename) //Filename + PGEX_StrVal("P", crash.path) //Path + PGEX_StrVal("FP", crash.fullPath) //Full file Path + } } - } - }//meta sys crash - ///////////////////////////////MetaDATA//End//////////////////////////////////////// - ///////////////////SECTION////////////////////// - PGEX_Section("SECTION") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + + if(cb.load_crash_data) + cb.load_crash_data(cb.userdata, crash); + }//meta sys crash + ///////////////////////////////MetaDATA//End//////////////////////////////////////// + ///////////////////SECTION////////////////////// + PGEX_Section("SECTION") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - lvl_section = CreateLvlSection(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_USIntVal("SC", lvl_section.id) //Section ID - PGEX_SLongVal("L", lvl_section.size_left) //Left side - PGEX_SLongVal("R", lvl_section.size_right)//Right side - PGEX_SLongVal("T", lvl_section.size_top) //Top side - PGEX_SLongVal("B", lvl_section.size_bottom)//Bottom side - PGEX_UIntVal("MZ", lvl_section.music_id)//Built-in music ID - PGEX_UIntVal("BG", lvl_section.background)//Built-in background ID - PGEX_SIntVal("LT", lvl_section.lighting_value)//Lighting value - PGEX_StrVal("MF", lvl_section.music_file) //External music file path - PGEX_SIntVal("ME", lvl_section.music_file_idx) //External music entry from level list - PGEX_BoolVal("CS", lvl_section.wrap_h)//Connect sides horizontally - PGEX_BoolVal("CSV", lvl_section.wrap_v)//Connect sides vertically - PGEX_BoolVal("OE", lvl_section.OffScreenEn)//Offscreen exit - PGEX_BoolVal("SR", lvl_section.lock_left_scroll)//Right-way scroll only (No Turn-back) - PGEX_BoolVal("SL", lvl_section.lock_right_scroll)//Left-way scroll only (No Turn-forward) - PGEX_BoolVal("SD", lvl_section.lock_up_scroll)//Down-way scroll only (No Turn-forward) - PGEX_BoolVal("SU", lvl_section.lock_down_scroll)//Up-way scroll only (No Turn-forward) - PGEX_BoolVal("UW", lvl_section.underwater)//Underwater bit - PGEX_StrVal("XTRA", lvl_section.custom_params)//Custom JSON data tree - } - lvl_section.PositionX = lvl_section.size_left - 10; - lvl_section.PositionY = lvl_section.size_top - 10; - - //add captured value into array - pge_size_t sections_count = FileData.sections.size(); + PGEX_ItemBegin(PGEFile::PGEX_Struct) + lvl_section = CreateLvlSection(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_USIntVal("SC", lvl_section.id) //Section ID + PGEX_SLongVal("L", lvl_section.size_left) //Left side + PGEX_SLongVal("R", lvl_section.size_right)//Right side + PGEX_SLongVal("T", lvl_section.size_top) //Top side + PGEX_SLongVal("B", lvl_section.size_bottom)//Bottom side + PGEX_UIntVal("MZ", lvl_section.music_id)//Built-in music ID + PGEX_UIntVal("BG", lvl_section.background)//Built-in background ID + PGEX_SIntVal("LT", lvl_section.lighting_value)//Lighting value + PGEX_StrVal("MF", lvl_section.music_file) //External music file path + PGEX_SIntVal("ME", lvl_section.music_file_idx) //External music entry from level list + PGEX_BoolVal("CS", lvl_section.wrap_h)//Connect sides horizontally + PGEX_BoolVal("CSV", lvl_section.wrap_v)//Connect sides vertically + PGEX_BoolVal("OE", lvl_section.OffScreenEn)//Offscreen exit + PGEX_BoolVal("SR", lvl_section.lock_left_scroll)//Right-way scroll only (No Turn-back) + PGEX_BoolVal("SL", lvl_section.lock_right_scroll)//Left-way scroll only (No Turn-forward) + PGEX_BoolVal("SD", lvl_section.lock_up_scroll)//Down-way scroll only (No Turn-forward) + PGEX_BoolVal("SU", lvl_section.lock_down_scroll)//Up-way scroll only (No Turn-forward) + PGEX_BoolVal("UW", lvl_section.underwater)//Underwater bit + PGEX_StrVal("XTRA", lvl_section.custom_params)//Custom JSON data tree + } - if(lvl_section.id < 0) - { - errorString = "Section ID has negative value"; - goto badfile; + if(cb.load_section) + cb.load_section(cb.userdata, lvl_section); } - - if(lvl_section.id > 1000) + }//SECTION + ///////////////////STARTPOINT////////////////////// + PGEX_Section("STARTPOINT") + { + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - errorString = "Section ID is larger than 1000"; - goto badfile; - } + PGEX_ItemBegin(PGEFile::PGEX_Struct) + player = CreateLvlPlayerPoint(); - if(lvl_section.id >= static_cast(sections_count)) - { - pge_size_t needToAdd = static_cast(lvl_section.id) - (FileData.sections.size() - 1); - while(needToAdd > 0) + // default player dimensions overridden in the callback below + player.w = 0; + player.h = 0; + + PGEX_Values() //Look markers and values { - LevelSection dummySct = CreateLvlSection(); - dummySct.id = (int)FileData.sections.size(); - FileData.sections.push_back(dummySct); - needToAdd--; + PGEX_ValueBegin() + PGEX_UIntVal("ID", player.id) //ID of player point + PGEX_SLongVal("X", player.x) + PGEX_SLongVal("Y", player.y) + PGEX_SIntVal("D", player.direction) } - } - FileData.sections[static_cast(lvl_section.id)] = lvl_section; - } - }//SECTION - ///////////////////STARTPOINT////////////////////// - PGEX_Section("STARTPOINT") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() - { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - player = CreateLvlPlayerPoint(); - PGEX_Values() //Look markers and values - { - PGEX_ValueBegin() - PGEX_UIntVal("ID", player.id) //ID of player point - PGEX_SLongVal("X", player.x) - PGEX_SLongVal("Y", player.y) - PGEX_SIntVal("D", player.direction) + if(cb.load_startpoint) + cb.load_startpoint(cb.userdata, player); } - - //add captured value into array - bool found = false; - pge_size_t q = 0; - pge_size_t playersCount = FileData.players.size(); - for(q = 0; q < playersCount; q++) + }//STARTPOINT + ///////////////////BLOCK////////////////////// + PGEX_Section("BLOCK") + { + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - if(FileData.players[q].id == player.id) + PGEX_ItemBegin(PGEFile::PGEX_Struct) + block = CreateLvlBlock(); + PGEX_Values() //Look markers and values { - found = true; - break; + PGEX_ValueBegin() + PGEX_ULongVal("ID", block.id) //Block ID + PGEX_SLongVal("X", block.x) // Position X + PGEX_SLongVal("Y", block.y) //Position Y + PGEX_USLongVal("W", block.w) //Width + PGEX_USLongVal("H", block.h) //Height + PGEX_BoolVal("AS", block.autoscale)//Enable auto-Scaling + PGEX_StrVal("GXN", block.gfx_name) //38A GFX-Name + PGEX_SLongVal("GXX", block.gfx_dx) //38A graphics extend x + PGEX_SLongVal("GXY", block.gfx_dy) //38A graphics extend y + PGEX_SLongVal("CN", block.npc_id) //Contains (coins/NPC) + PGEX_SLongVal("CS", block.npc_special_value) //Special value for contained NPC + PGEX_BoolVal("IV", block.invisible) //Invisible + PGEX_BoolVal("SL", block.slippery) //Slippery + PGEX_UInt32Val("MA", block.motion_ai_id) //Motion AI type + PGEX_SLongVal("S1", block.special_data) //Special value 1 + PGEX_SLongVal("S2", block.special_data2) //Special value 2 + PGEX_StrVal("LR", block.layer) //Layer name + PGEX_StrVal("ED", block.event_destroy) //Destroy event slot + PGEX_StrVal("EH", block.event_hit) //Hit event slot + PGEX_StrVal("EE", block.event_emptylayer) //Hit event slot + PGEX_StrVal("XTRA", block.meta.custom_params)//Custom JSON data tree } - } - - PlayerPoint sz = CreateLvlPlayerPoint(player.id); - player.w = sz.w; - player.h = sz.h; - if(found) - FileData.players[q] = player; - else - FileData.players.push_back(player); - } - }//STARTPOINT - ///////////////////BLOCK////////////////////// - PGEX_Section("BLOCK") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() - { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - block = CreateLvlBlock(); - PGEX_Values() //Look markers and values - { - PGEX_ValueBegin() - PGEX_ULongVal("ID", block.id) //Block ID - PGEX_SLongVal("X", block.x) // Position X - PGEX_SLongVal("Y", block.y) //Position Y - PGEX_USLongVal("W", block.w) //Width - PGEX_USLongVal("H", block.h) //Height - PGEX_BoolVal("AS", block.autoscale)//Enable auto-Scaling - PGEX_StrVal("GXN", block.gfx_name) //38A GFX-Name - PGEX_SLongVal("GXX", block.gfx_dx) //38A graphics extend x - PGEX_SLongVal("GXY", block.gfx_dy) //38A graphics extend y - PGEX_SLongVal("CN", block.npc_id) //Contains (coins/NPC) - PGEX_SLongVal("CS", block.npc_special_value) //Special value for contained NPC - PGEX_BoolVal("IV", block.invisible) //Invisible - PGEX_BoolVal("SL", block.slippery) //Slippery - PGEX_UInt32Val("MA", block.motion_ai_id) //Motion AI type - PGEX_SLongVal("S1", block.special_data) //Special value 1 - PGEX_SLongVal("S2", block.special_data2) //Special value 2 - PGEX_StrVal("LR", block.layer) //Layer name - PGEX_StrVal("ED", block.event_destroy) //Destroy event slot - PGEX_StrVal("EH", block.event_hit) //Hit event slot - PGEX_StrVal("EE", block.event_emptylayer) //Hit event slot - PGEX_StrVal("XTRA", block.meta.custom_params)//Custom JSON data tree + if(cb.load_block) + cb.load_block(cb.userdata, block); } - block.meta.array_id = FileData.blocks_array_id++; - block.meta.index = static_cast(FileData.blocks.size()); - FileData.blocks.push_back(block); - } - }//BLOCK - ///////////////////BGO////////////////////// - PGEX_Section("BGO") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + }//BLOCK + ///////////////////BGO////////////////////// + PGEX_Section("BGO") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - bgodata = CreateLvlBgo(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_ULongVal("ID", bgodata.id) //BGO ID - PGEX_SLongVal("X", bgodata.x) //X Position - PGEX_SLongVal("Y", bgodata.y) //Y Position - PGEX_SLongVal("GXX", bgodata.gfx_dx) //38A graphics extend x - PGEX_SLongVal("GXY", bgodata.gfx_dy) //38A graphics extend y - PGEX_FloatVal("ZO", bgodata.z_offset) //Z Offset - PGEX_SIntVal("ZP", bgodata.z_mode) //Z Position - PGEX_SLongVal("SP", bgodata.smbx64_sp) //SMBX64 Sorting priority - PGEX_StrVal("LR", bgodata.layer) //Layer name - PGEX_StrVal("XTRA", bgodata.meta.custom_params)//Custom JSON data tree + PGEX_ItemBegin(PGEFile::PGEX_Struct) + bgodata = CreateLvlBgo(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_ULongVal("ID", bgodata.id) //BGO ID + PGEX_SLongVal("X", bgodata.x) //X Position + PGEX_SLongVal("Y", bgodata.y) //Y Position + PGEX_SLongVal("GXX", bgodata.gfx_dx) //38A graphics extend x + PGEX_SLongVal("GXY", bgodata.gfx_dy) //38A graphics extend y + PGEX_FloatVal("ZO", bgodata.z_offset) //Z Offset + PGEX_SIntVal("ZP", bgodata.z_mode) //Z Position + PGEX_SLongVal("SP", bgodata.smbx64_sp) //SMBX64 Sorting priority + PGEX_StrVal("LR", bgodata.layer) //Layer name + PGEX_StrVal("XTRA", bgodata.meta.custom_params)//Custom JSON data tree + } + + if(cb.load_bgo) + cb.load_bgo(cb.userdata, bgodata); } - bgodata.meta.array_id = FileData.bgo_array_id++; - bgodata.meta.index = static_cast(FileData.bgo.size()); - FileData.bgo.push_back(bgodata); - } - }//BGO - ///////////////////NPC////////////////////// - PGEX_Section("NPC") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + }//BGO + ///////////////////NPC////////////////////// + PGEX_Section("NPC") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - npcdata = CreateLvlNpc(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_UInt64Val("ID", npcdata.id) //NPC ID - PGEX_SLongVal("X", npcdata.x) //X position - PGEX_SLongVal("Y", npcdata.y) //Y position - PGEX_StrVal("GXN", npcdata.gfx_name) //38A GFX-Name - PGEX_SLongVal("GXX", npcdata.gfx_dx) //38A graphics extend x - PGEX_SLongVal("GXY", npcdata.gfx_dy) //38A graphics extend y - PGEX_SLongVal("OW", npcdata.override_width) //Override width - PGEX_SLongVal("OH", npcdata.override_height) //Override height - PGEX_BoolVal("GAS", npcdata.gfx_autoscale) //Autoscale GFX on size override - PGEX_SLongVal("WGT", npcdata.wings_type) //38A: Wings type - PGEX_SLongVal("WGS", npcdata.wings_style) //38A: Wings style - PGEX_SIntVal("D", npcdata.direct) //Direction - PGEX_SLongVal("CN", npcdata.contents) //Contents of container-NPC - PGEX_SLongVal("S1", npcdata.special_data) //Special value 1 - PGEX_SLongVal("S2", npcdata.special_data2) //Special value 2 - PGEX_BoolVal("GE", npcdata.generator) //Generator - PGEX_SIntVal("GT", npcdata.generator_type) //Generator type - PGEX_SIntVal("GD", npcdata.generator_direct) //Generator direction - PGEX_USIntVal("GM", npcdata.generator_period) //Generator period - PGEX_FloatVal("GA", npcdata.generator_custom_angle) //Generator custom angle - PGEX_USIntVal("GB", npcdata.generator_branches) //Generator number of branches - PGEX_FloatVal("GR", npcdata.generator_angle_range) //Generator angle range - PGEX_FloatVal("GS", npcdata.generator_initial_speed) //Generator custom initial speed - PGEX_StrVal("MG", npcdata.msg) //Message - PGEX_BoolVal("FD", npcdata.friendly) //Friendly - PGEX_BoolVal("NM", npcdata.nomove) //Don't move - PGEX_BoolVal("BS", npcdata.is_boss) //Enable boss mode! - PGEX_StrVal("LR", npcdata.layer) //Layer - PGEX_StrVal("LA", npcdata.attach_layer) //Attach Layer - PGEX_StrVal("SV", npcdata.send_id_to_variable) //Send ID to variable - PGEX_StrVal("EA", npcdata.event_activate) //Event slot "Activated" - PGEX_StrVal("ED", npcdata.event_die) //Event slot "Death/Take/Destroy" - PGEX_StrVal("ET", npcdata.event_talk) //Event slot "Talk" - PGEX_StrVal("EE", npcdata.event_emptylayer) //Event slot "Layer is empty" - PGEX_StrVal("EG", npcdata.event_grab)//Event slot "On grab" - PGEX_StrVal("EO", npcdata.event_touch)//Event slot "On touch" - PGEX_StrVal("EF", npcdata.event_nextframe)//Evemt slot "Trigger every frame" - PGEX_StrVal("XTRA", npcdata.meta.custom_params)//Custom JSON data tree + PGEX_ItemBegin(PGEFile::PGEX_Struct) + npcdata = CreateLvlNpc(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_UInt64Val("ID", npcdata.id) //NPC ID + PGEX_SLongVal("X", npcdata.x) //X position + PGEX_SLongVal("Y", npcdata.y) //Y position + PGEX_StrVal("GXN", npcdata.gfx_name) //38A GFX-Name + PGEX_SLongVal("GXX", npcdata.gfx_dx) //38A graphics extend x + PGEX_SLongVal("GXY", npcdata.gfx_dy) //38A graphics extend y + PGEX_SLongVal("OW", npcdata.override_width) //Override width + PGEX_SLongVal("OH", npcdata.override_height) //Override height + PGEX_BoolVal("GAS", npcdata.gfx_autoscale) //Autoscale GFX on size override + PGEX_SLongVal("WGT", npcdata.wings_type) //38A: Wings type + PGEX_SLongVal("WGS", npcdata.wings_style) //38A: Wings style + PGEX_SIntVal("D", npcdata.direct) //Direction + PGEX_SLongVal("CN", npcdata.contents) //Contents of container-NPC + PGEX_SLongVal("S1", npcdata.special_data) //Special value 1 + PGEX_SLongVal("S2", npcdata.special_data2) //Special value 2 + PGEX_BoolVal("GE", npcdata.generator) //Generator + PGEX_SIntVal("GT", npcdata.generator_type) //Generator type + PGEX_SIntVal("GD", npcdata.generator_direct) //Generator direction + PGEX_USIntVal("GM", npcdata.generator_period) //Generator period + PGEX_FloatVal("GA", npcdata.generator_custom_angle) //Generator custom angle + PGEX_USIntVal("GB", npcdata.generator_branches) //Generator number of branches + PGEX_FloatVal("GR", npcdata.generator_angle_range) //Generator angle range + PGEX_FloatVal("GS", npcdata.generator_initial_speed) //Generator custom initial speed + PGEX_StrVal("MG", npcdata.msg) //Message + PGEX_BoolVal("FD", npcdata.friendly) //Friendly + PGEX_BoolVal("NM", npcdata.nomove) //Don't move + PGEX_BoolVal("BS", npcdata.is_boss) //Enable boss mode! + PGEX_StrVal("LR", npcdata.layer) //Layer + PGEX_StrVal("LA", npcdata.attach_layer) //Attach Layer + PGEX_StrVal("SV", npcdata.send_id_to_variable) //Send ID to variable + PGEX_StrVal("EA", npcdata.event_activate) //Event slot "Activated" + PGEX_StrVal("ED", npcdata.event_die) //Event slot "Death/Take/Destroy" + PGEX_StrVal("ET", npcdata.event_talk) //Event slot "Talk" + PGEX_StrVal("EE", npcdata.event_emptylayer) //Event slot "Layer is empty" + PGEX_StrVal("EG", npcdata.event_grab)//Event slot "On grab" + PGEX_StrVal("EO", npcdata.event_touch)//Event slot "On touch" + PGEX_StrVal("EF", npcdata.event_nextframe)//Evemt slot "Trigger every frame" + PGEX_StrVal("XTRA", npcdata.meta.custom_params)//Custom JSON data tree + } + + if(cb.load_npc) + cb.load_npc(cb.userdata, npcdata); } - npcdata.meta.array_id = FileData.npc_array_id++; - npcdata.meta.index = static_cast(FileData.npc.size()); - FileData.npc.push_back(npcdata); - } - }//TILES - ///////////////////PHYSICS////////////////////// - PGEX_Section("PHYSICS") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + }//TILES + ///////////////////PHYSICS////////////////////// + PGEX_Section("PHYSICS") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - physiczone = CreateLvlPhysEnv(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_USIntVal("ET", physiczone.env_type) //Environment type - PGEX_SLongVal("X", physiczone.x) //X position - PGEX_SLongVal("Y", physiczone.y) //Y position - PGEX_USLongVal("W", physiczone.w) //Width or circle Radius - PGEX_USLongVal("H", physiczone.h) //Height or -1 to turn the shape into circle - PGEX_StrVal("LR", physiczone.layer) //Layer - PGEX_FloatVal("FR", physiczone.friction) //Friction - PGEX_FloatVal("AD", physiczone.accel_direct) //Custom acceleration direction - PGEX_FloatVal("AC", physiczone.accel) //Custom acceleration - PGEX_FloatVal("MV", physiczone.max_velocity) //Maximal velocity - PGEX_StrVal("EO", physiczone.touch_event) //Touch event/script - PGEX_StrVal("XTRA", physiczone.meta.custom_params)//Custom JSON data tree + PGEX_ItemBegin(PGEFile::PGEX_Struct) + physiczone = CreateLvlPhysEnv(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_USIntVal("ET", physiczone.env_type) //Environment type + PGEX_SLongVal("X", physiczone.x) //X position + PGEX_SLongVal("Y", physiczone.y) //Y position + PGEX_USLongVal("W", physiczone.w) //Width or circle Radius + PGEX_USLongVal("H", physiczone.h) //Height or -1 to turn the shape into circle + PGEX_StrVal("LR", physiczone.layer) //Layer + PGEX_FloatVal("FR", physiczone.friction) //Friction + PGEX_FloatVal("AD", physiczone.accel_direct) //Custom acceleration direction + PGEX_FloatVal("AC", physiczone.accel) //Custom acceleration + PGEX_FloatVal("MV", physiczone.max_velocity) //Maximal velocity + PGEX_StrVal("EO", physiczone.touch_event) //Touch event/script + PGEX_StrVal("XTRA", physiczone.meta.custom_params)//Custom JSON data tree + } + + if(cb.load_phys) + cb.load_phys(cb.userdata, physiczone); } - physiczone.meta.array_id = FileData.physenv_array_id++; - physiczone.meta.index = static_cast(FileData.physez.size()); - FileData.physez.push_back(physiczone); - } - }//PHYSICS - ///////////////////DOORS////////////////////// - PGEX_Section("DOORS") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + }//PHYSICS + ///////////////////DOORS////////////////////// + PGEX_Section("DOORS") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - door = CreateLvlWarp(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_SLongVal("IX", door.ix) //Input point - PGEX_SLongVal("IY", door.iy) //Input point - PGEX_SLongVal("OX", door.ox) //Output point - PGEX_SLongVal("OY", door.oy) //Output point - PGEX_UIntVal("IL", door.length_i) //Length of entrance (input) point - PGEX_UIntVal("OL", door.length_o) //Length of exit (output) point - PGEX_UIntVal("IH", door.height_i) //Height of entrance (input) point - PGEX_UIntVal("OH", door.height_o) //Height of exit (output) point - PGEX_USIntVal("DT", door.type) //Input point - PGEX_USIntVal("ID", door.idirect) //Input direction - PGEX_USIntVal("OD", door.odirect) //Output direction - PGEX_SLongVal("WX", door.world_x) //Target world map point - PGEX_SLongVal("WY", door.world_y) //Target world map point - PGEX_StrVal("LF", door.lname) //Target level file - PGEX_USLongVal("LI", door.warpto) //Target level file's input warp - PGEX_BoolVal("ET", door.lvl_i) //Level Entrance - PGEX_BoolVal("EX", door.lvl_o) //Level exit - PGEX_USIntVal("SL", door.stars) //Stars limit - PGEX_StrVal("SM", door.stars_msg) //Message about stars/leeks - PGEX_BoolVal("NV", door.novehicles) //No Vehicles - PGEX_BoolVal("SH", door.star_num_hide) //Don't show stars number - PGEX_BoolVal("AI", door.allownpc) //Allow grabbed items - PGEX_BoolVal("LC", door.locked) //Door is locked - PGEX_BoolVal("LB", door.need_a_bomb) //Door is blocked, need bomb to unlock - PGEX_BoolVal("HS", door.hide_entering_scene) //Don't show entering scene - PGEX_BoolVal("AL", door.allownpc_interlevel) //Allow NPC's inter-level - PGEX_BoolVal("SR", door.special_state_required) //Required a special state to enter - PGEX_BoolVal("STR", door.stood_state_required) //Required a stood state to enter - PGEX_SIntVal("TE", door.transition_effect) //Transition effect - PGEX_BoolVal("PT", door.cannon_exit) //Cannon exit - PGEX_FloatVal("PS", door.cannon_exit_speed) //Cannon exit speed - PGEX_StrVal("LR", door.layer) //Layer - PGEX_StrVal("EE", door.event_enter) //On-Enter event slot - PGEX_StrVal("EEX", door.event_exit) //On-Exit event slot - PGEX_BoolVal("TW", door.two_way) //Two-way warp - PGEX_StrVal("XTRA", door.meta.custom_params)//Custom JSON data tree - } - door.isSetIn = (!door.lvl_i); - door.isSetOut = (!door.lvl_o || (door.lvl_i)); - - if(!door.isSetIn && door.isSetOut) - { - door.ix = door.ox; - door.iy = door.oy; - } + PGEX_ItemBegin(PGEFile::PGEX_Struct) + door = CreateLvlWarp(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_SLongVal("IX", door.ix) //Input point + PGEX_SLongVal("IY", door.iy) //Input point + PGEX_SLongVal("OX", door.ox) //Output point + PGEX_SLongVal("OY", door.oy) //Output point + PGEX_UIntVal("IL", door.length_i) //Length of entrance (input) point + PGEX_UIntVal("OL", door.length_o) //Length of exit (output) point + PGEX_UIntVal("IH", door.height_i) //Height of entrance (input) point + PGEX_UIntVal("OH", door.height_o) //Height of exit (output) point + PGEX_USIntVal("DT", door.type) //Input point + PGEX_USIntVal("ID", door.idirect) //Input direction + PGEX_USIntVal("OD", door.odirect) //Output direction + PGEX_SLongVal("WX", door.world_x) //Target world map point + PGEX_SLongVal("WY", door.world_y) //Target world map point + PGEX_StrVal("LF", door.lname) //Target level file + PGEX_USLongVal("LI", door.warpto) //Target level file's input warp + PGEX_BoolVal("ET", door.lvl_i) //Level Entrance + PGEX_BoolVal("EX", door.lvl_o) //Level exit + PGEX_USIntVal("SL", door.stars) //Stars limit + PGEX_StrVal("SM", door.stars_msg) //Message about stars/leeks + PGEX_BoolVal("NV", door.novehicles) //No Vehicles + PGEX_BoolVal("SH", door.star_num_hide) //Don't show stars number + PGEX_BoolVal("AI", door.allownpc) //Allow grabbed items + PGEX_BoolVal("LC", door.locked) //Door is locked + PGEX_BoolVal("LB", door.need_a_bomb) //Door is blocked, need bomb to unlock + PGEX_BoolVal("HS", door.hide_entering_scene) //Don't show entering scene + PGEX_BoolVal("AL", door.allownpc_interlevel) //Allow NPC's inter-level + PGEX_BoolVal("SR", door.special_state_required) //Required a special state to enter + PGEX_BoolVal("STR", door.stood_state_required) //Required a stood state to enter + PGEX_SIntVal("TE", door.transition_effect) //Transition effect + PGEX_BoolVal("PT", door.cannon_exit) //Cannon exit + PGEX_FloatVal("PS", door.cannon_exit_speed) //Cannon exit speed + PGEX_StrVal("LR", door.layer) //Layer + PGEX_StrVal("EE", door.event_enter) //On-Enter event slot + PGEX_StrVal("EEX", door.event_exit) //On-Exit event slot + PGEX_BoolVal("TW", door.two_way) //Two-way warp + PGEX_StrVal("XTRA", door.meta.custom_params)//Custom JSON data tree + } - if(!door.isSetOut && door.isSetIn) - { - door.ox = door.ix; - door.oy = door.iy; + if(cb.load_warp) + cb.load_warp(cb.userdata, door); } - - door.meta.array_id = FileData.doors_array_id++; - door.meta.index = static_cast(FileData.doors.size()); - FileData.doors.push_back(door); - } - }//DOORS - ///////////////////LAYERS////////////////////// - PGEX_Section("LAYERS") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + }//DOORS + ///////////////////LAYERS////////////////////// + PGEX_Section("LAYERS") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - layer = CreateLvlLayer(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_StrVal("LR", layer.name) //Layer name - PGEX_BoolVal("HD", layer.hidden) //Hidden - PGEX_BoolVal("LC", layer.locked) //Locked - } - //add captured value into array - bool found = false; - pge_size_t q = 0; - for(q = 0; q < FileData.layers.size(); q++) - { - if(FileData.layers[q].name == layer.name) + PGEX_ItemBegin(PGEFile::PGEX_Struct) + layer = CreateLvlLayer(); + PGEX_Values() //Look markers and values { - found = true; - break; + PGEX_ValueBegin() + PGEX_StrVal("LR", layer.name) //Layer name + PGEX_BoolVal("HD", layer.hidden) //Hidden + PGEX_BoolVal("LC", layer.locked) //Locked } - } - if(found) - { - layer.meta.array_id = FileData.layers[q].meta.array_id; - FileData.layers[q] = layer; - } - else - { - layer.meta.array_id = FileData.layers_array_id++; - FileData.layers.push_back(layer); + if(cb.load_layer) + cb.load_layer(cb.userdata, layer); } - } - }//LAYERS - //EVENTS comming soon - // else - // if(sct.first=="EVENTS_CLASSIC") //Action-styled events - // { - // foreach(PGESTRINGList value, sectData) //Look markers and values - // { - // // if(v.marker=="TL") //Level Title - // // { - // // if(PGEFile::IsQStr(v.value)) - // // FileData.LevelName = PGEFile::X2STR(v.value); - // // else - // // goto badfile; - // // } - // // else - // // if(v.marker=="SZ") //Starz number - // // { - // // if(PGEFile::IsIntU(v.value)) - // // FileData.stars = toInt(v.value); - // // else - // // goto badfile; - // // } - // } - // }//EVENTS - ///////////////////EVENTS_CLASSIC////////////////////// - PGEX_Section("EVENTS_CLASSIC") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + }//LAYERS + //EVENTS comming soon + // else + // if(sct.first=="EVENTS_CLASSIC") //Action-styled events + // { + // foreach(PGESTRINGList value, sectData) //Look markers and values + // { + // // if(v.marker=="TL") //Level Title + // // { + // // if(PGEFile::IsQStr(v.value)) + // // FileData.LevelName = PGEFile::X2STR(v.value); + // // else + // // goto badfile; + // // } + // // else + // // if(v.marker=="SZ") //Starz number + // // { + // // if(PGEFile::IsIntU(v.value)) + // // FileData.stars = toInt(v.value); + // // else + // // goto badfile; + // // } + // } + // }//EVENTS + ///////////////////EVENTS_CLASSIC////////////////////// + PGEX_Section("EVENTS_CLASSIC") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - event = CreateLvlEvent(); - PGESTRINGList musicSets; - pge_size_t musicSets_begin = 0; - PGESTRINGList bgSets; - pge_size_t bgSets_begin = 0; - PGESTRINGList ssSets; - pge_size_t ssSets_begin = 0; - PGESTRINGList movingLayers; - pge_size_t movingLayers_begin = 0; - PGESTRINGList newSectionSettingsSets; - int newSectionSettingsSets_begin = -1; - PGESTRINGList spawnNPCs; - pge_size_t spawnNPCs_begin = 0; - PGESTRINGList spawnEffectss; - pge_size_t spawnEffectss_begin = 0; - PGESTRINGList variablesToUpdate; - pge_size_t variablesToUpdate_begin = 0; - PGELIST controls; - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_StrVal("ET", event.name) //Event Title - PGEX_StrVal("MG", event.msg) //Event Message - PGEX_USLongVal("SD", event.sound_id) //Play Sound ID - PGEX_USLongVal("EG", event.end_game) //End game algorithm - PGEX_StrArrVal("LH", event.layers_hide) //Hide layers - PGEX_StrArrVal("LS", event.layers_show) //Show layers - PGEX_StrArrVal("LT", event.layers_toggle) //Toggle layers - //Legacy values (without SMBX-38A values support) - PGEX_StrArrVal_Validate("SM", musicSets, musicSets_begin) //Switch music - PGEX_StrArrVal_Validate("SB", bgSets, bgSets_begin) //Switch background - PGEX_StrArrVal_Validate("SS", ssSets, ssSets_begin) //Section Size - //------------------- - //New values (with SMBX-38A values support) - PGEX_StrArrVal_Validate("SSS", newSectionSettingsSets, newSectionSettingsSets_begin) //Section settings in new format - //------------------- - //---SMBX-38A entries----- - PGEX_StrArrVal_Validate("MLA", movingLayers, movingLayers_begin) //NPC's to spawn - PGEX_StrArrVal_Validate("SNPC", spawnNPCs, spawnNPCs_begin) //NPC's to spawn - PGEX_StrArrVal_Validate("SEF", spawnEffectss, spawnEffectss_begin) //Effects to spawn - PGEX_StrArrVal_Validate("UV", variablesToUpdate, variablesToUpdate_begin) //Variables to update - PGEX_StrVal("TSCR", event.trigger_script) //Trigger script - PGEX_USIntVal("TAPI", event.trigger_api_id) //Trigger script - PGEX_BoolVal("TMR", event.timer_def.enable) //Enable timer - PGEX_USLongVal("TMC", event.timer_def.count) //Count of timer units - PGEX_FloatVal("TMI", event.timer_def.interval) //Interval of timer tick - PGEX_USIntVal("TMD", event.timer_def.count_dir) //Direction of count - PGEX_BoolVal("TMV", event.timer_def.show) //Show timer on screen - //------------------- - PGEX_StrVal("TE", event.trigger) //Trigger event - PGEX_USLongVal("TD", event.trigger_timer) //Trigger delay - PGEX_BoolVal("DS", event.nosmoke) //Disable smoke - PGEX_USIntVal("AU", event.autostart) //Auto start - PGEX_StrVal("AUC", event.autostart_condition) //Auto start condition - PGEX_BoolArrVal("PC", controls) //Player controls - PGEX_StrVal("ML", event.movelayer) //Move layer - PGEX_FloatVal("MX", event.layer_speed_x) //Layer motion speed X - PGEX_FloatVal("MY", event.layer_speed_y) //Layer motion speed Y - PGEX_SLongVal("AS", event.scroll_section) //Autoscroll section ID - PGEX_FloatVal("AX", event.move_camera_x) //Autoscroll speed X - PGEX_FloatVal("AY", event.move_camera_y) //Autoscroll speed Y - } - - //Parse new-style parameters - if(newSectionSettingsSets_begin != -1) - { - for(pge_size_t i = 0; i < newSectionSettingsSets.size(); i++) + PGEX_ItemBegin(PGEFile::PGEX_Struct) + event = CreateLvlEvent(); + PGESTRINGList musicSets; + pge_size_t musicSets_begin = 0; + PGESTRINGList bgSets; + pge_size_t bgSets_begin = 0; + PGESTRINGList ssSets; + pge_size_t ssSets_begin = 0; + PGESTRINGList movingLayers; + pge_size_t movingLayers_begin = 0; + PGESTRINGList newSectionSettingsSets; + int newSectionSettingsSets_begin = -1; + PGESTRINGList spawnNPCs; + pge_size_t spawnNPCs_begin = 0; + PGESTRINGList spawnEffectss; + pge_size_t spawnEffectss_begin = 0; + PGESTRINGList variablesToUpdate; + pge_size_t variablesToUpdate_begin = 0; + PGELIST controls; + PGEX_Values() //Look markers and values { - const auto &newSectionSettingsSet = newSectionSettingsSets[i]; - - LevelEvent_Sets sectionSet; - bool valid = false; - PGELIST sssData = PGEFile::splitDataLine(newSectionSettingsSet, &valid); + PGEX_ValueBegin() + PGEX_StrVal("ET", event.name) //Event Title + PGEX_StrVal("MG", event.msg) //Event Message + PGEX_USLongVal("SD", event.sound_id) //Play Sound ID + PGEX_USLongVal("EG", event.end_game) //End game algorithm + PGEX_StrArrVal("LH", event.layers_hide) //Hide layers + PGEX_StrArrVal("LS", event.layers_show) //Show layers + PGEX_StrArrVal("LT", event.layers_toggle) //Toggle layers + //Legacy values (without SMBX-38A values support) + PGEX_StrArrVal_Validate("SM", musicSets, musicSets_begin) //Switch music + PGEX_StrArrVal_Validate("SB", bgSets, bgSets_begin) //Switch background + PGEX_StrArrVal_Validate("SS", ssSets, ssSets_begin) //Section Size + //------------------- + //New values (with SMBX-38A values support) + PGEX_StrArrVal_Validate("SSS", newSectionSettingsSets, newSectionSettingsSets_begin) //Section settings in new format + //------------------- + //---SMBX-38A entries----- + PGEX_StrArrVal_Validate("MLA", movingLayers, movingLayers_begin) //NPC's to spawn + PGEX_StrArrVal_Validate("SNPC", spawnNPCs, spawnNPCs_begin) //NPC's to spawn + PGEX_StrArrVal_Validate("SEF", spawnEffectss, spawnEffectss_begin) //Effects to spawn + PGEX_StrArrVal_Validate("UV", variablesToUpdate, variablesToUpdate_begin) //Variables to update + PGEX_StrVal("TSCR", event.trigger_script) //Trigger script + PGEX_USIntVal("TAPI", event.trigger_api_id) //Trigger script + PGEX_BoolVal("TMR", event.timer_def.enable) //Enable timer + PGEX_USLongVal("TMC", event.timer_def.count) //Count of timer units + PGEX_FloatVal("TMI", event.timer_def.interval) //Interval of timer tick + PGEX_USIntVal("TMD", event.timer_def.count_dir) //Direction of count + PGEX_BoolVal("TMV", event.timer_def.show) //Show timer on screen + //------------------- + PGEX_StrVal("TE", event.trigger) //Trigger event + PGEX_USLongVal("TD", event.trigger_timer) //Trigger delay + PGEX_BoolVal("DS", event.nosmoke) //Disable smoke + PGEX_USIntVal("AU", event.autostart) //Auto start + PGEX_StrVal("AUC", event.autostart_condition) //Auto start condition + PGEX_BoolArrVal("PC", controls) //Player controls + PGEX_StrVal("ML", event.movelayer) //Move layer + PGEX_FloatVal("MX", event.layer_speed_x) //Layer motion speed X + PGEX_FloatVal("MY", event.layer_speed_y) //Layer motion speed Y + PGEX_SLongVal("AS", event.scroll_section) //Autoscroll section ID + PGEX_FloatVal("AX", event.move_camera_x) //Autoscroll speed X + PGEX_FloatVal("AY", event.move_camera_y) //Autoscroll speed Y + } - if(!valid) + //Parse new-style parameters + if(newSectionSettingsSets_begin != -1) + { + for(pge_size_t i = 0; i < newSectionSettingsSets.size(); i++) { - errorString = "Wrong section settings event encoded sub-entry"; - goto badfile; - } + const auto &newSectionSettingsSet = newSectionSettingsSets[i]; - for(auto ¶m : sssData) - { - if(param[0] == "ID") - { - errorString = "Invalid sectionID value type"; + LevelEvent_Sets sectionSet; + bool valid = false; + PGELIST sssData = PGEFile::splitDataLine(newSectionSettingsSet, &valid); - if(PGEFile::IsIntU(param[1])) - sectionSet.id = toLong(param[1]); - else - goto badfile; - } - else if(param[0] == "SL") + if(!valid) { - errorString = "Invalid Section size left value type"; - - if(PGEFile::IsIntS(param[1])) - sectionSet.position_left = toLong(param[1]); - else - goto badfile; + errorString = "Wrong section settings event encoded sub-entry"; + goto badfile; } - else if(param[0] == "ST") - { - errorString = "Invalid Section size top value type"; - if(PGEFile::IsIntS(param[1])) - sectionSet.position_top = toLong(param[1]); - else - goto badfile; - } - else if(param[0] == "SB") + for(auto ¶m : sssData) { - errorString = "Invalid Section size bottom value type"; + if(param[0] == "ID") + { + errorString = "Invalid sectionID value type"; - if(PGEFile::IsIntS(param[1])) - sectionSet.position_bottom = toLong(param[1]); - else - goto badfile; - } - else if(param[0] == "SR") - { - errorString = "Invalid Section size right value type"; + if(PGEFile::IsIntU(param[1])) + sectionSet.id = toLong(param[1]); + else + goto badfile; + } + else if(param[0] == "SL") + { + errorString = "Invalid Section size left value type"; - if(PGEFile::IsIntS(param[1])) - sectionSet.position_right = toLong(param[1]); - else - goto badfile; - } - else if(param[0] == "SXX") - { - errorString = "Invalid Section pos x expression value type"; + if(PGEFile::IsIntS(param[1])) + sectionSet.position_left = toLong(param[1]); + else + goto badfile; + } + else if(param[0] == "ST") + { + errorString = "Invalid Section size top value type"; - if(PGEFile::IsQoutedString(param[1])) - sectionSet.expression_pos_x = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "SYX") - { - errorString = "Invalid Section pos y expression value type"; + if(PGEFile::IsIntS(param[1])) + sectionSet.position_top = toLong(param[1]); + else + goto badfile; + } + else if(param[0] == "SB") + { + errorString = "Invalid Section size bottom value type"; - if(PGEFile::IsQoutedString(param[1])) - sectionSet.expression_pos_y = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "SWX") - { - errorString = "Invalid Section pos w expression value type"; + if(PGEFile::IsIntS(param[1])) + sectionSet.position_bottom = toLong(param[1]); + else + goto badfile; + } + else if(param[0] == "SR") + { + errorString = "Invalid Section size right value type"; - if(PGEFile::IsQoutedString(param[1])) - sectionSet.expression_pos_w = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "SHX") - { - errorString = "Invalid Section pos h expression value type"; + if(PGEFile::IsIntS(param[1])) + sectionSet.position_right = toLong(param[1]); + else + goto badfile; + } + else if(param[0] == "SXX") + { + errorString = "Invalid Section pos x expression value type"; - if(PGEFile::IsQoutedString(param[1])) - sectionSet.expression_pos_h = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "MI") - { - errorString = "Invalid Section music ID value type"; + if(PGEFile::IsQoutedString(param[1])) + sectionSet.expression_pos_x = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "SYX") + { + errorString = "Invalid Section pos y expression value type"; - if(PGEFile::IsIntS(param[1])) - sectionSet.music_id = toLong(param[1]); - else - goto badfile; - } - else if(param[0] == "MF") - { - errorString = "Invalid Section music file value type"; + if(PGEFile::IsQoutedString(param[1])) + sectionSet.expression_pos_y = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "SWX") + { + errorString = "Invalid Section pos w expression value type"; - if(PGEFile::IsQoutedString(param[1])) - sectionSet.music_file = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "ME") - { - errorString = "Invalid Section music file value type"; + if(PGEFile::IsQoutedString(param[1])) + sectionSet.expression_pos_w = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "SHX") + { + errorString = "Invalid Section pos h expression value type"; - if(PGEFile::IsIntS(param[1])) - sectionSet.music_file_idx = toInt(param[1]); - else - goto badfile; - } - else if(param[0] == "BG") - { - errorString = "Invalid Section background ID value type"; + if(PGEFile::IsQoutedString(param[1])) + sectionSet.expression_pos_h = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "MI") + { + errorString = "Invalid Section music ID value type"; - if(PGEFile::IsIntS(param[1])) - sectionSet.background_id = toLong(param[1]); - else - goto badfile; - } - else if(param[0] == "AS") - { - errorString = "Invalid Section Autoscroll value type"; + if(PGEFile::IsIntS(param[1])) + sectionSet.music_id = toLong(param[1]); + else + goto badfile; + } + else if(param[0] == "MF") + { + errorString = "Invalid Section music file value type"; - if(PGEFile::IsBool(param[1])) - sectionSet.autoscrol = static_cast(toInt(param[1]) != 0); - else - goto badfile; - } - else if(param[0] == "AST") - { - errorString = "Invalid Section Autoscroll type value type"; + if(PGEFile::IsQoutedString(param[1])) + sectionSet.music_file = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "ME") + { + errorString = "Invalid Section music file value type"; - if(PGEFile::IsIntU(param[1])) - sectionSet.autoscroll_style = toInt(param[1]); - else - goto badfile; - } - else if(param[0] == "ASP") - { - errorString = "Invalid Section Autoscroll path value type"; + if(PGEFile::IsIntS(param[1])) + sectionSet.music_file_idx = toInt(param[1]); + else + goto badfile; + } + else if(param[0] == "BG") + { + errorString = "Invalid Section background ID value type"; - if(PGEFile::IsIntArray(param[1])) + if(PGEFile::IsIntS(param[1])) + sectionSet.background_id = toLong(param[1]); + else + goto badfile; + } + else if(param[0] == "AS") { - bool valid2 = false; - PGELIST arr = PGEFile::X2IntArr(param[1], &valid2); - if(!valid2) + errorString = "Invalid Section Autoscroll value type"; + + if(PGEFile::IsBool(param[1])) + sectionSet.autoscrol = static_cast(toInt(param[1]) != 0); + else goto badfile; - if(arr.size() % 4) - { - errorString = "Invalid Section Autoscroll path data contains non-multiple 4 entries"; + } + else if(param[0] == "AST") + { + errorString = "Invalid Section Autoscroll type value type"; + + if(PGEFile::IsIntU(param[1])) + sectionSet.autoscroll_style = toInt(param[1]); + else goto badfile; - } - for(pge_size_t pe = 0; pe < arr.size(); pe += 4) + } + else if(param[0] == "ASP") + { + errorString = "Invalid Section Autoscroll path value type"; + + if(PGEFile::IsIntArray(param[1])) { - LevelEvent_Sets::AutoScrollStopPoint stop; - stop.x = arr[pe + 0]; - stop.y = arr[pe + 1]; - stop.type = (int)arr[pe + 2]; - stop.speed = arr[pe + 3]; - sectionSet.autoscroll_path.push_back(stop); + bool valid2 = false; + PGELIST arr = PGEFile::X2IntArr(param[1], &valid2); + if(!valid2) + goto badfile; + if(arr.size() % 4) + { + errorString = "Invalid Section Autoscroll path data contains non-multiple 4 entries"; + goto badfile; + } + for(pge_size_t pe = 0; pe < arr.size(); pe += 4) + { + LevelEvent_Sets::AutoScrollStopPoint stop; + stop.x = arr[pe + 0]; + stop.y = arr[pe + 1]; + stop.type = (int)arr[pe + 2]; + stop.speed = arr[pe + 3]; + sectionSet.autoscroll_path.push_back(stop); + } } + else + goto badfile; } - else - goto badfile; - } - else if(param[0] == "AX") - { - errorString = "Invalid Section Autoscroll X value type"; + else if(param[0] == "AX") + { + errorString = "Invalid Section Autoscroll X value type"; - if(PGEFile::IsFloat(param[1])) - sectionSet.autoscrol_x = toFloat(param[1]); - else - goto badfile; - } - else if(param[0] == "AY") - { - errorString = "Invalid Section Autoscroll Y value type"; + if(PGEFile::IsFloat(param[1])) + sectionSet.autoscrol_x = toFloat(param[1]); + else + goto badfile; + } + else if(param[0] == "AY") + { + errorString = "Invalid Section Autoscroll Y value type"; - if(PGEFile::IsFloat(param[1])) - sectionSet.autoscrol_y = toFloat(param[1]); - else - goto badfile; - } - else if(param[0] == "AXX") - { - errorString = "Invalid Section Autoscroll X expression value type"; + if(PGEFile::IsFloat(param[1])) + sectionSet.autoscrol_y = toFloat(param[1]); + else + goto badfile; + } + else if(param[0] == "AXX") + { + errorString = "Invalid Section Autoscroll X expression value type"; - if(PGEFile::IsQoutedString(param[1])) - sectionSet.expression_autoscrool_x = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "AYX") - { - errorString = "Invalid Section Autoscroll y expression value type"; + if(PGEFile::IsQoutedString(param[1])) + sectionSet.expression_autoscrool_x = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "AYX") + { + errorString = "Invalid Section Autoscroll y expression value type"; - if(PGEFile::IsQoutedString(param[1])) - sectionSet.expression_autoscrool_y = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - }//for parameters + if(PGEFile::IsQoutedString(param[1])) + sectionSet.expression_autoscrool_y = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + }//for parameters - // skip after validating if the field got duplicated - if(i < (pge_size_t)newSectionSettingsSets_begin) - continue; + // skip after validating if the field got duplicated + if(i < (pge_size_t)newSectionSettingsSets_begin) + continue; - // TODO: remove this logic (duplicated in the load callback) - if( - ((sectionSet.id < 0) || (sectionSet.id >= static_cast(event.sets.size()))) - )//Append sections - { - if((sectionSet.id < 0) || (sectionSet.id > 1000)) + // removed this logic (duplicated in the event load callback) +#if 0 + if( + ((sectionSet.id < 0) || (sectionSet.id >= static_cast(event.sets.size()))) + )//Append sections { - errorString = "Section settings event contains negative section ID value or missed!"; - goto badfile;//Missmatched section ID! - } + if((sectionSet.id < 0) || (sectionSet.id > 1000)) + { + errorString = "Section settings event contains negative section ID value or missed!"; + goto badfile;//Missmatched section ID! + } - long last = static_cast(event.sets.size() - 1); + long last = static_cast(event.sets.size() - 1); - while(sectionSet.id >= static_cast(event.sets.size())) - { - LevelEvent_Sets set; - set.id = last; - event.sets.push_back(set); - last++; + while(sectionSet.id >= static_cast(event.sets.size())) + { + LevelEvent_Sets set; + set.id = last; + event.sets.push_back(set); + last++; + } } - } - event.sets[static_cast(sectionSet.id)] = sectionSet; - }//for section settings entries + event.sets[static_cast(sectionSet.id)] = sectionSet; +#endif + event.sets.push_back(sectionSet); + }//for section settings entries - // skip over (but validate) legacy arrays - musicSets_begin = musicSets.size(); - bgSets_begin = bgSets.size(); - ssSets_begin = ssSets.size(); - }//If new-styled section settings are gotten + // skip over (but validate) legacy arrays + musicSets_begin = musicSets.size(); + bgSets_begin = bgSets.size(); + ssSets_begin = ssSets.size(); + }//If new-styled section settings are gotten - //Apply old MusicSets (if presented) - for(pge_size_t q = 0; q < musicSets.size(); q++) - { - if(!PGEFile::IsIntS(musicSets[q])) goto badfile; - long got = toLong(musicSets[q]); - - if(q < musicSets_begin) - continue; - - pge_size_t s_i = q - musicSets_begin; - if(s_i >= event.sets.size()) - continue; - - auto &s = event.sets[s_i]; - s.id = static_cast(q); - s.music_id = got; - } + //Apply old MusicSets (if presented) + for(pge_size_t q = 0; q < musicSets.size(); q++) + { + if(!PGEFile::IsIntS(musicSets[q])) goto badfile; + long got = toLong(musicSets[q]); - //Apply old Background sets (if presented) - for(pge_size_t q = 0; q < bgSets.size(); q++) - { - if(!PGEFile::IsIntS(bgSets[q])) goto badfile; - long got = toLong(bgSets[q]); + if(q < musicSets_begin) + continue; - if(q < bgSets_begin) - continue; + pge_size_t s_i = q - musicSets_begin; + if(s_i >= event.sets.size()) + continue; - pge_size_t s_i = q - bgSets_begin; - if(s_i >= event.sets.size()) - continue; + auto &s = event.sets[s_i]; + s.id = static_cast(q); + s.music_id = got; + } - auto &s = event.sets[s_i]; - s.id = static_cast(q); - s.background_id = got; - } + //Apply old Background sets (if presented) + for(pge_size_t q = 0; q < bgSets.size(); q++) + { + if(!PGEFile::IsIntS(bgSets[q])) goto badfile; + long got = toLong(bgSets[q]); - //Apply old Background sets (if presented) - for(pge_size_t q = 0; q < ssSets.size(); q++) - { - PGESTRINGList sizes; - PGE_SPLITSTRING(sizes, ssSets[q], ","); + if(q < bgSets_begin) + continue; - if(sizes.size() != 4) goto badfile; //-V112 + pge_size_t s_i = q - bgSets_begin; + if(s_i >= event.sets.size()) + continue; - if(!PGEFile::IsIntS(sizes[0])) goto badfile; - if(!PGEFile::IsIntS(sizes[1])) goto badfile; - if(!PGEFile::IsIntS(sizes[2])) goto badfile; - if(!PGEFile::IsIntS(sizes[3])) goto badfile; + auto &s = event.sets[s_i]; + s.id = static_cast(q); + s.background_id = got; + } - long got[4]; - for(int i = 0; i < 4; i++) + //Apply old Background sets (if presented) + for(pge_size_t q = 0; q < ssSets.size(); q++) { - if(!PGEFile::IsIntS(sizes[i])) goto badfile; - got[i] = toLong(sizes[i]); - } + PGESTRINGList sizes; + PGE_SPLITSTRING(sizes, ssSets[q], ","); - if(q < ssSets_begin) - continue; + if(sizes.size() != 4) goto badfile; //-V112 - pge_size_t s_i = q - ssSets_begin; - if(s_i >= event.sets.size()) - continue; + if(!PGEFile::IsIntS(sizes[0])) goto badfile; + if(!PGEFile::IsIntS(sizes[1])) goto badfile; + if(!PGEFile::IsIntS(sizes[2])) goto badfile; + if(!PGEFile::IsIntS(sizes[3])) goto badfile; - auto &s = event.sets[s_i]; - s.id = static_cast(q); - s.position_left = got[0]; - s.position_top = toLong(sizes[1]); - s.position_bottom = toLong(sizes[2]); - s.position_right = toLong(sizes[3]); - } + long got[4]; + for(int i = 0; i < 4; i++) + { + if(!PGEFile::IsIntS(sizes[i])) goto badfile; + got[i] = toLong(sizes[i]); + } + if(q < ssSets_begin) + continue; - //Parse Moving layers - if(!movingLayers.empty()) - { - for(pge_size_t i = 0; i < movingLayers.size(); i++) - { - const auto &movingLayer = movingLayers[i]; + pge_size_t s_i = q - ssSets_begin; + if(s_i >= event.sets.size()) + continue; - LevelEvent_MoveLayer moveLayer; - bool valid = false; - PGELIST mlaData = PGEFile::splitDataLine(movingLayer, &valid); + auto &s = event.sets[s_i]; + s.id = static_cast(q); + s.position_left = got[0]; + s.position_top = toLong(sizes[1]); + s.position_bottom = toLong(sizes[2]); + s.position_right = toLong(sizes[3]); + } - if(!valid) - { - errorString = "Wrong Move layer event encoded sub-entry"; - goto badfile; - } - for(auto ¶m : mlaData) + //Parse Moving layers + if(!movingLayers.empty()) + { + for(pge_size_t i = 0; i < movingLayers.size(); i++) { - if(param[0] == "LN") - { - errorString = "Invalid Moving layer name value type"; + const auto &movingLayer = movingLayers[i]; - if(PGEFile::IsQoutedString(param[1])) - moveLayer.name = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "SX") - { - errorString = "Invalid movelayer speed X value type"; + LevelEvent_MoveLayer moveLayer; + bool valid = false; + PGELIST mlaData = PGEFile::splitDataLine(movingLayer, &valid); - if(PGEFile::IsFloat(param[1])) - moveLayer.speed_x = toDouble(param[1]); - else - goto badfile; - } - else if(param[0] == "SY") + if(!valid) { - errorString = "Invalid movelayer speed Y value type"; - - if(PGEFile::IsFloat(param[1])) - moveLayer.speed_y = toDouble(param[1]); - else - goto badfile; + errorString = "Wrong Move layer event encoded sub-entry"; + goto badfile; } - else if(param[0] == "SXX") - { - errorString = "Invalid movelayer speed X expression value type"; - if(PGEFile::IsQoutedString(param[1])) - moveLayer.expression_x = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "SYX") + for(auto ¶m : mlaData) { - errorString = "Invalid movelayer speed Y expression value type"; + if(param[0] == "LN") + { + errorString = "Invalid Moving layer name value type"; - if(PGEFile::IsQoutedString(param[1])) - moveLayer.expression_y = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "MW") - { - errorString = "Invalid movelayer way type value type"; + if(PGEFile::IsQoutedString(param[1])) + moveLayer.name = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "SX") + { + errorString = "Invalid movelayer speed X value type"; - if(PGEFile::IsIntU(param[1])) - moveLayer.way = toInt(param[1]); - else - goto badfile; - } - }//for parameters + if(PGEFile::IsFloat(param[1])) + moveLayer.speed_x = toDouble(param[1]); + else + goto badfile; + } + else if(param[0] == "SY") + { + errorString = "Invalid movelayer speed Y value type"; - // skip after validating if the field got duplicated - if(i < movingLayers_begin) - continue; + if(PGEFile::IsFloat(param[1])) + moveLayer.speed_y = toDouble(param[1]); + else + goto badfile; + } + else if(param[0] == "SXX") + { + errorString = "Invalid movelayer speed X expression value type"; + + if(PGEFile::IsQoutedString(param[1])) + moveLayer.expression_x = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "SYX") + { + errorString = "Invalid movelayer speed Y expression value type"; - event.moving_layers.push_back(moveLayer); - }//for moving layers entries - }//If SMBX38A moving layers are gotten + if(PGEFile::IsQoutedString(param[1])) + moveLayer.expression_y = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "MW") + { + errorString = "Invalid movelayer way type value type"; - //Parse NPCs to spawn - if(!spawnNPCs.empty()) - { - for(pge_size_t i = 0; i < spawnNPCs.size(); i++) - { - auto &spawnNpc = spawnNPCs[i]; + if(PGEFile::IsIntU(param[1])) + moveLayer.way = toInt(param[1]); + else + goto badfile; + } + }//for parameters - LevelEvent_SpawnNPC spawnNPC; - bool valid = false; - PGELIST mlaData = PGEFile::splitDataLine(spawnNpc, &valid); + // skip after validating if the field got duplicated + if(i < movingLayers_begin) + continue; - if(!valid) - { - errorString = "Wrong Spawn NPC event encoded sub-entry"; - goto badfile; - } + event.moving_layers.push_back(moveLayer); + }//for moving layers entries + }//If SMBX38A moving layers are gotten - for(auto ¶m : mlaData) + //Parse NPCs to spawn + if(!spawnNPCs.empty()) + { + for(pge_size_t i = 0; i < spawnNPCs.size(); i++) { - if(param[0] == "ID") - { - errorString = "Invalid Spawn NPC ID value type"; + auto &spawnNpc = spawnNPCs[i]; - if(PGEFile::IsIntU(param[1])) - spawnNPC.id = toLong(param[1]); - else - goto badfile; - } - else if(param[0] == "SX") - { - errorString = "Invalid Spawn NPC X value type"; + LevelEvent_SpawnNPC spawnNPC; + bool valid = false; + PGELIST mlaData = PGEFile::splitDataLine(spawnNpc, &valid); - if(PGEFile::IsFloat(param[1])) - spawnNPC.x = static_cast(toFloat(param[1])); - else - goto badfile; - } - else if(param[0] == "SY") + if(!valid) { - errorString = "Invalid Spawn NPC Y value type"; - - if(PGEFile::IsFloat(param[1])) - spawnNPC.y = static_cast(toFloat(param[1])); - else - goto badfile; + errorString = "Wrong Spawn NPC event encoded sub-entry"; + goto badfile; } - else if(param[0] == "SXX") - { - errorString = "Invalid Spawn NPC X expression value type"; - if(PGEFile::IsQoutedString(param[1])) - spawnNPC.expression_x = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "SYX") + for(auto ¶m : mlaData) { - errorString = "Invalid Spawn NPC X expression value type"; + if(param[0] == "ID") + { + errorString = "Invalid Spawn NPC ID value type"; - if(PGEFile::IsQoutedString(param[1])) - spawnNPC.expression_y = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "SSX") - { - errorString = "Invalid Spawn NPC X value type"; + if(PGEFile::IsIntU(param[1])) + spawnNPC.id = toLong(param[1]); + else + goto badfile; + } + else if(param[0] == "SX") + { + errorString = "Invalid Spawn NPC X value type"; - if(PGEFile::IsFloat(param[1])) - spawnNPC.speed_x = toFloat(param[1]); - else - goto badfile; - } - else if(param[0] == "SSY") - { - errorString = "Invalid Spawn NPC Y value type"; + if(PGEFile::IsFloat(param[1])) + spawnNPC.x = static_cast(toFloat(param[1])); + else + goto badfile; + } + else if(param[0] == "SY") + { + errorString = "Invalid Spawn NPC Y value type"; - if(PGEFile::IsFloat(param[1])) - spawnNPC.speed_y = toFloat(param[1]); - else - goto badfile; - } - else if(param[0] == "SSXX") - { - errorString = "Invalid Spawn NPC Speed X expression value type"; + if(PGEFile::IsFloat(param[1])) + spawnNPC.y = static_cast(toFloat(param[1])); + else + goto badfile; + } + else if(param[0] == "SXX") + { + errorString = "Invalid Spawn NPC X expression value type"; - if(PGEFile::IsQoutedString(param[1])) - spawnNPC.expression_sx = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "SSYX") - { - errorString = "Invalid Spawn NPC Speed Y expression value type"; + if(PGEFile::IsQoutedString(param[1])) + spawnNPC.expression_x = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "SYX") + { + errorString = "Invalid Spawn NPC X expression value type"; - if(PGEFile::IsQoutedString(param[1])) - spawnNPC.expression_sy = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "SSS") - { - errorString = "Invalid Spawn NPC Special value type"; + if(PGEFile::IsQoutedString(param[1])) + spawnNPC.expression_y = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "SSX") + { + errorString = "Invalid Spawn NPC X value type"; - if(PGEFile::IsIntU(param[1])) - spawnNPC.special = toLong(param[1]); - else - goto badfile; - } - }//for parameters + if(PGEFile::IsFloat(param[1])) + spawnNPC.speed_x = toFloat(param[1]); + else + goto badfile; + } + else if(param[0] == "SSY") + { + errorString = "Invalid Spawn NPC Y value type"; - // skip after validating if the field got duplicated - if(i < spawnNPCs_begin) - continue; + if(PGEFile::IsFloat(param[1])) + spawnNPC.speed_y = toFloat(param[1]); + else + goto badfile; + } + else if(param[0] == "SSXX") + { + errorString = "Invalid Spawn NPC Speed X expression value type"; + + if(PGEFile::IsQoutedString(param[1])) + spawnNPC.expression_sx = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "SSYX") + { + errorString = "Invalid Spawn NPC Speed Y expression value type"; - event.spawn_npc.push_back(spawnNPC); - }//for Spawn NPC - }//If SMBX38A NPC Spawning lists are gotten + if(PGEFile::IsQoutedString(param[1])) + spawnNPC.expression_sy = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "SSS") + { + errorString = "Invalid Spawn NPC Special value type"; - //Parse Effects to spawn - if(!spawnEffectss.empty()) - { - for(pge_size_t i = 0; i < spawnEffectss.size(); i++) - { - const auto &spawnEffects = spawnEffectss[i]; + if(PGEFile::IsIntU(param[1])) + spawnNPC.special = toLong(param[1]); + else + goto badfile; + } + }//for parameters - LevelEvent_SpawnEffect spawnEffect; - bool valid = false; - PGELIST mlaData = PGEFile::splitDataLine(spawnEffects, &valid); + // skip after validating if the field got duplicated + if(i < spawnNPCs_begin) + continue; - if(!valid) - { - errorString = "Wrong Spawn Effect event encoded sub-entry"; - goto badfile; - } + event.spawn_npc.push_back(spawnNPC); + }//for Spawn NPC + }//If SMBX38A NPC Spawning lists are gotten - for(auto ¶m : mlaData) + //Parse Effects to spawn + if(!spawnEffectss.empty()) + { + for(pge_size_t i = 0; i < spawnEffectss.size(); i++) { - if(param[0] == "ID") - { - errorString = "Invalid Spawn Effect ID value type"; + const auto &spawnEffects = spawnEffectss[i]; - if(PGEFile::IsIntU(param[1])) - spawnEffect.id = toLong(param[1]); - else - goto badfile; - } - else if(param[0] == "SX") - { - errorString = "Invalid Spawn Effect X value type"; + LevelEvent_SpawnEffect spawnEffect; + bool valid = false; + PGELIST mlaData = PGEFile::splitDataLine(spawnEffects, &valid); - if(PGEFile::IsFloat(param[1])) - spawnEffect.x = static_cast(toFloat(param[1])); - else - goto badfile; - } - else if(param[0] == "SY") + if(!valid) { - errorString = "Invalid Spawn Effect Y value type"; - - if(PGEFile::IsFloat(param[1])) - spawnEffect.y = static_cast(toFloat(param[1])); - else - goto badfile; + errorString = "Wrong Spawn Effect event encoded sub-entry"; + goto badfile; } - else if(param[0] == "SXX") - { - errorString = "Invalid Spawn NPC X expression value type"; - if(PGEFile::IsQoutedString(param[1])) - spawnEffect.expression_x = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "SYX") + for(auto ¶m : mlaData) { - errorString = "Invalid Spawn NPC X expression value type"; + if(param[0] == "ID") + { + errorString = "Invalid Spawn Effect ID value type"; - if(PGEFile::IsQoutedString(param[1])) - spawnEffect.expression_y = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "SSX") - { - errorString = "Invalid Spawn NPC X value type"; + if(PGEFile::IsIntU(param[1])) + spawnEffect.id = toLong(param[1]); + else + goto badfile; + } + else if(param[0] == "SX") + { + errorString = "Invalid Spawn Effect X value type"; - if(PGEFile::IsFloat(param[1])) - spawnEffect.speed_x = toDouble(param[1]); - else - goto badfile; - } - else if(param[0] == "SSY") - { - errorString = "Invalid Spawn NPC Y value type"; + if(PGEFile::IsFloat(param[1])) + spawnEffect.x = static_cast(toFloat(param[1])); + else + goto badfile; + } + else if(param[0] == "SY") + { + errorString = "Invalid Spawn Effect Y value type"; - if(PGEFile::IsFloat(param[1])) - spawnEffect.speed_y = toDouble(param[1]); - else - goto badfile; - } - else if(param[0] == "SSXX") - { - errorString = "Invalid Spawn NPC Speed X expression value type"; + if(PGEFile::IsFloat(param[1])) + spawnEffect.y = static_cast(toFloat(param[1])); + else + goto badfile; + } + else if(param[0] == "SXX") + { + errorString = "Invalid Spawn NPC X expression value type"; - if(PGEFile::IsQoutedString(param[1])) - spawnEffect.expression_sx = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "SSYX") - { - errorString = "Invalid Spawn NPC Speed Y expression value type"; + if(PGEFile::IsQoutedString(param[1])) + spawnEffect.expression_x = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "SYX") + { + errorString = "Invalid Spawn NPC X expression value type"; - if(PGEFile::IsQoutedString(param[1])) - spawnEffect.expression_sy = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - else if(param[0] == "FP") - { - errorString = "Invalid Spawn Effect FPS value type"; + if(PGEFile::IsQoutedString(param[1])) + spawnEffect.expression_y = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "SSX") + { + errorString = "Invalid Spawn NPC X value type"; - if(PGEFile::IsIntS(param[1])) - spawnEffect.fps = toInt(param[1]); - else - goto badfile; - } - else if(param[0] == "TTL") - { - errorString = "Invalid Spawn Effect time to live value type"; + if(PGEFile::IsFloat(param[1])) + spawnEffect.speed_x = toDouble(param[1]); + else + goto badfile; + } + else if(param[0] == "SSY") + { + errorString = "Invalid Spawn NPC Y value type"; - if(PGEFile::IsIntS(param[1])) - spawnEffect.max_life_time = toInt(param[1]); - else - goto badfile; - } - else if(param[0] == "GT") - { - errorString = "Invalid Spawn Effect Gravity value type"; + if(PGEFile::IsFloat(param[1])) + spawnEffect.speed_y = toDouble(param[1]); + else + goto badfile; + } + else if(param[0] == "SSXX") + { + errorString = "Invalid Spawn NPC Speed X expression value type"; - if(PGEFile::IsBool(param[1])) - spawnEffect.gravity = static_cast(toInt(param[1]) != 0); - else - goto badfile; - } - }//for parameters + if(PGEFile::IsQoutedString(param[1])) + spawnEffect.expression_sx = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "SSYX") + { + errorString = "Invalid Spawn NPC Speed Y expression value type"; - // skip after validating if the field got duplicated - if(i < spawnEffectss_begin) - continue; + if(PGEFile::IsQoutedString(param[1])) + spawnEffect.expression_sy = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "FP") + { + errorString = "Invalid Spawn Effect FPS value type"; - event.spawn_effects.push_back(spawnEffect); - }//for Spawn Effect - }//If SMBX38A Effect Spawning lists are gotten + if(PGEFile::IsIntS(param[1])) + spawnEffect.fps = toInt(param[1]); + else + goto badfile; + } + else if(param[0] == "TTL") + { + errorString = "Invalid Spawn Effect time to live value type"; - //Parse Variables to update - if(!variablesToUpdate.empty()) - { - for(pge_size_t i = 0; i < variablesToUpdate.size(); i++) - { - const auto &updVar = variablesToUpdate[i]; + if(PGEFile::IsIntS(param[1])) + spawnEffect.max_life_time = toInt(param[1]); + else + goto badfile; + } + else if(param[0] == "GT") + { + errorString = "Invalid Spawn Effect Gravity value type"; - LevelEvent_UpdateVariable variableToUpdate; - bool valid = false; - PGELIST mlaData = PGEFile::splitDataLine(updVar, &valid); + if(PGEFile::IsBool(param[1])) + spawnEffect.gravity = static_cast(toInt(param[1]) != 0); + else + goto badfile; + } + }//for parameters - if(!valid) - { - errorString = "Wrong Variable to update event encoded sub-entry"; - goto badfile; - } + // skip after validating if the field got duplicated + if(i < spawnEffectss_begin) + continue; + + event.spawn_effects.push_back(spawnEffect); + }//for Spawn Effect + }//If SMBX38A Effect Spawning lists are gotten - for(auto ¶m : mlaData) + //Parse Variables to update + if(!variablesToUpdate.empty()) + { + for(pge_size_t i = 0; i < variablesToUpdate.size(); i++) { - if(param[0] == "N") - { - errorString = "Invalid Variable to update name value type"; + const auto &updVar = variablesToUpdate[i]; - if(PGEFile::IsQoutedString(param[1])) - variableToUpdate.name = PGEFile::X2STRING(param[1]); - else - goto badfile; + LevelEvent_UpdateVariable variableToUpdate; + bool valid = false; + PGELIST mlaData = PGEFile::splitDataLine(updVar, &valid); + + if(!valid) + { + errorString = "Wrong Variable to update event encoded sub-entry"; + goto badfile; } - else if(param[0] == "V") + + for(auto ¶m : mlaData) { - errorString = "Invalid Variable to update new value type"; + if(param[0] == "N") + { + errorString = "Invalid Variable to update name value type"; - if(PGEFile::IsQoutedString(param[1])) - variableToUpdate.newval = PGEFile::X2STRING(param[1]); - else - goto badfile; - } - }//for parameters + if(PGEFile::IsQoutedString(param[1])) + variableToUpdate.name = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + else if(param[0] == "V") + { + errorString = "Invalid Variable to update new value type"; - // skip after validating if the field got duplicated - if(i < variablesToUpdate_begin) - continue; + if(PGEFile::IsQoutedString(param[1])) + variableToUpdate.newval = PGEFile::X2STRING(param[1]); + else + goto badfile; + } + }//for parameters - event.update_variable.push_back(variableToUpdate); - }//for Variable update events - }//If SMBX38A variable update lists are gotten + // skip after validating if the field got duplicated + if(i < variablesToUpdate_begin) + continue; - //Convert boolean array into control flags - bool *co [] = - { - // SMBX64-only - &event.ctrl_up, - &event.ctrl_down, - &event.ctrl_left, - &event.ctrl_right, - &event.ctrl_run, - &event.ctrl_jump, - &event.ctrl_drop, - &event.ctrl_start, - &event.ctrl_altrun, - &event.ctrl_altjump, - // SMBX64-only end - // SMBX-38A begin - &event.ctrls_enable, - &event.ctrl_lock_keyboard - // SMBX-38A end - }; - - for(pge_size_t c = 0; c < controls.size() && c < 12; ++c) - *(co[c]) = controls[c]; - - //add captured value into array - bool found = false; - pge_size_t q = 0; - - for(q = 0; q < FileData.events.size(); q++) - { - if(FileData.events[q].name == event.name) - { - found = true; - break; - } - } + event.update_variable.push_back(variableToUpdate); + }//for Variable update events + }//If SMBX38A variable update lists are gotten - if(found) - { - event.meta.array_id = FileData.events[q].meta.array_id; - FileData.events[q] = event; - } - else - { - event.meta.array_id = FileData.events_array_id++; - FileData.events.push_back(event); + //Convert boolean array into control flags + bool *co [] = + { + // SMBX64-only + &event.ctrl_up, + &event.ctrl_down, + &event.ctrl_left, + &event.ctrl_right, + &event.ctrl_run, + &event.ctrl_jump, + &event.ctrl_drop, + &event.ctrl_start, + &event.ctrl_altrun, + &event.ctrl_altjump, + // SMBX64-only end + // SMBX-38A begin + &event.ctrls_enable, + &event.ctrl_lock_keyboard + // SMBX-38A end + }; + + for(pge_size_t c = 0; c < controls.size() && c < 12; ++c) + *(co[c]) = controls[c]; + + if(cb.load_event) + cb.load_event(cb.userdata, event); } - } - }//EVENTS_CLASSIC - ///////////////////VARIABLES////////////////////// - PGEX_Section("VARIABLES") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + }//EVENTS_CLASSIC + ///////////////////VARIABLES////////////////////// + PGEX_Section("VARIABLES") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - variable = CreateLvlVariable("unknown"); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_StrVal("N", variable.name) //Variable name - PGEX_StrVal("V", variable.value) //Variable value - PGEX_BoolVal("G", variable.is_global) //Is global variable + PGEX_ItemBegin(PGEFile::PGEX_Struct) + variable = CreateLvlVariable("unknown"); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_StrVal("N", variable.name) //Variable name + PGEX_StrVal("V", variable.value) //Variable value + PGEX_BoolVal("G", variable.is_global) //Is global variable + } + + if(cb.load_var) + cb.load_var(cb.userdata, variable); } - FileData.variables.push_back(variable); - } - }//VARIABLES - ///////////////////ARRAYS////////////////////// - PGEX_Section("ARRAYS") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + }//VARIABLES + ///////////////////ARRAYS////////////////////// + PGEX_Section("ARRAYS") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - array_field = LevelArray(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_StrVal("N", array_field.name) //Variable name + PGEX_ItemBegin(PGEFile::PGEX_Struct) + array_field = LevelArray(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_StrVal("N", array_field.name) //Variable name + } + + if(cb.load_arr) + cb.load_arr(cb.userdata, array_field); } - FileData.arrays.push_back(array_field); - } - }//ARRAYS - ///////////////////SCRIPTS////////////////////// - PGEX_Section("SCRIPTS") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + }//ARRAYS + ///////////////////SCRIPTS////////////////////// + PGEX_Section("SCRIPTS") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - script = CreateLvlScript("unknown", LevelScript::LANG_LUA); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_StrVal("N", script.name) //Variable name - PGEX_SIntVal("L", script.language) //Variable name - PGEX_StrVal("S", script.script) //Script text - } + PGEX_ItemBegin(PGEFile::PGEX_Struct) + script = CreateLvlScript("unknown", LevelScript::LANG_LUA); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_StrVal("N", script.name) //Variable name + PGEX_SIntVal("L", script.language) //Variable name + PGEX_StrVal("S", script.script) //Script text + } - switch(script.language) - { - case LevelScript::LANG_LUA: - case LevelScript::LANG_TEASCRIPT: - case LevelScript::LANG_AUTOCODE: - break; + switch(script.language) + { + case LevelScript::LANG_LUA: + case LevelScript::LANG_TEASCRIPT: + case LevelScript::LANG_AUTOCODE: + break; - default: - script.language = LevelScript::LANG_LUA; //LUA by default if any other language code! - } + default: + script.language = LevelScript::LANG_LUA; //LUA by default if any other language code! + } - FileData.variables.push_back(variable); - } - }//SCRIPTS - ///////////////////CUSTOM ITEM CONFIGS (38A)////////////////////// - PGEX_Section("CUSTOM_ITEMS_38A") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct) - PGEX_Items() + if(cb.load_script) + cb.load_script(cb.userdata, script); + } + }//SCRIPTS + ///////////////////CUSTOM ITEM CONFIGS (38A)////////////////////// + PGEX_Section("CUSTOM_ITEMS_38A") { - PGEX_ItemBegin(PGEFile::PGEX_Struct) - customcfg38A = LevelItemSetup38A(); - PGESTRINGList data; - pge_size_t data_begin = 0; - int type = -1; - errorString = "Wrong type"; - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct) + PGEX_Items() { - PGEX_ValueBegin() - PGEX_USIntVal("T", type) //Type of item - PGEX_USInt64Val("ID", customcfg38A.id) - PGEX_StrArrVal_Validate("D", data, data_begin) //Variable value + PGEX_ItemBegin(PGEFile::PGEX_Struct) + customcfg38A = LevelItemSetup38A(); + PGESTRINGList data; + pge_size_t data_begin = 0; + int type = -1; + errorString = "Wrong type"; + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_USIntVal("T", type) //Type of item + PGEX_USInt64Val("ID", customcfg38A.id) + PGEX_StrArrVal_Validate("D", data, data_begin) //Variable value + + // check type for every value (instead of only the final stored type) + if(type <= LevelItemSetup38A::UNKNOWN || type >= LevelItemSetup38A::ITEM_TYPE_MAX) + goto badfile; + } - // check type for every value (instead of only the final stored type) - if(type <= LevelItemSetup38A::UNKNOWN || type >= LevelItemSetup38A::ITEM_TYPE_MAX) + if(type == -1) goto badfile; - } - if(type == -1) - goto badfile; + errorString = "Wrong pair syntax"; + for(pge_size_t i = 0; i < data.size(); i++) + { + PGESTRING &s = data[i]; - errorString = "Wrong pair syntax"; - for(pge_size_t i = 0; i < data.size(); i++) - { - PGESTRING &s = data[i]; + LevelItemSetup38A::Entry e; + PGESTRINGList pair; + PGE_SPLITSTRING(pair, s, "="); + if(pair.size() != 2) + goto badfile; - LevelItemSetup38A::Entry e; - PGESTRINGList pair; - PGE_SPLITSTRING(pair, s, "="); - if(pair.size() != 2) - goto badfile; + if(PGEFile::IsIntU(pair[0])) + e.key = toInt(pair[0]); + else goto badfile; - if(PGEFile::IsIntU(pair[0])) - e.key = toInt(pair[0]); - else goto badfile; + if(PGEFile::IsIntS(pair[1])) + e.value = toLong(pair[1]); + else goto badfile; - if(PGEFile::IsIntS(pair[1])) - e.value = toLong(pair[1]); - else goto badfile; + if(i < data_begin) + continue; - if(i < data_begin) - continue; + customcfg38A.data.push_back(e); + } + customcfg38A.type = (LevelItemSetup38A::ItemType)type; - customcfg38A.data.push_back(e); + if(cb.load_levelitem38a) + cb.load_levelitem38a(cb.userdata, customcfg38A); } - customcfg38A.type = (LevelItemSetup38A::ItemType)type; - FileData.custom38A_configs.push_back(customcfg38A); - } - }//CUSTOM_ITEMS_38A + }//CUSTOM_ITEMS_38A + } + ///////////////////////////////////////EndFile/////////////////////////////////////// + errorString.clear(); //If no errors, clear string; + return true; + +badfile: //If file format is not correct + if(cb.on_error) + { + FileFormatsError error; + error.ERROR_info = errorString; + error.ERROR_linenum = in.getCurrentLineNumber(); + error.ERROR_linedata = std::move(line); + PGE_CutLength(error.ERROR_linedata, 50); + PGE_FilterBinary(error.ERROR_linedata); + cb.on_error(cb.userdata, error); + } + + return false; + } + catch(const PGE_FileFormats_misc::callback_interrupt& e) + { + return true; + } + catch(const std::exception& e) + { + if(cb.on_error) + { + FileFormatsError error; + error.ERROR_info.clear(); + error.add_exc_info(e, in.getCurrentLineNumber(), std::move(line)); + cb.on_error(cb.userdata, error); + } + + return false; + } +} + +bool FileFormats::ReadExtendedLvlFile(PGE_FileFormats_misc::TextInput &in, LevelData &FileData) +{ + CreateLevelData(FileData); + + //Add path data + PGESTRING filePath = in.getFilePath(); + if(!IsEmpty(filePath)) + { + PGE_FileFormats_misc::FileInfo in_1(filePath); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); } - ///////////////////////////////////////EndFile/////////////////////////////////////// - errorString.clear(); //If no errors, clear string; + + FileData.meta.untitled = false; + FileData.meta.modified = false; FileData.meta.ReadFileValid = true; - return true; -badfile: //If file format is not correct - FileData.meta.ERROR_info = errorString; - FileData.meta.ERROR_linenum = in.getCurrentLineNumber(); - FileData.meta.ERROR_linedata = std::move(line); - FileData.meta.ReadFileValid = false; - PGE_CutLength(FileData.meta.ERROR_linedata, 50); - PGE_FilterBinary(FileData.meta.ERROR_linedata); - return false; - } - catch(const std::exception& e) - { - FileData.meta.ERROR_info = e.what(); - FileData.meta.ERROR_linedata.clear(); - FileData.meta.ERROR_linenum = -1; - FileData.meta.ReadFileValid = false; - return false; - } + FileData.CurSection = 0; + FileData.playmusic = false; + + return ReadExtendedLvlFile(in, PGEFL_make_load_callbacks(FileData)); } @@ -1743,6 +1731,9 @@ bool FileFormats::WriteExtendedLvlFileRaw(LevelData &FileData, PGESTRING &rawdat bool FileFormats::WriteExtendedLvlFile(PGE_FileFormats_misc::TextOutput &out, LevelData &FileData) { + if(!g_use_legacy_pgex_parser) + return MDX_save_level(out, FileData); + pge_size_t i; FileData.meta.RecentFormat = LevelData::PGEX; //Count placed stars on this level diff --git a/src/pgex/file_rw_meta.cpp b/src/pgex/file_rw_meta.cpp index c8f0884..a612b20 100644 --- a/src/pgex/file_rw_meta.cpp +++ b/src/pgex/file_rw_meta.cpp @@ -27,6 +27,7 @@ #include "file_formats.h" #include "pge_file_lib_private.h" #include "pge_x.h" +#include "mdx/mdx_meta_file.h" //********************************************************* //****************READ FILE FORMAT************************* @@ -68,95 +69,112 @@ bool FileFormats::ReadNonSMBX64MetaDataRaw(PGESTRING &rawdata, const PGESTRING & bool FileFormats::ReadNonSMBX64MetaDataFile(PGE_FileFormats_misc::TextInput &in, MetaData &FileData) { - PGESTRING errorString; - int str_count = 0; //Line Counter - PGESTRING line; //Current Line data - ///////////////////////////////////////Begin file/////////////////////////////////////// - PGEFile pgeX_Data(in.readAll()); + if(!g_use_legacy_pgex_parser) + return MDX_load_meta(in, FileData); - if(!pgeX_Data.buildTreeFromRaw()) + // BEFORE: indented 2 spaces to avoid large diff hunk + // REPLY: Spit on diff hung, do that just in next commit after :) + // Don't make "zoo" of code styles in the same file. + try { - errorString = pgeX_Data.lastError(); - goto badfile; - } + PGESTRING errorString; + int str_count = 0; //Line Counter + PGESTRING line; //Current Line data + ///////////////////////////////////////Begin file/////////////////////////////////////// + PGEFile pgeX_Data(in.readAll()); - for(pge_size_t section = 0; section < pgeX_Data.dataTree.size(); section++) //look sections - { - PGEFile::PGEX_Entry &f_section = pgeX_Data.dataTree[section]; + if(!pgeX_Data.buildTreeFromRaw()) + { + errorString = pgeX_Data.lastError(); + goto badfile; + } - if(f_section.name == "META_BOOKMARKS") + for(pge_size_t section = 0; section < pgeX_Data.dataTree.size(); section++) //look sections { - if(f_section.type != PGEFile::PGEX_Struct) - { - errorString = PGESTRING("Wrong section data syntax:\nSection [") + f_section.name + "%1]"; - goto badfile; - } + PGEFile::PGEX_Entry &f_section = pgeX_Data.dataTree[section]; - for(pge_size_t sdata = 0; sdata < f_section.data.size(); sdata++) + if(f_section.name == "META_BOOKMARKS") { - if(f_section.data[sdata].type != PGEFile::PGEX_Struct) + if(f_section.type != PGEFile::PGEX_Struct) { - errorString = PGESTRING("Wrong data item syntax:\nSection [") + - f_section.name + "]\nData line " + - fromNum(sdata) + ")"; + errorString = PGESTRING("Wrong section data syntax:\nSection [") + f_section.name + "%1]"; goto badfile; } - PGEFile::PGEX_Item x = f_section.data[sdata]; - Bookmark meta_bookmark; - meta_bookmark.bookmarkName.clear(); - meta_bookmark.x = 0; - meta_bookmark.y = 0; - - for(const auto &v : x.values) //Look markers and values + for(pge_size_t sdata = 0; sdata < f_section.data.size(); sdata++) { - errorString = PGESTRING("Wrong value syntax\nSection [") + - f_section.name + "]\nData line " + - fromNum(sdata) + "\nMarker " + - v.marker + "\nValue " + - v.value; - - if(v.marker == "BM") //Bookmark name - { - if(PGEFile::IsQoutedString(v.value)) - meta_bookmark.bookmarkName = PGEFile::X2STRING(v.value); - else - goto badfile; - } - else if(v.marker == "X") // Position X + if(f_section.data[sdata].type != PGEFile::PGEX_Struct) { - if(PGEFile::IsFloat(v.value)) - meta_bookmark.x = toFloat(v.value); - else - goto badfile; + errorString = PGESTRING("Wrong data item syntax:\nSection [") + + f_section.name + "]\nData line " + + fromNum(sdata) + ")"; + goto badfile; } - else if(v.marker == "Y") //Position Y + + PGEFile::PGEX_Item x = f_section.data[sdata]; + Bookmark meta_bookmark; + meta_bookmark.bookmarkName.clear(); + meta_bookmark.x = 0; + meta_bookmark.y = 0; + + for(const auto &v : x.values) //Look markers and values { - if(PGEFile::IsFloat(v.value)) - meta_bookmark.y = toFloat(v.value); - else - goto badfile; + errorString = PGESTRING("Wrong value syntax\nSection [") + + f_section.name + "]\nData line " + + fromNum(sdata) + "\nMarker " + + v.marker + "\nValue " + + v.value; + + if(v.marker == "BM") //Bookmark name + { + if(PGEFile::IsQoutedString(v.value)) + meta_bookmark.bookmarkName = PGEFile::X2STRING(v.value); + else + goto badfile; + } + else if(v.marker == "X") // Position X + { + if(PGEFile::IsFloat(v.value)) + meta_bookmark.x = toFloat(v.value); + else + goto badfile; + } + else if(v.marker == "Y") //Position Y + { + if(PGEFile::IsFloat(v.value)) + meta_bookmark.y = toFloat(v.value); + else + goto badfile; + } } - } - FileData.bookmarks.push_back(meta_bookmark); + FileData.bookmarks.push_back(meta_bookmark); + } } } - } - ///////////////////////////////////////EndFile/////////////////////////////////////// - errorString.clear(); //If no errors, clear string; - FileData.meta.ReadFileValid = true; - return true; + ///////////////////////////////////////EndFile/////////////////////////////////////// + errorString.clear(); //If no errors, clear string; + FileData.meta.ReadFileValid = true; + return true; badfile: //If file format is not correct - //BadFileMsg(filePath+"\nError message: "+errorString, str_count, line); - FileData.meta.ERROR_info = errorString; - FileData.meta.ERROR_linenum = str_count; - FileData.meta.ERROR_linedata = line; - FileData.meta.ReadFileValid = false; - FileData.bookmarks.clear(); - return false; + //BadFileMsg(filePath+"\nError message: "+errorString, str_count, line); + FileData.meta.ERROR_info = errorString; + FileData.meta.ERROR_linenum = str_count; + FileData.meta.ERROR_linedata = line; + FileData.meta.ReadFileValid = false; + FileData.bookmarks.clear(); + return false; + } + catch(const std::exception& e) + { + FileData.meta.ERROR_info = e.what(); + FileData.meta.ERROR_linedata.clear(); + FileData.meta.ERROR_linenum = -1; + FileData.meta.ReadFileValid = false; + return false; + } } @@ -198,6 +216,9 @@ bool FileFormats::WriteNonSMBX64MetaDataRaw(MetaData &metaData, PGESTRING &rawda bool FileFormats::WriteNonSMBX64MetaData(PGE_FileFormats_misc::TextOutput &out, MetaData &metaData) { + if(!g_use_legacy_pgex_parser) + return MDX_save_meta(out, metaData); + pge_size_t i; //Bookmarks diff --git a/src/pgex/file_rw_savx.cpp b/src/pgex/file_rw_savx.cpp index 5f3b2e5..e20b089 100644 --- a/src/pgex/file_rw_savx.cpp +++ b/src/pgex/file_rw_savx.cpp @@ -28,6 +28,7 @@ #include "file_strlist.h" #include "pge_x.h" #include "pgex/pge_x_macro.h" +#include "mdx/mdx_gamesave_file.h" #ifdef PGE_FILES_QT #include @@ -73,267 +74,272 @@ bool FileFormats::ReadExtendedSaveFileRaw(PGESTRING &rawdata, const PGESTRING &f bool FileFormats::ReadExtendedSaveFile(PGE_FileFormats_misc::TextInput &in, GamesaveData &FileData) { - // indented 2 spaces to avoid large diff hunk - try - { - FileData = CreateGameSaveData(); - PGESTRING errorString; - PGEX_FileBegin(); - saveCharState plr_state; - visibleItem vz_item; - starOnLevel star_level; - savedLayerSaveEntry saved_layer; - saveLevelInfo level_info; - saveUserData::DataSection user_data_entry; - //Add path data - PGESTRING fPath = in.getFilePath(); - - if(!IsEmpty(fPath)) - { - PGE_FileFormats_misc::FileInfo in_1(fPath); - FileData.meta.filename = in_1.basename(); - FileData.meta.path = in_1.dirpath(); - } + if(!g_use_legacy_pgex_parser) + return MDX_load_gamesave(in, FileData); - FileData.characterStates.clear(); - FileData.currentCharacter.clear(); - FileData.meta.untitled = false; - FileData.meta.modified = false; - ///////////////////////////////////////Begin file/////////////////////////////////////// - PGEX_FileParseTree(in.readAll()); - PGEX_FetchSection() + // BEFORE: indented 2 spaces to avoid large diff hunk + // REPLY: Spit on diff hung, do that just in next commit after :) + // Don't make "zoo" of code styles in the same file. + try { - PGEX_FetchSection_begin() - ///////////////////HEADER////////////////////// - PGEX_Section("SAVE_HEADER") + FileData = CreateGameSaveData(); + PGESTRING errorString; + PGEX_FileBegin(); + saveCharState plr_state; + visibleItem vz_item; + starOnLevel star_level; + savedLayerSaveEntry saved_layer; + saveLevelInfo level_info; + saveUserData::DataSection user_data_entry; + //Add path data + PGESTRING fPath = in.getFilePath(); + + if(!IsEmpty(fPath)) { - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() - { - PGEX_ItemBegin(PGEFile::PGEX_Struct); - PGEX_Values() //Look markers and values - { - PGEX_ValueBegin() - PGEX_SIntVal("LV", FileData.lives) - PGEX_SIntVal("HN", FileData.hundreds) - PGEX_UIntVal("CN", FileData.coins) - PGEX_UIntVal("PT", FileData.points) - PGEX_UIntVal("TS", FileData.totalStars) - PGEX_SLongVal("WX", FileData.worldPosX) - PGEX_SLongVal("WY", FileData.worldPosY) - PGEX_ULongVal("HW", FileData.last_hub_warp) - PGEX_StrVal("HL", FileData.last_hub_level_file) - PGEX_UIntVal("MI", FileData.musicID) - PGEX_StrVal("MF", FileData.musicFile) - PGEX_BoolVal("GC", FileData.gameCompleted) - PGEX_UIntVal("TI", FileData.lvl_path_count) - } - } - }//Header - ///////////////////CHARACTERS////////////////////// - PGEX_Section("CHARACTERS") + PGE_FileFormats_misc::FileInfo in_1(fPath); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); + } + + FileData.characterStates.clear(); + FileData.currentCharacter.clear(); + FileData.meta.untitled = false; + FileData.meta.modified = false; + ///////////////////////////////////////Begin file/////////////////////////////////////// + PGEX_FileParseTree(in.readAll()); + PGEX_FetchSection() { - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + PGEX_FetchSection_begin() + ///////////////////HEADER////////////////////// + PGEX_Section("SAVE_HEADER") { - PGEX_ItemBegin(PGEFile::PGEX_Struct); - plr_state = CreateSavCharacterState(); - PGEX_Values() + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_ULongVal("ID", plr_state.id) - PGEX_ULongVal("ST", plr_state.state) - PGEX_ULongVal("IT", plr_state.itemID) - PGEX_UIntVal("MT", plr_state.mountType) - PGEX_UIntVal("MI", plr_state.mountID) - PGEX_UIntVal("HL", plr_state.health) + PGEX_ItemBegin(PGEFile::PGEX_Struct); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_SIntVal("LV", FileData.lives) + PGEX_SIntVal("HN", FileData.hundreds) + PGEX_UIntVal("CN", FileData.coins) + PGEX_UIntVal("PT", FileData.points) + PGEX_UIntVal("TS", FileData.totalStars) + PGEX_SLongVal("WX", FileData.worldPosX) + PGEX_SLongVal("WY", FileData.worldPosY) + PGEX_ULongVal("HW", FileData.last_hub_warp) + PGEX_StrVal("HL", FileData.last_hub_level_file) + PGEX_UIntVal("MI", FileData.musicID) + PGEX_StrVal("MF", FileData.musicFile) + PGEX_BoolVal("GC", FileData.gameCompleted) + PGEX_UIntVal("TI", FileData.lvl_path_count) + } } - FileData.characterStates.push_back(plr_state); - } - }//CHARACTERS - ///////////////////CHARACTERS_PER_PLAYERS////////////////////// - PGEX_Section("CHARACTERS_PER_PLAYERS") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//Header + ///////////////////CHARACTERS////////////////////// + PGEX_Section("CHARACTERS") { - PGEX_ItemBegin(PGEFile::PGEX_Struct); - unsigned long character = 0; - PGEX_Values() + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_ULongVal("ID", character) + PGEX_ItemBegin(PGEFile::PGEX_Struct); + plr_state = CreateSavCharacterState(); + PGEX_Values() + { + PGEX_ValueBegin() + PGEX_ULongVal("ID", plr_state.id) + PGEX_ULongVal("ST", plr_state.state) + PGEX_ULongVal("IT", plr_state.itemID) + PGEX_UIntVal("MT", plr_state.mountType) + PGEX_UIntVal("MI", plr_state.mountID) + PGEX_UIntVal("HL", plr_state.health) + } + FileData.characterStates.push_back(plr_state); } - FileData.currentCharacter.push_back(character); - } - }//CHARACTERS_PER_PLAYERS - ///////////////////VIZ_LEVELS////////////////////// - PGEX_Section("VIZ_LEVELS") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//CHARACTERS + ///////////////////CHARACTERS_PER_PLAYERS////////////////////// + PGEX_Section("CHARACTERS_PER_PLAYERS") { - PGEX_ItemBegin(PGEFile::PGEX_Struct); - vz_item.first = 0; - vz_item.second = false; - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_UIntVal("ID", vz_item.first) - PGEX_BoolVal("V", vz_item.second) + PGEX_ItemBegin(PGEFile::PGEX_Struct); + unsigned long character = 0; + PGEX_Values() + { + PGEX_ValueBegin() + PGEX_ULongVal("ID", character) + } + FileData.currentCharacter.push_back(character); } - FileData.visibleLevels.push_back(vz_item); - } - }//VIZ_LEVELS - ///////////////////VIZ_PATHS////////////////////// - PGEX_Section("VIZ_PATHS") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//CHARACTERS_PER_PLAYERS + ///////////////////VIZ_LEVELS////////////////////// + PGEX_Section("VIZ_LEVELS") { - PGEX_ItemBegin(PGEFile::PGEX_Struct); - vz_item.first = 0; - vz_item.second = false; - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_UIntVal("ID", vz_item.first) - PGEX_BoolVal("V", vz_item.second) + PGEX_ItemBegin(PGEFile::PGEX_Struct); + vz_item.first = 0; + vz_item.second = false; + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_UIntVal("ID", vz_item.first) + PGEX_BoolVal("V", vz_item.second) + } + FileData.visibleLevels.push_back(vz_item); } - FileData.visiblePaths.push_back(vz_item); - } - }//VIZ_PATHS - ///////////////////VIZ_SCENERY////////////////////// - PGEX_Section("VIZ_SCENERY") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//VIZ_LEVELS + ///////////////////VIZ_PATHS////////////////////// + PGEX_Section("VIZ_PATHS") { - PGEX_ItemBegin(PGEFile::PGEX_Struct); - vz_item.first = 0; - vz_item.second = false; - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_UIntVal("ID", vz_item.first) - PGEX_BoolVal("V", vz_item.second) + PGEX_ItemBegin(PGEFile::PGEX_Struct); + vz_item.first = 0; + vz_item.second = false; + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_UIntVal("ID", vz_item.first) + PGEX_BoolVal("V", vz_item.second) + } + FileData.visiblePaths.push_back(vz_item); } - FileData.visibleScenery.push_back(vz_item); - } - }//VIZ_SCENERY - ///////////////////STARS////////////////////// - PGEX_Section("STARS") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//VIZ_PATHS + ///////////////////VIZ_SCENERY////////////////////// + PGEX_Section("VIZ_SCENERY") { - PGEX_ItemBegin(PGEFile::PGEX_Struct); - star_level.first.clear(); - star_level.second = 0; - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_StrVal("L", star_level.first) - PGEX_SIntVal("S", star_level.second) + PGEX_ItemBegin(PGEFile::PGEX_Struct); + vz_item.first = 0; + vz_item.second = false; + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_UIntVal("ID", vz_item.first) + PGEX_BoolVal("V", vz_item.second) + } + FileData.visibleScenery.push_back(vz_item); } - FileData.gottenStars.push_back(star_level); - } - }//STARS - ///////////////////SAVED_LAYERS////////////////////// - PGEX_Section("SAVED_LAYERS") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//VIZ_SCENERY + ///////////////////STARS////////////////////// + PGEX_Section("STARS") { - PGEX_ItemBegin(PGEFile::PGEX_Struct); - saved_layer.first.clear(); - saved_layer.second = 0; - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_StrVal("L", saved_layer.first) - PGEX_SIntVal("S", saved_layer.second) + PGEX_ItemBegin(PGEFile::PGEX_Struct); + star_level.first.clear(); + star_level.second = 0; + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_StrVal("L", star_level.first) + PGEX_SIntVal("S", star_level.second) + } + FileData.gottenStars.push_back(star_level); } - FileData.savedLayers.push_back(saved_layer); - } - }//SAVED_LAYERS - ///////////////////LEVEL INFO////////////////////// - PGEX_Section("LEVEL_INFO") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//STARS + ///////////////////SAVED_LAYERS////////////////////// + PGEX_Section("SAVED_LAYERS") { - PGEX_ItemBegin(PGEFile::PGEX_Struct); - level_info = saveLevelInfo(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_StrVal("L", level_info.level_filename) - PGEX_UIntVal("S", level_info.max_stars) - PGEX_UIntVal("M", level_info.max_medals) - PGEX_BoolArrVal("MG", level_info.medals_got) - PGEX_BoolArrVal("MB", level_info.medals_best) - PGEX_UIntVal("E", level_info.exits_got) + PGEX_ItemBegin(PGEFile::PGEX_Struct); + saved_layer.first.clear(); + saved_layer.second = 0; + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_StrVal("L", saved_layer.first) + PGEX_SIntVal("S", saved_layer.second) + } + FileData.savedLayers.push_back(saved_layer); } - FileData.levelInfo.push_back(level_info); - } - }//LEVEL_INFO - ///////////////////USERDATA////////////////////// - PGEX_Section("USERDATA") - { - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//SAVED_LAYERS + ///////////////////LEVEL INFO////////////////////// + PGEX_Section("LEVEL_INFO") { - PGEX_ItemBegin(PGEFile::PGEX_Struct); - user_data_entry.data.clear(); - user_data_entry.name = "default"; - user_data_entry.location_name.clear(); - user_data_entry.location = saveUserData::DATA_WORLD; - PGESTRINGList data; - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_SIntVal("L", user_data_entry.location) - PGEX_StrVal("SN", user_data_entry.name) - PGEX_StrVal("LN", user_data_entry.location_name) - PGEX_StrArrVal("D", data) + PGEX_ItemBegin(PGEFile::PGEX_Struct); + level_info = saveLevelInfo(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_StrVal("L", level_info.level_filename) + PGEX_UIntVal("S", level_info.max_stars) + PGEX_UIntVal("M", level_info.max_medals) + PGEX_BoolArrVal("MG", level_info.medals_got) + PGEX_BoolArrVal("MB", level_info.medals_best) + PGEX_UIntVal("E", level_info.exits_got) + } + FileData.levelInfo.push_back(level_info); } - for(PGESTRING &s : data) + }//LEVEL_INFO + ///////////////////USERDATA////////////////////// + PGEX_Section("USERDATA") + { + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - saveUserData::DataEntry e; - PGESTRINGList dp; - PGE_SPLITSTRING(dp, s, "="); - if(dp.size() < 2) - goto badfile; - e.key = PGE_ReplSTRING(PGEFile::X2STRING(dp[0]), "\\q", "="); - e.value = PGE_ReplSTRING(PGEFile::X2STRING(dp[1]), "\\q", "="); - user_data_entry.data.push_back(e); + PGEX_ItemBegin(PGEFile::PGEX_Struct); + user_data_entry.data.clear(); + user_data_entry.name = "default"; + user_data_entry.location_name.clear(); + user_data_entry.location = saveUserData::DATA_WORLD; + PGESTRINGList data; + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_SIntVal("L", user_data_entry.location) + PGEX_StrVal("SN", user_data_entry.name) + PGEX_StrVal("LN", user_data_entry.location_name) + PGEX_StrArrVal("D", data) + } + for(PGESTRING &s : data) + { + saveUserData::DataEntry e; + PGESTRINGList dp; + PGE_SPLITSTRING(dp, s, "="); + if(dp.size() < 2) + goto badfile; + e.key = PGE_ReplSTRING(PGEFile::X2STRING(dp[0]), "\\q", "="); + e.value = PGE_ReplSTRING(PGEFile::X2STRING(dp[1]), "\\q", "="); + user_data_entry.data.push_back(e); + } + FileData.userData.store.push_back(user_data_entry); } - FileData.userData.store.push_back(user_data_entry); - } - }//USERDATA - } - ///////////////////////////////////////EndFile/////////////////////////////////////// - errorString.clear(); //If no errors, clear string; - FileData.meta.ReadFileValid = true; - return true; + }//USERDATA + } + ///////////////////////////////////////EndFile/////////////////////////////////////// + errorString.clear(); //If no errors, clear string; + FileData.meta.ReadFileValid = true; + return true; badfile: //If file format not corrects - FileData.meta.ERROR_info = errorString; - FileData.meta.ERROR_linenum = str_count; - FileData.meta.ERROR_linedata = line; - FileData.meta.ReadFileValid = false; - PGE_CutLength(FileData.meta.ERROR_linedata, 50); - PGE_FilterBinary(FileData.meta.ERROR_linedata); - return false; - } - catch(const std::exception& e) - { - FileData.meta.ERROR_info = e.what(); - FileData.meta.ERROR_linedata.clear(); - FileData.meta.ERROR_linenum = -1; - FileData.meta.ReadFileValid = false; - return false; - } + FileData.meta.ERROR_info = errorString; + FileData.meta.ERROR_linenum = str_count; + FileData.meta.ERROR_linedata = line; + FileData.meta.ReadFileValid = false; + PGE_CutLength(FileData.meta.ERROR_linedata, 50); + PGE_FilterBinary(FileData.meta.ERROR_linedata); + return false; + } + catch(const std::exception& e) + { + FileData.meta.ERROR_info = e.what(); + FileData.meta.ERROR_linedata.clear(); + FileData.meta.ERROR_linenum = -1; + FileData.meta.ReadFileValid = false; + return false; + } } @@ -371,6 +377,9 @@ bool FileFormats::WriteExtendedSaveFileRaw(GamesaveData &FileData, PGESTRING &ra bool FileFormats::WriteExtendedSaveFile(PGE_FileFormats_misc::TextOutput &out, GamesaveData &FileData) { + if(!g_use_legacy_pgex_parser) + return MDX_save_gamesave(out, FileData); + pge_size_t i; out << "SAVE_HEADER\n"; out << PGEFile::value("LV", PGEFile::WriteInt(FileData.lives)); diff --git a/src/pgex/file_rw_wldx.cpp b/src/pgex/file_rw_wldx.cpp index bc707f1..2d922dd 100644 --- a/src/pgex/file_rw_wldx.cpp +++ b/src/pgex/file_rw_wldx.cpp @@ -31,6 +31,8 @@ #include "pgex/pge_x_macro.h" #include "pge_file_lib_sys.h" +#include "mdx/mdx_world_file.h" + //********************************************************* //****************READ FILE FORMAT************************* //********************************************************* @@ -78,174 +80,179 @@ bool FileFormats::ReadExtendedWldFileHeaderRaw(PGESTRING &rawdata, const PGESTRI bool FileFormats::ReadExtendedWldFileHeaderT(PGE_FileFormats_misc::TextInput &inf, WorldData &FileData) { - // indented 2 spaces to avoid large diff hunk - try - { - PGESTRING line; - int str_count = 0; - bool valid = false; - PGE_FileFormats_misc::FileInfo in_1(inf.getFilePath()); - FileData.meta.filename = in_1.basename(); - FileData.meta.path = in_1.dirpath(); - FileData.meta.RecentFormat = LevelData::PGEX; - - FileData.nocharacter.clear(); + if(!g_use_legacy_pgex_parser) + return MDX_load_world_header(inf, FileData); - //Find level header part - do + // BEFORE: indented 2 spaces to avoid large diff hunk + // REPLY: Spit on diff hung, do that just in next commit after :) + // Don't make "zoo" of code styles in the same file. + try { - str_count++; - inf.readLine(line); - } - while((line != "HEAD") && (!inf.eof())); + PGESTRING line; + int str_count = 0; + bool valid = false; + PGE_FileFormats_misc::FileInfo in_1(inf.getFilePath()); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); + FileData.meta.RecentFormat = LevelData::PGEX; - PGESTRINGList header; - bool closed = false; + FileData.nocharacter.clear(); - if(line != "HEAD")//Header not found, this world map is head-less - goto skipHeaderParse; + //Find level header part + do + { + str_count++; + inf.readLine(line); + } + while((line != "HEAD") && (!inf.eof())); - str_count++; - inf.readLine(line); + PGESTRINGList header; + bool closed = false; + + if(line != "HEAD")//Header not found, this world map is head-less + goto skipHeaderParse; - while((line != "HEAD_END") && (!inf.eof())) - { - header.push_back(line); str_count++; inf.readLine(line); - if(line == "HEAD_END") - closed = true; - } + while((line != "HEAD_END") && (!inf.eof())) + { + header.push_back(line); + str_count++; + inf.readLine(line); - if(!closed) - goto badfile; + if(line == "HEAD_END") + closed = true; + } - for(pge_size_t zzz = 0; zzz < header.size(); zzz++) - { - PGESTRING &header_line = header[zzz]; - PGELISTdata = PGEFile::splitDataLine(header_line, &valid); + if(!closed) + goto badfile; - for(pge_size_t i = 0; i < data.size(); i++) + for(pge_size_t zzz = 0; zzz < header.size(); zzz++) { - if(data[i].size() != 2) goto badfile; + PGESTRING &header_line = header[zzz]; + PGELISTdata = PGEFile::splitDataLine(header_line, &valid); - if(data[i][0] == "TL") //Episode Title - { - if(PGEFile::IsQoutedString(data[i][1])) - FileData.EpisodeTitle = PGEFile::X2STRING(data[i][1]); - else - goto badfile; - } - else if(data[i][0] == "DC") //Disabled characters - { - if(PGEFile::IsBoolArray(data[i][1])) - FileData.nocharacter = PGEFile::X2BollArr(data[i][1]); - else - goto badfile; - } - else if(data[i][0] == "IT") //Intro level - { - if(PGEFile::IsQoutedString(data[i][1])) - FileData.IntroLevel_file = PGEFile::X2STRING(data[i][1]); - else - goto badfile; - } - else if(data[i][0] == "GO") //Game Over level - { - if(PGEFile::IsQoutedString(data[i][1])) - FileData.GameOverLevel_file = PGEFile::X2STRING(data[i][1]); - else - goto badfile; - } - else if(data[i][0] == "HB") //Hub Styled - { - if(PGEFile::IsBool(data[i][1])) - FileData.HubStyledWorld = static_cast(toInt(data[i][1]) != 0); - else - goto badfile; - } - else if(data[i][0] == "RL") //Restart level on fail - { - if(PGEFile::IsBool(data[i][1])) - FileData.restartlevel = static_cast(toInt(data[i][1]) != 0); - else - goto badfile; - } - else if(data[i][0] == "SZ") //Starz number - { - if(PGEFile::IsIntU(data[i][1])) - FileData.stars = toUInt(data[i][1]); - else - goto badfile; - } - else if(data[i][0] == "CD") //Credits list + for(pge_size_t i = 0; i < data.size(); i++) { - if(PGEFile::IsQoutedString(data[i][1])) - FileData.authors = PGEFile::X2STRING(data[i][1]); - else - goto badfile; - } - else if(data[i][0] == "CM") //Credits scene background music - { - if(PGEFile::IsQoutedString(data[i][1])) - FileData.authors_music = PGEFile::X2STRING(data[i][1]); - else - goto badfile; - } - else if(data[i][0] == "SSS") //Per-level stars count showing policy - { - if(PGEFile::IsIntS(data[i][1])) - FileData.starsShowPolicy = toInt(data[i][1]); - else - goto badfile; - } - else if(data[i][0] == "XTRA") //Extra settings - { - if(PGEFile::IsQoutedString(data[i][1])) - FileData.custom_params = PGEFile::X2STRING(data[i][1]); - else - goto badfile; - } - else if(data[i][0] == "CPID") //Config pack ID string - { - if(PGEFile::IsQoutedString(data[i][1])) - FileData.meta.configPackId = PGEFile::X2STRING(data[i][1]); - else - goto badfile; - } - else if(data[i][0] == "EFL") //Engine feature level - { - if(PGEFile::IsIntU(data[i][1])) - FileData.meta.engineFeatureLevel = toUInt(data[i][1]); - else - goto badfile; + if(data[i].size() != 2) goto badfile; + + if(data[i][0] == "TL") //Episode Title + { + if(PGEFile::IsQoutedString(data[i][1])) + FileData.EpisodeTitle = PGEFile::X2STRING(data[i][1]); + else + goto badfile; + } + else if(data[i][0] == "DC") //Disabled characters + { + if(PGEFile::IsBoolArray(data[i][1])) + FileData.nocharacter = PGEFile::X2BollArr(data[i][1]); + else + goto badfile; + } + else if(data[i][0] == "IT") //Intro level + { + if(PGEFile::IsQoutedString(data[i][1])) + FileData.IntroLevel_file = PGEFile::X2STRING(data[i][1]); + else + goto badfile; + } + else if(data[i][0] == "GO") //Game Over level + { + if(PGEFile::IsQoutedString(data[i][1])) + FileData.GameOverLevel_file = PGEFile::X2STRING(data[i][1]); + else + goto badfile; + } + else if(data[i][0] == "HB") //Hub Styled + { + if(PGEFile::IsBool(data[i][1])) + FileData.HubStyledWorld = static_cast(toInt(data[i][1]) != 0); + else + goto badfile; + } + else if(data[i][0] == "RL") //Restart level on fail + { + if(PGEFile::IsBool(data[i][1])) + FileData.restartlevel = static_cast(toInt(data[i][1]) != 0); + else + goto badfile; + } + else if(data[i][0] == "SZ") //Starz number + { + if(PGEFile::IsIntU(data[i][1])) + FileData.stars = toUInt(data[i][1]); + else + goto badfile; + } + else if(data[i][0] == "CD") //Credits list + { + if(PGEFile::IsQoutedString(data[i][1])) + FileData.authors = PGEFile::X2STRING(data[i][1]); + else + goto badfile; + } + else if(data[i][0] == "CM") //Credits scene background music + { + if(PGEFile::IsQoutedString(data[i][1])) + FileData.authors_music = PGEFile::X2STRING(data[i][1]); + else + goto badfile; + } + else if(data[i][0] == "SSS") //Per-level stars count showing policy + { + if(PGEFile::IsIntS(data[i][1])) + FileData.starsShowPolicy = toInt(data[i][1]); + else + goto badfile; + } + else if(data[i][0] == "XTRA") //Extra settings + { + if(PGEFile::IsQoutedString(data[i][1])) + FileData.custom_params = PGEFile::X2STRING(data[i][1]); + else + goto badfile; + } + else if(data[i][0] == "CPID") //Config pack ID string + { + if(PGEFile::IsQoutedString(data[i][1])) + FileData.meta.configPackId = PGEFile::X2STRING(data[i][1]); + else + goto badfile; + } + else if(data[i][0] == "EFL") //Engine feature level + { + if(PGEFile::IsIntU(data[i][1])) + FileData.meta.engineFeatureLevel = toUInt(data[i][1]); + else + goto badfile; + } } } - } skipHeaderParse: - FileData.CurSection = 0; - FileData.playmusic = 0; - FileData.meta.ReadFileValid = true; - return true; + FileData.CurSection = 0; + FileData.playmusic = 0; + FileData.meta.ReadFileValid = true; + return true; badfile: - FileData.meta.ERROR_info = "Invalid file format"; - FileData.meta.ERROR_linenum = str_count; - FileData.meta.ERROR_linedata = line; - FileData.meta.ReadFileValid = false; - PGE_CutLength(FileData.meta.ERROR_linedata, 50); - PGE_FilterBinary(FileData.meta.ERROR_linedata); - return false; - } - catch(const std::exception& e) - { - FileData.meta.ERROR_info = e.what(); - FileData.meta.ERROR_linedata.clear(); - FileData.meta.ERROR_linenum = -1; - FileData.meta.ReadFileValid = false; - return false; - } + FileData.meta.ERROR_info = "Invalid file format"; + FileData.meta.ERROR_linenum = str_count; + FileData.meta.ERROR_linedata = line; + FileData.meta.ReadFileValid = false; + PGE_CutLength(FileData.meta.ERROR_linedata, 50); + PGE_FilterBinary(FileData.meta.ERROR_linedata); + return false; + } + catch(const std::exception& e) + { + FileData.meta.ERROR_info = e.what(); + FileData.meta.ERROR_linedata.clear(); + FileData.meta.ERROR_linenum = -1; + FileData.meta.ReadFileValid = false; + return false; + } } bool FileFormats::ReadExtendedWldFileF(const PGESTRING &filePath, WorldData &FileData) @@ -282,300 +289,310 @@ bool FileFormats::ReadExtendedWldFileRaw(PGESTRING &rawdata, const PGESTRING &fi return ReadExtendedWldFile(file, FileData); } +bool FileFormats::ReadExtendedWldFile(PGE_FileFormats_misc::TextInput &in, const WorldLoadCallbacks& cb) +{ + return MDX_load_world(in, cb); +} + bool FileFormats::ReadExtendedWldFile(PGE_FileFormats_misc::TextInput &in, WorldData &FileData) { - // indented 2 spaces to avoid large diff hunk - try - { - PGESTRING errorString; - PGEX_FileBegin(); - PGESTRING filePath = in.getFilePath(); - CreateWorldData(FileData); - FileData.meta.RecentFormat = WorldData::PGEX; + if(!g_use_legacy_pgex_parser) + return MDX_load_world(in, FileData); - //Add path data - if(!IsEmpty(filePath)) + // BEFORE: indented 2 spaces to avoid large diff hunk + // REPLY: Spit on diff hung, do that just in next commit after :) + // Don't make "zoo" of code styles in the same file. + try { - PGE_FileFormats_misc::FileInfo in_1(filePath); - FileData.meta.filename = in_1.basename(); - FileData.meta.path = in_1.dirpath(); - } + PGESTRING errorString; + PGEX_FileBegin(); + PGESTRING filePath = in.getFilePath(); + CreateWorldData(FileData); + FileData.meta.RecentFormat = WorldData::PGEX; + + //Add path data + if(!IsEmpty(filePath)) + { + PGE_FileFormats_misc::FileInfo in_1(filePath); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); + } - FileData.meta.untitled = false; - FileData.meta.modified = false; - WorldTerrainTile tile; - WorldScenery scen; - WorldPathTile pathitem; - WorldMusicBox musicbox; - WorldAreaRect arearect; - WorldLevelTile lvlitem; - ///////////////////////////////////////Begin file/////////////////////////////////////// - PGEX_FileParseTree(in.readAll()); - PGEX_FetchSection() //look sections - { - PGEX_FetchSection_begin() - ///////////////////HEADER////////////////////// - PGEX_Section("HEAD") + FileData.meta.untitled = false; + FileData.meta.modified = false; + WorldTerrainTile tile; + WorldScenery scen; + WorldPathTile pathitem; + WorldMusicBox musicbox; + WorldAreaRect arearect; + WorldLevelTile lvlitem; + ///////////////////////////////////////Begin file/////////////////////////////////////// + PGEX_FileParseTree(in.readAll()); + PGEX_FetchSection() //look sections { - str_count++; - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + PGEX_FetchSection_begin() + ///////////////////HEADER////////////////////// + PGEX_Section("HEAD") { - str_count += 8; - PGEX_ItemBegin(PGEFile::PGEX_Struct); - PGEX_Values() //Look markers and values + str_count++; + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_StrVal("TL", FileData.EpisodeTitle) //Episode Title - PGEX_BoolArrVal("DC", FileData.nocharacter) //Disabled characters - PGEX_StrVal("IT", FileData.IntroLevel_file) //Intro level - PGEX_StrVal("GO", FileData.GameOverLevel_file) //Game Over level - PGEX_BoolVal("HB", FileData.HubStyledWorld) //Hub Styled - PGEX_BoolVal("RL", FileData.restartlevel) //Restart level on fail - PGEX_UIntVal("SZ", FileData.stars) //Starz number - PGEX_StrVal("CD", FileData.authors) //Credits list - PGEX_StrVal("CM", FileData.authors_music) //Credits scene background music - PGEX_SIntVal("SSS", FileData.starsShowPolicy) //Per-level stars count showing policy - PGEX_StrVal("XTRA", FileData.custom_params) //World-wide Extra settings - PGEX_StrVal("CPID", FileData.meta.configPackId)//Config pack ID string - PGEX_UIntVal("EFL", FileData.meta.engineFeatureLevel) //Target engine version + str_count += 8; + PGEX_ItemBegin(PGEFile::PGEX_Struct); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_StrVal("TL", FileData.EpisodeTitle) //Episode Title + PGEX_BoolArrVal("DC", FileData.nocharacter) //Disabled characters + PGEX_StrVal("IT", FileData.IntroLevel_file) //Intro level + PGEX_StrVal("GO", FileData.GameOverLevel_file) //Game Over level + PGEX_BoolVal("HB", FileData.HubStyledWorld) //Hub Styled + PGEX_BoolVal("RL", FileData.restartlevel) //Restart level on fail + PGEX_UIntVal("SZ", FileData.stars) //Starz number + PGEX_StrVal("CD", FileData.authors) //Credits list + PGEX_StrVal("CM", FileData.authors_music) //Credits scene background music + PGEX_SIntVal("SSS", FileData.starsShowPolicy) //Per-level stars count showing policy + PGEX_StrVal("XTRA", FileData.custom_params) //World-wide Extra settings + PGEX_StrVal("CPID", FileData.meta.configPackId)//Config pack ID string + PGEX_UIntVal("EFL", FileData.meta.engineFeatureLevel) //Target engine version + } } - } - }//head - ///////////////////////////////MetaDATA///////////////////////////////////////////// - PGEX_Section("META_BOOKMARKS") - { - str_count++; - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//head + ///////////////////////////////MetaDATA///////////////////////////////////////////// + PGEX_Section("META_BOOKMARKS") { str_count++; - PGEX_ItemBegin(PGEFile::PGEX_Struct); - Bookmark meta_bookmark; - meta_bookmark.bookmarkName.clear(); - meta_bookmark.x = 0; - meta_bookmark.y = 0; - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_StrVal("BM", meta_bookmark.bookmarkName) //Bookmark name - PGEX_FloatVal("X", meta_bookmark.x) // Position X - PGEX_FloatVal("Y", meta_bookmark.y) // Position Y + str_count++; + PGEX_ItemBegin(PGEFile::PGEX_Struct); + Bookmark meta_bookmark; + meta_bookmark.bookmarkName.clear(); + meta_bookmark.x = 0; + meta_bookmark.y = 0; + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_StrVal("BM", meta_bookmark.bookmarkName) //Bookmark name + PGEX_FloatVal("X", meta_bookmark.x) // Position X + PGEX_FloatVal("Y", meta_bookmark.y) // Position Y + } + FileData.metaData.bookmarks.push_back(meta_bookmark); } - FileData.metaData.bookmarks.push_back(meta_bookmark); } - } - ////////////////////////meta bookmarks//////////////////////// - PGEX_Section("META_SYS_CRASH") - { - str_count++; - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + ////////////////////////meta bookmarks//////////////////////// + PGEX_Section("META_SYS_CRASH") { str_count++; - PGEX_ItemBegin(PGEFile::PGEX_Struct); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - FileData.metaData.crash.used = true; - PGEX_ValueBegin() - PGEX_BoolVal("UT", FileData.metaData.crash.untitled) //Untitled - PGEX_BoolVal("MD", FileData.metaData.crash.modifyed) //Modyfied - PGEX_SIntVal("FF", FileData.metaData.crash.fmtID) //Recent File format - PGEX_UIntVal("FV", FileData.metaData.crash.fmtVer) //Recent File format version - PGEX_StrVal("N", FileData.metaData.crash.filename) //Filename - PGEX_StrVal("P", FileData.metaData.crash.path) //Path - PGEX_StrVal("FP", FileData.metaData.crash.fullPath) //Full file Path + str_count++; + PGEX_ItemBegin(PGEFile::PGEX_Struct); + PGEX_Values() //Look markers and values + { + FileData.metaData.crash.used = true; + PGEX_ValueBegin() + PGEX_BoolVal("UT", FileData.metaData.crash.untitled) //Untitled + PGEX_BoolVal("MD", FileData.metaData.crash.modifyed) //Modyfied + PGEX_SIntVal("FF", FileData.metaData.crash.fmtID) //Recent File format + PGEX_UIntVal("FV", FileData.metaData.crash.fmtVer) //Recent File format version + PGEX_StrVal("N", FileData.metaData.crash.filename) //Filename + PGEX_StrVal("P", FileData.metaData.crash.path) //Path + PGEX_StrVal("FP", FileData.metaData.crash.fullPath) //Full file Path + } } - } - }//meta sys crash - ///////////////////////////////MetaDATA//End//////////////////////////////////////// - ///////////////////TILES////////////////////// - PGEX_Section("TILES") - { - str_count++; - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//meta sys crash + ///////////////////////////////MetaDATA//End//////////////////////////////////////// + ///////////////////TILES////////////////////// + PGEX_Section("TILES") { str_count++; - PGEX_ItemBegin(PGEFile::PGEX_Struct); - tile = CreateWldTile(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_ULongVal("ID", tile.id) //Tile ID - PGEX_SLongVal("X", tile.x) //X Position - PGEX_SLongVal("Y", tile.y) //Y Position - PGEX_StrVal("XTRA", tile.meta.custom_params)//Custom JSON data tree + str_count++; + PGEX_ItemBegin(PGEFile::PGEX_Struct); + tile = CreateWldTile(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_ULongVal("ID", tile.id) //Tile ID + PGEX_SLongVal("X", tile.x) //X Position + PGEX_SLongVal("Y", tile.y) //Y Position + PGEX_StrVal("XTRA", tile.meta.custom_params)//Custom JSON data tree + } + tile.meta.array_id = FileData.tile_array_id++; + tile.meta.index = static_cast(FileData.tiles.size()); + FileData.tiles.push_back(tile); } - tile.meta.array_id = FileData.tile_array_id++; - tile.meta.index = static_cast(FileData.tiles.size()); - FileData.tiles.push_back(tile); - } - }//TILES - ///////////////////SCENERY////////////////////// - PGEX_Section("SCENERY") - { - str_count++; - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//TILES + ///////////////////SCENERY////////////////////// + PGEX_Section("SCENERY") { str_count++; - PGEX_ItemBegin(PGEFile::PGEX_Struct); - scen = CreateWldScenery(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_ULongVal("ID", scen.id) //Scenery ID - PGEX_SLongVal("X", scen.x) //X Position - PGEX_SLongVal("Y", scen.y) //Y Position - PGEX_StrVal("XTRA", scen.meta.custom_params)//Custom JSON data tree + str_count++; + PGEX_ItemBegin(PGEFile::PGEX_Struct); + scen = CreateWldScenery(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_ULongVal("ID", scen.id) //Scenery ID + PGEX_SLongVal("X", scen.x) //X Position + PGEX_SLongVal("Y", scen.y) //Y Position + PGEX_StrVal("XTRA", scen.meta.custom_params)//Custom JSON data tree + } + scen.meta.array_id = FileData.scene_array_id++; + scen.meta.index = static_cast(FileData.scenery.size()); + FileData.scenery.push_back(scen); } - scen.meta.array_id = FileData.scene_array_id++; - scen.meta.index = static_cast(FileData.scenery.size()); - FileData.scenery.push_back(scen); - } - }//SCENERY - ///////////////////PATHS////////////////////// - PGEX_Section("PATHS") - { - str_count++; - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//SCENERY + ///////////////////PATHS////////////////////// + PGEX_Section("PATHS") { str_count++; - PGEX_ItemBegin(PGEFile::PGEX_Struct); - pathitem = CreateWldPath(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_ULongVal("ID", pathitem.id) //Path ID - PGEX_SLongVal("X", pathitem.x) //X Position - PGEX_SLongVal("Y", pathitem.y) //Y Position - PGEX_StrVal("XTRA", pathitem.meta.custom_params)//Custom JSON data tree + str_count++; + PGEX_ItemBegin(PGEFile::PGEX_Struct); + pathitem = CreateWldPath(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_ULongVal("ID", pathitem.id) //Path ID + PGEX_SLongVal("X", pathitem.x) //X Position + PGEX_SLongVal("Y", pathitem.y) //Y Position + PGEX_StrVal("XTRA", pathitem.meta.custom_params)//Custom JSON data tree + } + pathitem.meta.array_id = FileData.path_array_id++; + pathitem.meta.index = static_cast(FileData.paths.size()); + FileData.paths.push_back(pathitem); } - pathitem.meta.array_id = FileData.path_array_id++; - pathitem.meta.index = static_cast(FileData.paths.size()); - FileData.paths.push_back(pathitem); - } - }//PATHS - ///////////////////MUSICBOXES////////////////////// - PGEX_Section("MUSICBOXES") - { - str_count++; - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//PATHS + ///////////////////MUSICBOXES////////////////////// + PGEX_Section("MUSICBOXES") { str_count++; - PGEX_ItemBegin(PGEFile::PGEX_Struct); - musicbox = CreateWldMusicbox(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_ULongVal("ID", musicbox.id) //MISICBOX ID - PGEX_SLongVal("X", musicbox.x) //X Position - PGEX_SLongVal("Y", musicbox.y) //X Position - PGEX_StrVal("MF", musicbox.music_file) //Custom music file - PGEX_StrVal("XTRA", musicbox.meta.custom_params)//Custom JSON data tree + str_count++; + PGEX_ItemBegin(PGEFile::PGEX_Struct); + musicbox = CreateWldMusicbox(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_ULongVal("ID", musicbox.id) //MISICBOX ID + PGEX_SLongVal("X", musicbox.x) //X Position + PGEX_SLongVal("Y", musicbox.y) //X Position + PGEX_StrVal("MF", musicbox.music_file) //Custom music file + PGEX_StrVal("XTRA", musicbox.meta.custom_params)//Custom JSON data tree + } + musicbox.meta.array_id = FileData.musicbox_array_id++; + musicbox.meta.index = static_cast(FileData.music.size()); + FileData.music.push_back(musicbox); } - musicbox.meta.array_id = FileData.musicbox_array_id++; - musicbox.meta.index = static_cast(FileData.music.size()); - FileData.music.push_back(musicbox); - } - }//MUSICBOXES - ///////////////////AREARECTS////////////////////// - PGEX_Section("AREARECTS") - { - str_count++; - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//MUSICBOXES + ///////////////////AREARECTS////////////////////// + PGEX_Section("AREARECTS") { str_count++; - PGEX_ItemBegin(PGEFile::PGEX_Struct); - arearect = WorldAreaRect(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_UIntVal("F", arearect.flags) //Flags - PGEX_SLongVal("X", arearect.x) //X Position - PGEX_SLongVal("Y", arearect.y) //X Position - PGEX_USLongVal("W", arearect.w) //Width - PGEX_USLongVal("H", arearect.h) //Height - - // unused stuff - PGEX_ULongVal("MI", arearect.music_id) //MUSICBOX ID - PGEX_StrVal("MF", arearect.music_file) //Custom music file - PGEX_StrVal("LR", arearect.layer) - PGEX_StrVal("EB", arearect.eventBreak) - PGEX_StrVal("EW", arearect.eventWarp) - PGEX_StrVal("EA", arearect.eventAnchor) - PGEX_StrVal("ET", arearect.eventTouch) - PGEX_UIntVal("TP", arearect.eventTouchPolicy) - PGEX_StrVal("XTRA", arearect.meta.custom_params)//Custom JSON data tree + str_count++; + PGEX_ItemBegin(PGEFile::PGEX_Struct); + arearect = WorldAreaRect(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_UIntVal("F", arearect.flags) //Flags + PGEX_SLongVal("X", arearect.x) //X Position + PGEX_SLongVal("Y", arearect.y) //X Position + PGEX_USLongVal("W", arearect.w) //Width + PGEX_USLongVal("H", arearect.h) //Height + + // unused stuff + PGEX_ULongVal("MI", arearect.music_id) //MUSICBOX ID + PGEX_StrVal("MF", arearect.music_file) //Custom music file + PGEX_StrVal("LR", arearect.layer) + PGEX_StrVal("EB", arearect.eventBreak) + PGEX_StrVal("EW", arearect.eventWarp) + PGEX_StrVal("EA", arearect.eventAnchor) + PGEX_StrVal("ET", arearect.eventTouch) + PGEX_UIntVal("TP", arearect.eventTouchPolicy) + PGEX_StrVal("XTRA", arearect.meta.custom_params)//Custom JSON data tree + } + arearect.meta.array_id = FileData.arearect_array_id++; + arearect.meta.index = static_cast(FileData.arearects.size()); + FileData.arearects.push_back(arearect); } - arearect.meta.array_id = FileData.arearect_array_id++; - arearect.meta.index = static_cast(FileData.arearects.size()); - FileData.arearects.push_back(arearect); - } - }//AREARECTS - ///////////////////LEVELS////////////////////// - PGEX_Section("LEVELS") - { - str_count++; - PGEX_SectionBegin(PGEFile::PGEX_Struct); - PGEX_Items() + }//AREARECTS + ///////////////////LEVELS////////////////////// + PGEX_Section("LEVELS") { str_count++; - PGEX_ItemBegin(PGEFile::PGEX_Struct); - lvlitem = CreateWldLevel(); - PGEX_Values() //Look markers and values + PGEX_SectionBegin(PGEFile::PGEX_Struct); + PGEX_Items() { - PGEX_ValueBegin() - PGEX_ULongVal("ID", lvlitem.id) //LEVEL IMAGE ID - PGEX_SLongVal("X", lvlitem.x) //X Position - PGEX_SLongVal("Y", lvlitem.y) //X Position - PGEX_StrVal("LF", lvlitem.lvlfile) //Target level file - PGEX_StrVal("LT", lvlitem.title) //Level title - PGEX_ULongVal("EI", lvlitem.entertowarp) //Entrance Warp ID (if 0 - start level from default points) - PGEX_SIntVal("ET", lvlitem.top_exit) //Open top path on exit type - PGEX_SIntVal("EL", lvlitem.left_exit) //Open left path on exit type - PGEX_SIntVal("ER", lvlitem.right_exit) //Open right path on exit type - PGEX_SIntVal("EB", lvlitem.bottom_exit) //Open bottom path on exit type - PGEX_SLongVal("WX", lvlitem.gotox) //Goto world map X - PGEX_SLongVal("WY", lvlitem.gotoy) //Goto world map Y - PGEX_BoolVal("AV", lvlitem.alwaysVisible) //Always visible - PGEX_BoolVal("SP", lvlitem.gamestart) //Is Game start point - PGEX_BoolVal("BP", lvlitem.pathbg) //Path background - PGEX_BoolVal("BG", lvlitem.bigpathbg) //Big path background - PGEX_SIntVal("SSS", lvlitem.starsShowPolicy) // Stars count showing policy - PGEX_StrVal("XTRA", lvlitem.meta.custom_params)//Custom JSON data tree + str_count++; + PGEX_ItemBegin(PGEFile::PGEX_Struct); + lvlitem = CreateWldLevel(); + PGEX_Values() //Look markers and values + { + PGEX_ValueBegin() + PGEX_ULongVal("ID", lvlitem.id) //LEVEL IMAGE ID + PGEX_SLongVal("X", lvlitem.x) //X Position + PGEX_SLongVal("Y", lvlitem.y) //X Position + PGEX_StrVal("LF", lvlitem.lvlfile) //Target level file + PGEX_StrVal("LT", lvlitem.title) //Level title + PGEX_ULongVal("EI", lvlitem.entertowarp) //Entrance Warp ID (if 0 - start level from default points) + PGEX_SIntVal("ET", lvlitem.top_exit) //Open top path on exit type + PGEX_SIntVal("EL", lvlitem.left_exit) //Open left path on exit type + PGEX_SIntVal("ER", lvlitem.right_exit) //Open right path on exit type + PGEX_SIntVal("EB", lvlitem.bottom_exit) //Open bottom path on exit type + PGEX_SLongVal("WX", lvlitem.gotox) //Goto world map X + PGEX_SLongVal("WY", lvlitem.gotoy) //Goto world map Y + PGEX_BoolVal("AV", lvlitem.alwaysVisible) //Always visible + PGEX_BoolVal("SP", lvlitem.gamestart) //Is Game start point + PGEX_BoolVal("BP", lvlitem.pathbg) //Path background + PGEX_BoolVal("BG", lvlitem.bigpathbg) //Big path background + PGEX_SIntVal("SSS", lvlitem.starsShowPolicy) // Stars count showing policy + PGEX_StrVal("XTRA", lvlitem.meta.custom_params)//Custom JSON data tree + } + lvlitem.meta.array_id = FileData.level_array_id++; + lvlitem.meta.index = static_cast(FileData.levels.size()); + FileData.levels.push_back(lvlitem); } - lvlitem.meta.array_id = FileData.level_array_id++; - lvlitem.meta.index = static_cast(FileData.levels.size()); - FileData.levels.push_back(lvlitem); - } - }//LEVELS - } - ///////////////////////////////////////EndFile/////////////////////////////////////// - FileData.meta.ERROR_info.clear(); //If no errors, clear string; - FileData.meta.ReadFileValid = true; - return true; + }//LEVELS + } + ///////////////////////////////////////EndFile/////////////////////////////////////// + FileData.meta.ERROR_info.clear(); //If no errors, clear string; + FileData.meta.ReadFileValid = true; + return true; badfile: //If file format not corrects - FileData.meta.ERROR_info = errorString; - FileData.meta.ERROR_linenum = str_count; - FileData.meta.ERROR_linedata = line; - FileData.meta.ReadFileValid = false; - PGE_CutLength(FileData.meta.ERROR_linedata, 50); - PGE_FilterBinary(FileData.meta.ERROR_linedata); - return false; - } - catch(const std::exception& e) - { - FileData.meta.ERROR_info = e.what(); - FileData.meta.ERROR_linedata.clear(); - FileData.meta.ERROR_linenum = -1; - FileData.meta.ReadFileValid = false; - return false; - } + FileData.meta.ERROR_info = errorString; + FileData.meta.ERROR_linenum = str_count; + FileData.meta.ERROR_linedata = line; + FileData.meta.ReadFileValid = false; + PGE_CutLength(FileData.meta.ERROR_linedata, 50); + PGE_FilterBinary(FileData.meta.ERROR_linedata); + return false; + } + catch(const std::exception& e) + { + FileData.meta.ERROR_info = e.what(); + FileData.meta.ERROR_linedata.clear(); + FileData.meta.ERROR_linenum = -1; + FileData.meta.ReadFileValid = false; + return false; + } } @@ -614,6 +631,9 @@ bool FileFormats::WriteExtendedWldFileRaw(WorldData &FileData, PGESTRING &rawdat bool FileFormats::WriteExtendedWldFile(PGE_FileFormats_misc::TextOutput &out, WorldData &FileData) { + if(!g_use_legacy_pgex_parser) + return MDX_save_world(out, FileData); + pge_size_t i = 0; FileData.meta.RecentFormat = WorldData::PGEX; diff --git a/src/save_filedata.cpp b/src/save_filedata.cpp index 76ff0da..d701de7 100644 --- a/src/save_filedata.cpp +++ b/src/save_filedata.cpp @@ -34,12 +34,15 @@ saveCharState FileFormats::CreateSavCharacterState() { saveCharState newData; + // rely on constructor +#if 0 newData.id = 1; newData.health = 0; newData.itemID = 0; newData.mountID = 0; newData.mountType = 0; newData.state = 1; +#endif return newData; } diff --git a/src/smbx38a/file_rw_lvl_38a.cpp b/src/smbx38a/file_rw_lvl_38a.cpp index 9f0bf2a..d7d8431 100644 --- a/src/smbx38a/file_rw_lvl_38a.cpp +++ b/src/smbx38a/file_rw_lvl_38a.cpp @@ -29,6 +29,8 @@ #include "smbx38a_private.h" +#include "mdx/mdx_level_file.h" + /*********** Pre-defined values dependent to NPC Generator Effect field value **************/ @@ -243,34 +245,16 @@ struct LevelEvent_layers /**********************************************************************************************/ -bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelData &FileData) +bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, const LevelLoadCallbacks &cb) { SMBX38A_FileBeginN(); - PGESTRING filePath = in.getFilePath(); - FileData.meta.ERROR_info.clear(); - CreateLevelData(FileData); - FileData.meta.RecentFormat = LevelData::SMBX38A; - FileData.meta.RecentFormatVersion = c_latest_version_smbx38a; + LevelHead head; + head.RecentFormat = LevelData::SMBX38A; + head.RecentFormatVersion = FileFormats::c_latest_version_smbx38a; #if !defined(_MSC_VER) || _MSC_VER > 1800 - FileData.LevelName.clear(); - FileData.stars = 0; - FileData.CurSection = 0; - FileData.playmusic = 0; - //Enable strict mode for SMBX LVL file format - FileData.meta.smbx64strict = false; - //Begin all ArrayID's here; - FileData.blocks_array_id = 1; - FileData.bgo_array_id = 1; - FileData.npc_array_id = 1; - FileData.doors_array_id = 1; - FileData.physenv_array_id = 1; - FileData.layers_array_id = 1; - FileData.events_array_id = 1; - FileData.layers.clear(); - FileData.events.clear(); // Mark all 38A levels with a "SMBX-38A" key - FileData.meta.configPackId = "SMBX-38A"; + head.configPackId = "SMBX-38A"; LevelSection section; PlayerPoint playerdata; @@ -288,14 +272,6 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD PGESTRING identifier; - //Add path data - if(!IsEmpty(filePath)) - { - PGE_FileFormats_misc::FileInfo in_1(filePath); - FileData.meta.filename = in_1.basename(); - FileData.meta.path = in_1.dirpath(); - } - in.seek(0, PGE_FileFormats_misc::TextFileInput::begin); try @@ -308,16 +284,16 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD if(!PGE_StartsWith(fileIndentifier, "SMBXFile")) throw std::logic_error("Invalid file format"); - FileData.meta.RecentFormatVersion = toUInt(PGE_SubStr(fileIndentifier, 8, -1)); + head.RecentFormatVersion = toUInt(PGE_SubStr(fileIndentifier, 8, -1)); - if(FileData.meta.RecentFormatVersion > c_latest_version_smbx38a) + if(head.RecentFormatVersion > FileFormats::c_latest_version_smbx38a) throw std::logic_error("File format has newer version which is not supported yet"); while(!in.eof()) { identifier = dataReader.ReadField(1); - if(identifier == "A") + if(identifier == "A" && cb.load_head) { // FIXME: Remove copy from line 77 // A|param1|param2[|param3|param4]| @@ -333,10 +309,10 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD PGESTRING s[4]; dataReader.ReadDataLine( CSVDiscard(), // Skip the first field (this is already "identifier") - &FileData.stars, - MakeCSVPostProcessor(&FileData.LevelName, PGEUrlDecodeFunc), - MakeCSVOptional(&FileData.open_level_on_fail, PGESTRING(""), nullptr, PGEUrlDecodeFunc),//3 - MakeCSVOptionalEmpty(&FileData.open_level_on_fail_warpID, 0u), + &head.stars, + MakeCSVPostProcessor(&head.LevelName, PGEUrlDecodeFunc), + MakeCSVOptional(&head.open_level_on_fail, PGESTRING(""), nullptr, PGEUrlDecodeFunc),//3 + MakeCSVOptionalEmpty(&head.open_level_on_fail_warpID, 0u), MakeCSVOptionalSubReader( dataReader, ',', MakeCSVOptional(&s[0], PGESTRING(""), nullptr, PGEUrlDecodeFunc), @@ -354,14 +330,16 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD mo.type = LevelData::MusicOverrider::SPECIAL; mo.id = (i + 1); mo.fileName = s[i]; - FileData.music_overrides.push_back(mo); + head.music_overrides.push_back(mo); } } + + cb.load_head(cb.userdata, head); } - else if(identifier == "BTNS") + else if(identifier == "BTNS" && cb.load_head) { // BTNS|mario|luigi|peach|toad|link - FileData.player_names_overrides.clear(); + head.player_names_overrides.clear(); PGESTRING plr[5]; dataReader.ReadDataLine( @@ -374,24 +352,26 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD ); for(size_t i = 0; i < 5; i++) - FileData.player_names_overrides.push_back(plr[i]); + head.player_names_overrides.push_back(plr[i]); + + cb.load_head(cb.userdata, head); } - else if(identifier == "P1") + else if(identifier == "P1" && cb.load_startpoint) { // P1|x1|y1 playerdata = CreateLvlPlayerPoint(1); dataReader.ReadDataLine(CSVDiscard(), &playerdata.x, &playerdata.y); - FileData.players.push_back(playerdata); + cb.load_startpoint(cb.userdata, playerdata); } - else if(identifier == "P2") + else if(identifier == "P2" && cb.load_startpoint) { // P2|x2|y2 // FIXME: Copy from above (can be solved with switch?) playerdata = CreateLvlPlayerPoint(2); dataReader.ReadDataLine(CSVDiscard(), &playerdata.x, &playerdata.y); - FileData.players.push_back(playerdata); + cb.load_startpoint(cb.userdata, playerdata); } - else if(identifier == "M") + else if(identifier == "M" && cb.load_section) { // M|id|x|y|w|h|b1|b2|b3|b4|b5|b6|music|background,lightingvalue|musicfile section = CreateLvlSection(); @@ -457,16 +437,9 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD section.size_bottom = static_cast(round(y + h)); } - //Very important data! I'ts a camera position in the editor! - section.PositionX = section.size_left - 10; - section.PositionY = section.size_top - 10; - - if(section.id < static_cast(FileData.sections.size())) - FileData.sections[static_cast(section.id)] = section;//Replace if already exists - else - FileData.sections.push_back(section); //Add Section in main array + cb.load_section(cb.userdata, section); } - else if(identifier == "B") + else if(identifier == "B" && cb.load_block) { // B|layer[,name]|id[,dx,dy]|x|y|contain,sp|b11[,b12]|b2|[e1,e2,e3,e4]|w|h blockdata = CreateLvlBlock(); @@ -538,10 +511,9 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD if(blockdata.w < 0) blockdata.w *= -1; - blockdata.meta.array_id = FileData.blocks_array_id++; - FileData.blocks.push_back(blockdata); + cb.load_block(cb.userdata, blockdata); } - else if(identifier == "T") + else if(identifier == "T" && cb.load_bgo) { // T|layer|id[,dx,dy]|x|y bgodata = CreateLvlBgo(); @@ -559,10 +531,9 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD &bgodata.y ); - bgodata.meta.array_id = FileData.bgo_array_id++; - FileData.bgo.push_back(bgodata); + cb.load_bgo(cb.userdata, bgodata); } - else if(identifier == "N") + else if(identifier == "N" && cb.load_npc) { // N|layer[,name]|id[,dx,dy]|x|y|b1,b2,b3,b4|sp|[e1,e2,e3,e4,e5,e6,e7]|a1,a2|c1[,c2,c3,c4,c5,c6,c7]|msg| // N|layer[,name]|id[,dx,dy]|x|y|b1,b2,b3,b4|sp|[e1,e2,e3,e4,e5,e6,e7]|a1,a2|c1[,c2,c3,c4,c5,c6,c7]|msg| @@ -715,10 +686,10 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD npcdata.generator_period = PGE_FileLibrary::TimeUnitsCVT(static_cast(npcdata.generator_period_orig), PGE_FileLibrary::TimeUnit::FrameOneOf65sec, PGE_FileLibrary::TimeUnit::Decisecond); - npcdata.meta.array_id = FileData.npc_array_id++; - FileData.npc.push_back(npcdata); + + cb.load_npc(cb.userdata, npcdata); } - else if(identifier == "Q") + else if(identifier == "Q" && cb.load_phys) { // Q|layer|x|y|w|h|b1,b2,b3,b4,b5|event phyEnv = CreateLvlPhysEnv(); @@ -744,10 +715,9 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD MakeCSVPostProcessor(&phyEnv.touch_event, PGEUrlDecodeFunc) ); - phyEnv.meta.array_id = FileData.physenv_array_id++; - FileData.physez.push_back(phyEnv); + cb.load_phys(cb.userdata, phyEnv); } - else if(identifier == "W") + else if(identifier == "W" && cb.load_warp) { // W|layer|x|y|ex|ey|type|enterd|exitd|sn,msg,hide|locked,noyoshi,canpick,bomb,hidef,anpc,mini,size|lik|liid|noexit|wx|wy|le|we // W|layer|x|y|ex|ey|type|enterd|exitd|sn,msg,hide|locked,noyoshi,canpick,bomb,hidef,anpc,mini,size,ts,cannon,stand|lik|liid|noexit|wx|wy|le|we @@ -859,15 +829,13 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD } doordata.length_o = doordata.length_i; - doordata.isSetIn = !doordata.lvl_i; - doordata.isSetOut = !doordata.lvl_o || doordata.lvl_i; doordata.cannon_exit = (doordata.cannon_exit_speed > 0.0); if(doordata.cannon_exit_speed <= 0) doordata.cannon_exit_speed = 10.0; - doordata.meta.array_id = FileData.doors_array_id++; - FileData.doors.push_back(doordata); + + cb.load_warp(cb.userdata, doordata); } - else if(identifier == "L") + else if(identifier == "L" && cb.load_layer) { // L|name|status layerdata = CreateLvlLayer(); @@ -878,23 +846,25 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD MakeCSVPostProcessor(&layerdata.hidden, PGEFilpBool) ); - layerdata.meta.array_id = FileData.layers_array_id++; - FileData.layers.push_back(layerdata); + cb.load_layer(cb.userdata, layerdata); } - else if(identifier == "E") + else if(identifier == "E" && cb.load_event) { // E|name|msg|ea|el|elm|epy|eps|eef|ecn|evc|ene eventdata = CreateLvlEvent(); - // Here we can just align the section id with the index of the set - // It is an unsafe method, however, we should be safe when reading from the file, where the data object is empty. eventdata.sets.clear(); + // now, the padding is added at the callback +#if 0 + // Here we can just align the section id with the index of the set + // It is an unsafe method, however, we should be safe when reading from the file, where the data object is empty. for(int q = 0; q < static_cast(FileData.sections.size()); q++) { LevelEvent_Sets set; set.id = static_cast(q); eventdata.sets.push_back(set); } +#endif // Temp Field 11 double timer_def_interval_raw = 0.0; @@ -964,7 +934,9 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD auto fieldReader = MakeDirectReader(nextFieldStr); auto fullReader = MakeCSVReaderForPGESTRING(&fieldReader, ','); int sectionID = fullReader.ReadField(1) - 1; - LevelEvent_Sets &nextSet = eventdata.sets[static_cast(sectionID)]; + eventdata.sets.push_back(LevelEvent_Sets()); + LevelEvent_Sets &nextSet = eventdata.sets.back(); + nextSet.id = sectionID; bool customSize = false; unsigned int autoScrollType = 0; bool canAutoScroll = false; @@ -1248,10 +1220,10 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD eventdata.timer_def.interval = PGE_FileLibrary::TimeUnitsCVT(timer_def_interval_raw, PGE_FileLibrary::TimeUnit::FrameOneOf65sec, PGE_FileLibrary::TimeUnit::Millisecond); - eventdata.meta.array_id = FileData.events_array_id++; - FileData.events.push_back(eventdata); + + cb.load_event(cb.userdata, eventdata); } - else if(identifier == "V") + else if(identifier == "V" && cb.load_var) { // V|name|value vardata = CreateLvlVariable("var"); @@ -1265,12 +1237,12 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD MakeCSVOptionalEmpty(&vardata.is_global, false) ); - FileData.variables.push_back(vardata); + cb.load_var(cb.userdata, vardata); } - else if(identifier == "R") + else if(identifier == "R" && cb.load_arr) { // R|name1|name2|name3|....namen - dataReader.IterateDataLine([&FileData](const PGESTRING & nextFieldStr) + dataReader.IterateDataLine([&cb](const PGESTRING & nextFieldStr) { if(nextFieldStr == "R") return; @@ -1284,10 +1256,10 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD MakeCSVPostProcessor(&arr.name, PGEUrlDecodeFunc) ); - FileData.arrays.push_back(arr); + cb.load_arr(cb.userdata, arr); }); } - else if(identifier == "S") + else if(identifier == "S" && cb.load_script) { // S|name|script scriptdata = CreateLvlScript("doScript", LevelScript::LANG_TEASCRIPT); @@ -1298,9 +1270,9 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD MakeCSVPostProcessor(&scriptdata.script, PGEBase64DecodeFunc) ); - FileData.scripts.push_back(scriptdata); + cb.load_script(cb.userdata, scriptdata); } - else if(identifier == "Su" || identifier == "SU") + else if((identifier == "Su" || identifier == "SU") && cb.load_script) { // Su|name|scriptu scriptdata = CreateLvlScript("doScript", LevelScript::LANG_TEASCRIPT); @@ -1313,9 +1285,10 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD //Convert to LF PGE_ReplSTRING(scriptdata.script, "\r\n", "\n"); - FileData.scripts.push_back(scriptdata); + + cb.load_script(cb.userdata, scriptdata); } - else if((identifier == "CB") || (identifier == "CT") || (identifier == "CE") ) + else if(((identifier == "CB") || (identifier == "CT") || (identifier == "CE")) && cb.load_levelitem38a) { // CB|id|data :custom block/background/effect customcfg = LevelItemSetup38A(); @@ -1338,12 +1311,12 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD }) ); - FileData.custom38A_configs.push_back(customcfg); + cb.load_levelitem38a(cb.userdata, customcfg); } - else if(identifier == "CW") + else if(identifier == "CW" && cb.load_music_override) { // CW|cdata1|cdata2|...|cdatan :custom sound: same as wls file format - dataReader.IterateDataLine([&FileData](const PGESTRING & nextFieldStr) + dataReader.IterateDataLine([&cb](const PGESTRING & nextFieldStr) { if(nextFieldStr == "CW") return; @@ -1357,51 +1330,62 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD MakeCSVPostProcessor(&mo.fileName, PGEUrlDecodeFunc) ); - FileData.sound_overrides.push_back(mo); + cb.load_music_override(cb.userdata, mo); }); } - else + else if(cb.load_junk_line) { // Unsupported line, just keep it PGESTRING str; dataReader.ReadRawLine(str); - FileData.unsupported_38a_lines.push_back(str); + cb.load_junk_line(cb.userdata, str); } }//while is not EOF } + catch(const PGE_FileFormats_misc::callback_interrupt& i) + { + // not an error + return true; + } catch(const std::exception &err) { - // First we try to extract the line number out of the nested exception. - const auto *possibleNestedException = dynamic_cast(&err); //-V641 - - if(possibleNestedException) + if(cb.on_error) { - try - { - std::rethrow_exception(possibleNestedException->nested_ptr()); - } - catch(const parse_error &parseErr) + FileFormatsError error; + + // First we try to extract the line number out of the nested exception. + const auto *possibleNestedException = dynamic_cast(&err); //-V641 + + if(possibleNestedException) { - FileData.meta.ERROR_linenum = static_cast(parseErr.get_line_number()); - } - catch(...) - { //-V565 - // Do Nothing + try + { + std::rethrow_exception(possibleNestedException->nested_ptr()); + } + catch(const parse_error &parseErr) + { + error.ERROR_linenum = static_cast(parseErr.get_line_number()); + } + catch(...) + { //-V565 + // Do Nothing + } } - } - // Now fill in the error data. - FileData.meta.ReadFileValid = false; - FileData.meta.ERROR_info = "Invalid file format, detected file SMBX-38A-" + fromNum(FileData.meta.RecentFormatVersion) + " format\n" - "Caused by: \n" + PGESTRING(exception_to_pretty_string(err).c_str()); - if(!IsEmpty(identifier)) - FileData.meta.ERROR_info += "\n Field type " + identifier; + // Now fill in the error data. + error.ERROR_info = "Invalid file format, detected file SMBX-38A-" + fromNum(head.RecentFormatVersion) + " format\n" + "Caused by: \n" + PGESTRING(exception_to_pretty_string(err).c_str()); + if(!IsEmpty(identifier)) + error.ERROR_info += "\n Field type " + identifier; - // If we were unable to find error line number from the exception, then get the line number from the file reader. - if(FileData.meta.ERROR_linenum == 0) - FileData.meta.ERROR_linenum = in.getCurrentLineNumber(); + // If we were unable to find error line number from the exception, then get the line number from the file reader. + if(error.ERROR_linenum == 0) + error.ERROR_linenum = in.getCurrentLineNumber(); - FileData.meta.ERROR_linedata.clear(); + error.ERROR_linedata.clear(); + + cb.on_error(cb.userdata, error); + } return false; } catch(...) @@ -1410,34 +1394,72 @@ bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelD * This is an attempt to fix crash on Windows 32 bit release assembly, * and possible, on some other platforms too */ - // Now fill in the error data. - FileData.meta.ReadFileValid = false; - FileData.meta.ERROR_info = "Invalid file format, detected file SMBX-38A-" + fromNum(FileData.meta.RecentFormatVersion) + " format\n" - "Caused by unknown exception\n"; - if(!IsEmpty(identifier)) - FileData.meta.ERROR_info += "\n Field type " + identifier; - - // If we were unable to find error line number from the exception, then get the line number from the file reader. - if(FileData.meta.ERROR_linenum == 0) - FileData.meta.ERROR_linenum = in.getCurrentLineNumber(); - - FileData.meta.ERROR_linedata.clear(); + if(cb.on_error) + { + // Now fill in the error data. + FileFormatsError error; + error.ERROR_info = "Invalid file format, detected file SMBX-38A-" + fromNum(head.RecentFormatVersion) + " format\n" + "Caused by unknown exception\n"; + if(!IsEmpty(identifier)) + error.ERROR_info += "\n Field type " + identifier; + + // If we were unable to find error line number from the exception, then get the line number from the file reader. + if(error.ERROR_linenum == 0) + error.ERROR_linenum = in.getCurrentLineNumber(); + + error.ERROR_linedata.clear(); + cb.on_error(cb.userdata, error); + } return false; } - LevelAddInternalEvents(FileData); - FileData.CurSection = 0; - FileData.playmusic = false; - FileData.meta.ReadFileValid = true; + // they don't get cleared anymore, so they don't need to be restored here + // LevelAddInternalEvents(FileData); + return true; #else // MSVC2015+ - FileData.meta.ReadFileValid = false; - FileData.meta.ERROR_info = "Unsupported on MSVC2013"; + if(cb.on_error) + { + FileFormatsError error; + error.ERROR_info = "Unsupported on MSVC2013"; + cb.on_error(cb.userdata, error); + } return false; #endif // MSVC2015+ } +bool FileFormats::ReadSMBX38ALvlFile(PGE_FileFormats_misc::TextInput &in, LevelData &FileData) +{ + CreateLevelData(FileData); + + //Begin all ArrayID's here; + FileData.blocks_array_id = 1; + FileData.bgo_array_id = 1; + FileData.npc_array_id = 1; + FileData.doors_array_id = 1; + FileData.physenv_array_id = 1; + + //Add path data + PGESTRING filePath = in.getFilePath(); + if(!IsEmpty(filePath)) + { + PGE_FileFormats_misc::FileInfo in_1(filePath); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); + } + + FileData.meta.untitled = false; + FileData.meta.modified = false; + FileData.meta.ReadFileValid = true; + + FileData.CurSection = 0; + FileData.playmusic = false; + + return ReadSMBX38ALvlFile(in, PGEFL_make_load_callbacks(FileData)); +} + + //********************************************************* //****************WRITE FILE FORMAT************************ //********************************************************* @@ -2220,7 +2242,7 @@ bool FileFormats::WriteSMBX38ALvlFile(PGE_FileFormats_misc::TextOutput &out, Lev out << "," << expression_x; out << "," << expression_y; // way=[0=by speed][1=by Coordinate] - out << "," << fromNum(mvl.way); + out << "," << fromNum((int)mvl.way); } out << "|"; @@ -2357,7 +2379,7 @@ bool FileFormats::WriteSMBX38ALvlFile(PGE_FileFormats_misc::TextOutput &out, Lev expression_as_x += fromNum(stop.x); expression_as_x += "_" + fromNum(stop.y); - expression_as_x += "_" + fromNum(stop.type); + expression_as_x += "_" + fromNum((int)stop.type); expression_as_x += "_" + fromNum(stop.speed); } diff --git a/src/smbx38a/file_rw_wld_38a.cpp b/src/smbx38a/file_rw_wld_38a.cpp index 1b27c7f..18f85e1 100644 --- a/src/smbx38a/file_rw_wld_38a.cpp +++ b/src/smbx38a/file_rw_wld_38a.cpp @@ -29,6 +29,8 @@ #include "smbx38a_private.h" +#include "mdx/mdx_world_file.h" + //********************************************************* //****************READ FILE FORMAT************************* @@ -284,16 +286,11 @@ bool FileFormats::ReadSMBX38AWldFileRaw(PGESTRING& rawdata, const PGESTRING &fil bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldData& FileData) { - SMBX38A_FileBeginN(); PGESTRING filePath = in.getFilePath(); FileData.meta.ERROR_info.clear(); CreateWorldData(FileData); - FileData.meta.RecentFormat = WorldData::SMBX38A; - FileData.meta.RecentFormatVersion = c_latest_version_smbx38a; - -#if !defined(_MSC_VER) || _MSC_VER > 1800 FileData.EpisodeTitle.clear(); FileData.stars = 0; FileData.CurSection = 0; @@ -309,9 +306,30 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD FileData.level_array_id = 1; FileData.musicbox_array_id = 1; - // Mark all 38A levels with a "SMBX-38A" key - FileData.meta.configPackId = "SMBX-38A"; + //Add path data + if(!IsEmpty(filePath)) + { + PGE_FileFormats_misc::FileInfo in_1(filePath); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); + } + + FileData.meta.ReadFileValid = true; + + FileData.CurSection = 0; + FileData.playmusic = 0; + + return ReadSMBX38AWldFile(in, PGEFL_make_load_callbacks(FileData)); +} + +bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, const WorldLoadCallbacks& cb) +{ + SMBX38A_FileBeginN(); +#if !defined(_MSC_VER) || _MSC_VER > 1800 + + WorldHead head; + WorldHead38A head38a; WorldTerrainTile tile; WorldScenery scen; WorldPathTile pathitem; @@ -324,13 +342,11 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD PGESTRING identifier; - //Add path data - if(!IsEmpty(filePath)) - { - PGE_FileFormats_misc::FileInfo in_1(filePath); - FileData.meta.filename = in_1.basename(); - FileData.meta.path = in_1.dirpath(); - } + head.RecentFormat = WorldData::SMBX38A; + head.RecentFormatVersion = c_latest_version_smbx38a; + + // Mark all 38A levels with a "SMBX-38A" key + head.configPackId = "SMBX-38A"; in.seek(0, PGE_FileFormats_misc::TextFileInput::begin); @@ -344,9 +360,9 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD if(!PGE_StartsWith(fileIndentifier, "SMBXFile")) throw std::logic_error("Invalid file format"); - FileData.meta.RecentFormatVersion = toUInt(PGE_SubStr(fileIndentifier, 8, -1)); + head.RecentFormatVersion = toUInt(PGE_SubStr(fileIndentifier, 8, -1)); - if(FileData.meta.RecentFormatVersion > c_latest_version_smbx38a) + if(head.RecentFormatVersion > c_latest_version_smbx38a) throw std::logic_error("File format has newer version which is not supported yet"); while(!in.eof()) @@ -355,61 +371,68 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD if(identifier == "WS1") { + bool nocharacter1, nocharacter2, nocharacter3, nocharacter4, nocharacter5; + dataReader.ReadDataLine( CSVDiscard(), // Skip the first field (this is already "identifier") // wn=episode name[***urlencode!***] - MakeCSVPostProcessor(&FileData.EpisodeTitle, PGEUrlDecodeFunc), + MakeCSVPostProcessor(&head.EpisodeTitle, PGEUrlDecodeFunc), // bp(n)=don't use player(n) as player's character MakeCSVSubReader( dataReader, ',', - MakeCSVOptional(&FileData.nocharacter1, false), - MakeCSVOptional(&FileData.nocharacter2, false), - MakeCSVOptional(&FileData.nocharacter3, false), - MakeCSVOptional(&FileData.nocharacter4, false), - MakeCSVOptional(&FileData.nocharacter5, false) + MakeCSVOptional(&nocharacter1, false), + MakeCSVOptional(&nocharacter2, false), + MakeCSVOptional(&nocharacter3, false), + MakeCSVOptional(&nocharacter4, false), + MakeCSVOptional(&nocharacter5, false) ), MakeCSVSubReader( dataReader, ',', // asn=auto start level file name[***urlencode!***] - MakeCSVPostProcessor(&FileData.IntroLevel_file, PGEUrlDecodeFunc), + MakeCSVPostProcessor(&head.IntroLevel_file, PGEUrlDecodeFunc), // gon=game over level file name[***urlencode!***] - MakeCSVOptional(&FileData.GameOverLevel_file, "", nullptr, PGEUrlDecodeFunc) + MakeCSVOptional(&head38a.GameOverLevel_file, "", nullptr, PGEUrlDecodeFunc) ), MakeCSVSubReader( dataReader, ',', // dtp=disable two player[0=false !0=true] - MakeCSVOptional(&FileData.restrictSinglePlayer, false), + MakeCSVOptional(&head38a.restrictSinglePlayer, false), // nwm=no world map[0=false !0=true] - MakeCSVOptional(&FileData.HubStyledWorld, false), + MakeCSVOptional(&head.HubStyledWorld, false), // rsd=restart last level on player's character death[0=false !0=true] - MakeCSVOptional(&FileData.restartlevel, false), + MakeCSVOptional(&head.restartlevel, false), // dcp=disable change player[0=false !0=true] - MakeCSVOptional(&FileData.restrictCharacterSwitch, false), + MakeCSVOptional(&head38a.restrictCharacterSwitch, false), // sc=save machine code to sav file[0=false !0=true] - MakeCSVOptional(&FileData.restrictSecureGameSave, false), + MakeCSVOptional(&head38a.restrictSecureGameSave, false), // sm=save mode - MakeCSVOptional(&FileData.saveResumePolicy, 0), + MakeCSVOptional(&head38a.saveResumePolicy, 0), // asg=auto save game[0=false !0=true] - MakeCSVOptional(&FileData.saveAuto, false), + MakeCSVOptional(&head38a.saveAuto, false), // smb3=smb3 style world map[0=false !0=true] - MakeCSVOptional(&FileData.showEverything, false), + MakeCSVOptional(&head38a.showEverything, false), // dss=No Entry Scene - MakeCSVOptional(&FileData.disableEnterScreen, false) + MakeCSVOptional(&head38a.disableEnterScreen, false) ), MakeCSVSubReader( dataReader, ',', // sn=star number - MakeCSVOptional(&FileData.stars, 0), + MakeCSVOptional(&head.stars, 0), // mis=max item number in world inventory - MakeCSVOptional(&FileData.inventoryLimit, 0) + MakeCSVOptional(&head38a.inventoryLimit, 0) ), // acm=anti cheat mode[0=don't allow in list !0=allow in list] - &FileData.cheatsPolicy, + &head38a.cheatsPolicy, // sc=enable save locker[0=false !0=true] - &FileData.saveLocker + &head38a.saveLocker ); - FileData.charactersFromS64(); + head.nocharacter.clear(); + head.nocharacter.push_back(nocharacter1); + head.nocharacter.push_back(nocharacter2); + head.nocharacter.push_back(nocharacter3); + head.nocharacter.push_back(nocharacter4); + head.nocharacter.push_back(nocharacter5); } else if(identifier == "WS2") { @@ -421,14 +444,14 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD // [2] // #CUST#xxxxxx[***base64encode!***] // xxxxxx=any string - MakeCSVPostProcessor(&FileData.authors, [](PGESTRING& value) + MakeCSVPostProcessor(&head.authors, [](PGESTRING& value) { PGESTRING prefix = PGE_SubStr(value, 0, 6); if((prefix == "#DEFT#") || (prefix == "#CUST#")) PGE_RemStrRng(value, 0, 6); value = PGE_BASE64DEC(value); }), - MakeCSVOptionalEmpty(&FileData.authors_music, "", nullptr, PGEUrlDecodeFunc) + MakeCSVOptionalEmpty(&head.authors_music, "", nullptr, PGEUrlDecodeFunc) ); } else if(identifier == "WS3") @@ -442,7 +465,7 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD MakeCSVPostProcessor(&cheatsList, [&](PGESTRING& value) { PGESTRING list = PGE_URLDEC(value); - PGE_SPLITSTRING(FileData.cheatsList, list, ","); + PGE_SPLITSTRING(head38a.cheatsList, list, ","); }) ); } @@ -451,9 +474,9 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD dataReader.ReadDataLine( CSVDiscard(), // se=save locker syntax[***urlencode!***][syntax] - MakeCSVPostProcessor(&FileData.saveLockerEx, PGEUrlDecodeFunc), + MakeCSVPostProcessor(&head38a.saveLockerEx, PGEUrlDecodeFunc), // msg=message when save was locked[***urlencode!***] - MakeCSVPostProcessor(&FileData.saveLockerMsg, PGEUrlDecodeFunc) + MakeCSVPostProcessor(&head38a.saveLockerMsg, PGEUrlDecodeFunc) ); } else if(identifier == "T") @@ -473,8 +496,8 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD MakeCSVOptional(&tile.layer, "Default", nullptr, PGELayerOrDefault) ); - tile.meta.array_id = FileData.tile_array_id++; - FileData.tiles.push_back(tile); + if(cb.load_tile) + cb.load_tile(cb.userdata, tile); } else if(identifier == "S") { @@ -493,8 +516,8 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD MakeCSVOptional(&tile.layer, "Default", nullptr, PGELayerOrDefault) ); - scen.meta.array_id = FileData.scene_array_id++; - FileData.scenery.push_back(scen); + if(cb.load_scene) + cb.load_scene(cb.userdata, scen); } else if(identifier == "P") { @@ -513,8 +536,8 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD MakeCSVOptional(&tile.layer, "Default", nullptr, PGELayerOrDefault) ); - pathitem.meta.array_id = FileData.path_array_id++; - FileData.paths.push_back(pathitem); + if(cb.load_path) + cb.load_path(cb.userdata, pathitem); } else if(identifier == "M") { @@ -578,14 +601,15 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD musicbox.x = arearect.x; musicbox.y = arearect.y; musicbox.layer = arearect.layer; - musicbox.meta.array_id = FileData.musicbox_array_id++; - FileData.music.push_back(musicbox); + + if(cb.load_music) + cb.load_music(cb.userdata, musicbox); } else { //Store as separated "Area-rect" type - arearect.meta.array_id = FileData.arearect_array_id++; - FileData.arearects.push_back(arearect); + if(cb.load_arearect) + cb.load_arearect(cb.userdata, arearect); } } else if(identifier == "L") @@ -735,8 +759,8 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD ) ); - lvlitem.meta.array_id = FileData.level_array_id++; - FileData.levels.push_back(lvlitem); + if(cb.load_level) + cb.load_level(cb.userdata, lvlitem); } else if(identifier == "WL") { @@ -748,8 +772,8 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD &layer.hidden ); - layer.meta.array_id = FileData.layers_array_id++; - FileData.layers.push_back(layer); + if(cb.load_layer38a) + cb.load_layer38a(cb.userdata, layer); } else if(identifier == "WE") { @@ -880,8 +904,9 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD ) ) ); - event.meta.array_id = FileData.events38A_array_id++; - FileData.events38A.push_back(event); + + if(cb.load_event38a) + cb.load_event38a(cb.userdata, event); } else if((identifier == "WCT") || (identifier == "WCS") || (identifier == "WCL") ) { @@ -915,22 +940,41 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD } ) ); - FileData.custom38A_configs.push_back(customcfg); + + if(cb.load_config38a) + cb.load_config38a(cb.userdata, customcfg); } else { // Unsupported line, just keep it PGESTRING str; dataReader.ReadRawLine(str); - FileData.unsupported_38a_lines.push_back(str); + + if(cb.load_junk_line) + cb.load_junk_line(cb.userdata, str); } }//while is not EOF } + catch(const PGE_FileFormats_misc::callback_interrupt& e) + { + // not an error! + return true; + } catch(const std::exception &err) { - // First we try to extract the line number out of the nested exception. + if(!cb.on_error) + return false; + + FileFormatsError error; + + // Fill in the error data. + error.ERROR_info = "Invalid file format, detected file SMBX-38A-" + fromNum(head.RecentFormatVersion) + " format\n"; + + // Now we try to extract the line number out of the nested exception. auto *possibleNestedException = dynamic_cast(&err); //-V641 + int line_num = in.getCurrentLineNumber(); + if(possibleNestedException) { try @@ -939,7 +983,7 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD } catch(const parse_error &parseErr) { - FileData.meta.ERROR_linenum = static_cast(parseErr.get_line_number()); + line_num = static_cast(parseErr.get_line_number()); } catch(...) { //-V565 @@ -947,44 +991,54 @@ bool FileFormats::ReadSMBX38AWldFile(PGE_FileFormats_misc::TextInput& in, WorldD } } - // Now fill in the error data. - FileData.meta.ReadFileValid = false; - FileData.meta.ERROR_info = "Invalid file format, detected file SMBX-38A-" + fromNum(FileData.meta.RecentFormatVersion) + " format\n" - "Caused by: \n" + toPgeString(exception_to_pretty_string(err)); + // Fill exception data with line_num (which may be the file's line num) + error.add_exc_info(err, line_num, ""); - // If we were unable to find error line number from the exception, then get the line number from the file reader. - if(FileData.meta.ERROR_linenum == 0) - FileData.meta.ERROR_linenum = in.getCurrentLineNumber(); + // call error callback + cb.on_error(cb.userdata, error); - FileData.meta.ERROR_linedata.clear(); return false; } catch(...) { + if(!cb.on_error) + return false; + + FileFormatsError error; + /* * This is an attempt to fix crash on Windows 32 bit release assembly, * and possible, on some other platforms too */ // Now fill in the error data. - FileData.meta.ReadFileValid = false; - FileData.meta.ERROR_info = "Invalid file format, detected file SMBX-38A-" + fromNum(FileData.meta.RecentFormatVersion) + " format\n" + error.ERROR_info = "Invalid file format, detected file SMBX-38A-" + fromNum(head.RecentFormatVersion) + " format\n" "Caused by unknown exception\n"; if(!IsEmpty(identifier)) - FileData.meta.ERROR_info += "\n Field type " + identifier; + error.ERROR_info += "\n Field type " + identifier; // If we were unable to find error line number from the exception, then get the line number from the file reader. - if(FileData.meta.ERROR_linenum == 0) - FileData.meta.ERROR_linenum = in.getCurrentLineNumber(); - FileData.meta.ERROR_linedata.clear(); + error.ERROR_linenum = in.getCurrentLineNumber(); + error.ERROR_linedata.clear(); + + cb.on_error(cb.userdata, error); + return false; } - FileData.CurSection = 0; - FileData.playmusic = 0; - FileData.meta.ReadFileValid = true; + if(cb.load_head) + cb.load_head(cb.userdata, head); + + if(cb.load_head38a) + cb.load_head38a(cb.userdata, head38a); + return true; #else - FileData.meta.ReadFileValid = false; - FileData.meta.ERROR_info = "Unsupported on MSVC2013 or lower"; + if(!cb.on_error) + return false; + + FileFormatsError error; + error.ERROR_info = "38A format unsupported on MSVC2013 or lower"; + cb.on_error(cb.userdata, error); + return false; #endif } @@ -1429,7 +1483,7 @@ bool FileFormats::WriteSMBX38AWldFile(PGE_FileFormats_misc::TextOutput& out, Wor SMBX38A_Num2Exp_URLEN(mv.param_h, expression_ph); SMBX38A_Num2Exp_URLEN(mv.param_v, expression_pv); SMBX38A_Num2Exp_URLEN(mv.param_extra, expression_pe); - out << fromNum(mv.type); + out << fromNum((int)mv.type); out << "," << PGE_URLENC(mv.layer); out << "," << expression_ph; out << "," << expression_pv; diff --git a/src/smbx64/file_rw_lvl.cpp b/src/smbx64/file_rw_lvl.cpp index e5ae165..4b38eb5 100644 --- a/src/smbx64/file_rw_lvl.cpp +++ b/src/smbx64/file_rw_lvl.cpp @@ -30,6 +30,8 @@ #include "smbx64_macro.h" #include "CSVUtils.h" +#include "mdx/mdx_level_file.h" + static int s_smbx64_flags = FileFormats::F_SMBX64_NO_FLAGS; @@ -38,6 +40,12 @@ void FileFormats::SetSMBX64LvlFlags(int flags) s_smbx64_flags = flags; } +static inline void skipSection(PGE_FileFormats_misc::TextInput& in, PGESTRING& line) +{ + while(line != "next" && !in.eof()) + in.readCVSLine(line); +} + //********************************************************* //****************READ FILE FORMAT************************* @@ -176,31 +184,18 @@ bool FileFormats::ReadSMBX64LvlFileRaw(PGESTRING &rawdata, const PGESTRING &file return ReadSMBX64LvlFile(file, FileData); } -bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelData &FileData) +bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, const LevelLoadCallbacks &cb) { SMBX64_FileBegin(); PGESTRING filePath = in.getFilePath(); //SMBX64_File( RawData ); int i; //counters - CreateLevelData(FileData); - FileData.meta.RecentFormat = LevelData::SMBX64; - FileData.meta.RecentFormatVersion = 64; - FileData.LevelName.clear(); - FileData.stars = 0; - FileData.CurSection = 0; - FileData.playmusic = false; - //Enable strict mode for SMBX LVL file format - FileData.meta.smbx64strict = true; - //Begin all ArrayID's here; - FileData.blocks_array_id = 1; - FileData.bgo_array_id = 1; - FileData.npc_array_id = 1; - FileData.doors_array_id = 1; - FileData.physenv_array_id = 1; - FileData.layers_array_id = 1; - FileData.events_array_id = 1; - FileData.layers.clear(); - FileData.events.clear(); + + LevelHead head; + head.RecentFormat = LevelData::SMBX64; + head.RecentFormatVersion = 64; + head.LevelName.clear(); + head.stars = 0; LevelSection section; int sct; PlayerPoint players; @@ -214,42 +209,37 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa LevelEvent_layers events_layers; LevelEvent_Sets events_sets; - //Add path data - if(!IsEmpty(filePath)) - { - PGE_FileFormats_misc::FileInfo in_1(filePath); - FileData.meta.filename = in_1.basename(); - FileData.meta.path = in_1.dirpath(); - } - try { ///////////////////////////////////////Begin file/////////////////////////////////////// nextLine(); //Read first line SMBX64::ReadUInt(&file_format, line);//File format number - FileData.meta.RecentFormatVersion = file_format; + head.RecentFormatVersion = file_format; if(ge(17)) { nextLine(); - SMBX64::ReadUInt(&FileData.stars, line); //Number of stars + SMBX64::ReadUInt(&head.stars, line); //Number of stars } else - FileData.stars = 0; //-V1048 + head.stars = 0; //-V1048 if(ge(60)) { nextLine(); //LevelTitle - SMBX64::ReadStr(&FileData.LevelName, line); + SMBX64::ReadStr(&head.LevelName, line); } + if(cb.load_head) + cb.load_head(cb.userdata, head); + //total sections sct = (ge(8) ? 21 : 6); ////////////SECTION Data////////// for(i = 0; i < sct; i++) { - section = CreateLvlSection(); + section = FileFormats::CreateLvlSection(); nextLine(); SMBX64::ReadSIntFromFloat(§ion.size_left, line); nextLine(); @@ -287,33 +277,26 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa SMBX64::ReadStr(§ion.music_file, line); } - //Very important data! I'ts a camera position in the editor! - section.PositionX = section.size_left - 10; //left - section.PositionY = section.size_top - 10; //top section.id = i; - if(i < static_cast(FileData.sections.size())) - FileData.sections[static_cast(i)] = section; //Replace if already exists - else - FileData.sections.push_back(section); //Add Section in main array + if(cb.load_section) + cb.load_section(cb.userdata, section); } if(lt(8)) for(; i < 21; i++) { - section = CreateLvlSection(); + section = FileFormats::CreateLvlSection(); section.id = i; - if(i < static_cast(FileData.sections.size())) - FileData.sections[static_cast(i)] = section; //Replace if already exists - else - FileData.sections.push_back(section); //Add Section in main array + if(cb.load_section) + cb.load_section(cb.userdata, section); } //Player's point config for(i = 0; i < 2; i++) { - players = CreateLvlPlayerPoint(); + players = FileFormats::CreateLvlPlayerPoint(); nextLine(); SMBX64::ReadSIntFromFloat(&players.x, line);//Player x nextLine(); @@ -325,15 +308,21 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa players.id = static_cast(i) + 1u; if(players.x != 0 && players.y != 0 && players.w != 0 && players.h != 0) //Don't add into array non-exist point - FileData.players.push_back(players); //Add player in array + { + if(cb.load_startpoint) + cb.load_startpoint(cb.userdata, players); + } } ////////////Block Data////////// nextLine(); + if(!cb.load_block) + skipSection(in, line); + while(line != "next") { - blocks = CreateLvlBlock(); + blocks = FileFormats::CreateLvlBlock(); SMBX64::ReadSIntFromFloat(&blocks.x, line); nextLine(); SMBX64::ReadSIntFromFloat(&blocks.y, line); @@ -348,7 +337,7 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa SMBX64::ReadUInt(&xnpcID, line); //Containing NPC id { //Convert NPC-ID value from SMBX1/2 to SMBX64 - if((s_smbx64_flags & F_SMBX64_KEEP_LEGACY_NPC_IN_BLOCK_CODES) == 0) + if((s_smbx64_flags & FileFormats::F_SMBX64_KEEP_LEGACY_NPC_IN_BLOCK_CODES) == 0) { switch(xnpcID) { @@ -421,18 +410,20 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa SMBX64::ReadStr(&blocks.event_emptylayer, line); } - blocks.meta.array_id = FileData.blocks_array_id++; - blocks.meta.index = static_cast(FileData.blocks.size()); //Apply element index - FileData.blocks.push_back(blocks); //AddBlock into array + if(cb.load_block) + cb.load_block(cb.userdata, blocks); nextLine(); } ////////////BGO Data////////// nextLine(); + if(!cb.load_bgo) + skipSection(in, line); + while(line != "next") { - bgodata = CreateLvlBgo(); + bgodata = FileFormats::CreateLvlBgo(); SMBX64::ReadSIntFromFloat(&bgodata.x, line); nextLine(); SMBX64::ReadSIntFromFloat(&bgodata.y, line); @@ -453,18 +444,20 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa bgodata.smbx64_sp = 125; } - bgodata.meta.array_id = FileData.bgo_array_id++; - bgodata.meta.index = static_cast(FileData.bgo.size()); //Apply element index - FileData.bgo.push_back(bgodata); //Add Background object into array + if(cb.load_bgo) + cb.load_bgo(cb.userdata, bgodata); nextLine(); } ////////////NPC Data////////// nextLine(); + if(!cb.load_npc) + skipSection(in, line); + while(line != "next") { - npcdata = CreateLvlNpc(); + npcdata = FileFormats::CreateLvlNpc(); SMBX64::ReadSIntFromFloat(&npcdata.x, line); nextLine(); SMBX64::ReadSIntFromFloat(&npcdata.y, line); @@ -607,9 +600,9 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa nextLine(); //Layer name to attach SMBX64::ReadStr(&npcdata.attach_layer, line); } - npcdata.meta.array_id = FileData.npc_array_id++; - npcdata.meta.index = static_cast(FileData.npc.size()); //Apply element index - FileData.npc.push_back(npcdata); //Add NPC into array + + if(cb.load_npc) + cb.load_npc(cb.userdata, npcdata); nextLine(); } @@ -621,7 +614,7 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa || ((file_format < 10) && (!IsEmpty(line)) && (!in.eof())) ) { - doors = CreateLvlWarp(); + doors = FileFormats::CreateLvlWarp(); doors.isSetIn = true; doors.isSetOut = true; SMBX64::ReadSIntFromFloat(&doors.ix, line); //Entrance x @@ -692,9 +685,8 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa SMBX64::ReadCSVBool(&doors.locked, line); } - doors.meta.array_id = FileData.doors_array_id++; - doors.meta.index = static_cast(FileData.doors.size()); //Apply element index - FileData.doors.push_back(doors); //Add NPC into array + if(cb.load_warp) + cb.load_warp(cb.userdata, doors); nextLine(); } @@ -703,9 +695,12 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa { nextLine(); + if(!cb.load_phys) + skipSection(in, line); + while(line != "next") { - waters = CreateLvlPhysEnv(); + waters = FileFormats::CreateLvlPhysEnv(); SMBX64::ReadSIntFromFloat(&waters.x, line); nextLine(); SMBX64::ReadSIntFromFloat(&waters.y, line); @@ -724,9 +719,9 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa nextLine(); SMBX64::ReadStr(&waters.layer, line); - waters.meta.array_id = FileData.physenv_array_id++; - waters.meta.index = static_cast(FileData.physez.size()); //Apply element index - FileData.physez.push_back(waters); //Add Water area into array + + if(cb.load_phys) + cb.load_phys(cb.userdata, waters); nextLine(); } } @@ -736,23 +731,30 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa ////////////Layers Data////////// nextLine(); + if(!cb.load_layer) + skipSection(in, line); + while((line != "next") && (!in.eof()) && (!IsEmpty(line))) { SMBX64::ReadStr(&layers.name, line); //Layer name nextLine(); SMBX64::ReadCSVBool(&layers.hidden, line); //hidden layer layers.locked = false; - layers.meta.array_id = FileData.layers_array_id++; - FileData.layers.push_back(layers); //Add Water area into array + + if(cb.load_layer) + cb.load_layer(cb.userdata, layers); nextLine(); } ////////////Events Data////////// nextLine(); + if(!cb.load_event) + skipSection(in, line); + while((!IsEmpty(line)) && (!in.eof())) { - events = CreateLvlEvent(); + events = FileFormats::CreateLvlEvent(); SMBX64::ReadStr(&events.name, line);//Event name if(ge(11)) @@ -912,43 +914,66 @@ bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelDa // } } - events.meta.array_id = FileData.events_array_id++; - FileData.events.push_back(events); + if(cb.load_event) + cb.load_event(cb.userdata, events); nextLine(); } } - LevelAddInternalEvents(FileData); ///////////////////////////////////////EndFile/////////////////////////////////////// - FileData.meta.ReadFileValid = true; return true; } - catch(const std::exception &err) + catch(const PGE_FileFormats_misc::callback_interrupt& e) { - if(file_format > 0) - FileData.meta.ERROR_info = "Detected file format: SMBX-" + fromNum(file_format) + " is invalid\n"; - else - FileData.meta.ERROR_info = "It is not an SMBX level file\n"; + // not an error! + return true; + } + catch(const std::exception& e) + { + if(cb.on_error) + { + FileFormatsError error; + if(file_format > 0) + error.ERROR_info = "Detected file format: SMBX-" + fromNum(file_format) + " is invalid\n"; + else + error.ERROR_info = "It is not an SMBX level file\n"; + error.add_exc_info(e, in.getCurrentLineNumber(), std::move(line)); + cb.on_error(cb.userdata, error); + } -#ifdef PGE_FILES_QT - FileData.meta.ERROR_info += QString::fromStdString(exception_to_pretty_string(err)); -#else - FileData.meta.ERROR_info += exception_to_pretty_string(err); -#endif - FileData.meta.ERROR_linenum = in.getCurrentLineNumber(); - FileData.meta.ERROR_linedata = std::move(line); - FileData.meta.ReadFileValid = false; - PGE_CutLength(FileData.meta.ERROR_linedata, 50); - PGE_FilterBinary(FileData.meta.ERROR_linedata); return false; } } +bool FileFormats::ReadSMBX64LvlFile(PGE_FileFormats_misc::TextInput &in, LevelData &FileData) +{ + CreateLevelData(FileData); + //Begin all ArrayID's here; + FileData.blocks_array_id = 1; + FileData.bgo_array_id = 1; + FileData.npc_array_id = 1; + FileData.doors_array_id = 1; + FileData.physenv_array_id = 1; + //Add path data + PGESTRING filePath = in.getFilePath(); + if(!IsEmpty(filePath)) + { + PGE_FileFormats_misc::FileInfo in_1(filePath); + FileData.meta.filename = in_1.basename(); + FileData.meta.path = in_1.dirpath(); + } + FileData.meta.untitled = false; + FileData.meta.modified = false; + FileData.meta.ReadFileValid = true; + FileData.CurSection = 0; + FileData.playmusic = false; + return ReadSMBX64LvlFile(in, PGEFL_make_load_callbacks(FileData)); +} //********************************************************* diff --git a/src/smbx64/file_rw_npc_txt.cpp b/src/smbx64/file_rw_npc_txt.cpp index bccebb7..85e50b2 100644 --- a/src/smbx64/file_rw_npc_txt.cpp +++ b/src/smbx64/file_rw_npc_txt.cpp @@ -84,247 +84,249 @@ static inline PGESTRING invalidLine_BOOL(long line, const PGESTRING &data) bool FileFormats::ReadNpcTXTFile(PGE_FileFormats_misc::TextInput &inf, NPCConfigFile &fileData, bool ignoreBad) { - // indented 2 spaces to avoid large diff hunk - try - { - PGESTRING line; //Current Line data - PGESTRINGList params; - fileData = CreateEmpytNpcTXT(); - bool doLog = !ignoreBad; - - auto handlerSInt = [&](bool &dest_en, int32_t &dest, PGESTRING &input) + // BEFORE: indented 2 spaces to avoid large diff hunk + // REPLY: Spit on diff hung, do that just in next commit after :) + // Don't make "zoo" of code styles in the same file. + try { - input = PGESTR_Simpl(input); - input = PGE_RemSubSTRING(input, " ");//Delete spaces - if(!SMBX64::IsSInt(input)) - { - if(doLog) - fileData.unknownLines += invalidLine_SINT(inf.getCurrentLineNumber(), line); + PGESTRING line; //Current Line data + PGESTRINGList params; + fileData = CreateEmpytNpcTXT(); + bool doLog = !ignoreBad; - if(SMBX64::IsFloat(input)) + auto handlerSInt = [&](bool &dest_en, int32_t &dest, PGESTRING &input) + { + input = PGESTR_Simpl(input); + input = PGE_RemSubSTRING(input, " ");//Delete spaces + if(!SMBX64::IsSInt(input)) { - dest = PGE_toNearestS(toDouble(input)); - dest_en = true; + if(doLog) + fileData.unknownLines += invalidLine_SINT(inf.getCurrentLineNumber(), line); + + if(SMBX64::IsFloat(input)) + { + dest = PGE_toNearestS(toDouble(input)); + dest_en = true; + } } - } - else - { - if(input.size() > 9) - dest = 0; else { - dest = toInt(input); - dest_en = true; + if(input.size() > 9) + dest = 0; + else + { + dest = toInt(input); + dest_en = true; + } } - } - }; + }; - auto handlerUInt = [&](bool &dest_en, uint32_t &dest, PGESTRING &input) - { - input = PGESTR_Simpl(input); - input = PGE_RemSubSTRING(input, " ");//Delete spaces - if(!SMBX64::IsUInt(input)) + auto handlerUInt = [&](bool &dest_en, uint32_t &dest, PGESTRING &input) { - if(doLog) - fileData.unknownLines += invalidLine_UINT(inf.getCurrentLineNumber(), line); - - if(SMBX64::IsFloat(input)) + input = PGESTR_Simpl(input); + input = PGE_RemSubSTRING(input, " ");//Delete spaces + if(!SMBX64::IsUInt(input)) { - double o = toDouble(input); - if(o < 0) - o = -o; - dest = PGE_toNearestU(o); - dest_en = true; + if(doLog) + fileData.unknownLines += invalidLine_UINT(inf.getCurrentLineNumber(), line); + + if(SMBX64::IsFloat(input)) + { + double o = toDouble(input); + if(o < 0) + o = -o; + dest = PGE_toNearestU(o); + dest_en = true; + } } - } - else - { - if(input.size() > 9) - dest = 0; else { - dest = toUInt(input); - dest_en = true; + if(input.size() > 9) + dest = 0; + else + { + dest = toUInt(input); + dest_en = true; + } } - } - }; + }; - auto handlerBool = [&](bool &dest_en, bool &dest, PGESTRING &input) - { - input = PGESTR_Simpl(input); - input = PGE_RemSubSTRING(input, " ");//Delete spaces - if(!SMBX64::IsBool(input)) + auto handlerBool = [&](bool &dest_en, bool &dest, PGESTRING &input) { - if(doLog) - fileData.unknownLines += invalidLine_BOOL(inf.getCurrentLineNumber(), line); - - if(SMBX64::IsFloat(input)) + input = PGESTR_Simpl(input); + input = PGE_RemSubSTRING(input, " ");//Delete spaces + if(!SMBX64::IsBool(input)) { - dest = int(toDouble(input)) != 0; - dest_en = true; + if(doLog) + fileData.unknownLines += invalidLine_BOOL(inf.getCurrentLineNumber(), line); + + if(SMBX64::IsFloat(input)) + { + dest = int(toDouble(input)) != 0; + dest_en = true; + } } - } - else + else + { + if(input.size() > 9) + dest = 0; + else + { + dest = static_cast(toInt(input) != 0); + dest_en = true; + } + } + }; + + auto handlerDouble = [&](bool &dest_en, double &dest, PGESTRING &input) { - if(input.size() > 9) - dest = 0; + input = PGESTR_Simpl(input); + input = PGE_RemSubSTRING(input, " ");//Delete spaces + if(!SMBX64::IsFloat(input)) + { + if(doLog) + fileData.unknownLines += invalidLine_FLT(inf.getCurrentLineNumber(), line); + } else { - dest = static_cast(toInt(input) != 0); + dest = toDouble(input); dest_en = true; } - } - }; + }; - auto handlerDouble = [&](bool &dest_en, double &dest, PGESTRING &input) - { - input = PGESTR_Simpl(input); - input = PGE_RemSubSTRING(input, " ");//Delete spaces - if(!SMBX64::IsFloat(input)) + auto handlerString = [&](bool &dest_en, PGESTRING &dest, PGESTRING &input) { - if(doLog) - fileData.unknownLines += invalidLine_FLT(inf.getCurrentLineNumber(), line); - } - else + dest = removeQuotes(input); + dest_en = !IsEmpty(input); + }; + + typedef PGEHASH> NpcCfgHandlerMap; +#define SINT_ENTRY(param_name) { #param_name, [&](PGESTRING ¶m) { handlerSInt(fileData.en_##param_name, fileData.param_name, param);} } +#define UINT_ENTRY(param_name) { #param_name, [&](PGESTRING ¶m) { handlerUInt(fileData.en_##param_name, fileData.param_name, param);} } +#define BOOL_ENTRY(param_name) { #param_name, [&](PGESTRING ¶m) { handlerBool(fileData.en_##param_name, fileData.param_name, param);} } +#define STR_ENTRY(param_name) { #param_name, [&](PGESTRING ¶m) { handlerString(fileData.en_##param_name, fileData.param_name, param);} } +#define FLT_ENTRY(param_name) { #param_name, [&](PGESTRING ¶m) { handlerDouble(fileData.en_##param_name, fileData.param_name, param);} } + + NpcCfgHandlerMap paramsHandler = { - dest = toDouble(input); - dest_en = true; - } - }; - - auto handlerString = [&](bool &dest_en, PGESTRING &dest, PGESTRING &input) - { - dest = removeQuotes(input); - dest_en = !IsEmpty(input); - }; - - typedef PGEHASH> NpcCfgHandlerMap; - #define SINT_ENTRY(param_name) { #param_name, [&](PGESTRING ¶m) { handlerSInt(fileData.en_##param_name, fileData.param_name, param);} } - #define UINT_ENTRY(param_name) { #param_name, [&](PGESTRING ¶m) { handlerUInt(fileData.en_##param_name, fileData.param_name, param);} } - #define BOOL_ENTRY(param_name) { #param_name, [&](PGESTRING ¶m) { handlerBool(fileData.en_##param_name, fileData.param_name, param);} } - #define STR_ENTRY(param_name) { #param_name, [&](PGESTRING ¶m) { handlerString(fileData.en_##param_name, fileData.param_name, param);} } - #define FLT_ENTRY(param_name) { #param_name, [&](PGESTRING ¶m) { handlerDouble(fileData.en_##param_name, fileData.param_name, param);} } - - NpcCfgHandlerMap paramsHandler = - { - SINT_ENTRY(gfxoffsetx), - SINT_ENTRY(gfxoffsety), - UINT_ENTRY(width), - UINT_ENTRY(height), - UINT_ENTRY(gfxwidth), - UINT_ENTRY(gfxheight), - UINT_ENTRY(score), - UINT_ENTRY(health), - BOOL_ENTRY(playerblock), - BOOL_ENTRY(playerblocktop), - BOOL_ENTRY(npcblock), - BOOL_ENTRY(npcblocktop), - BOOL_ENTRY(grabside), - BOOL_ENTRY(grabtop), - BOOL_ENTRY(jumphurt), - BOOL_ENTRY(nohurt), - BOOL_ENTRY(noblockcollision), - BOOL_ENTRY(cliffturn), - BOOL_ENTRY(noyoshi), - BOOL_ENTRY(foreground), - FLT_ENTRY(speed), - BOOL_ENTRY(nofireball), - BOOL_ENTRY(nogravity), - UINT_ENTRY(frames), - UINT_ENTRY(framespeed), - UINT_ENTRY(framestyle), - BOOL_ENTRY(noiceball), - // Non-SMBX64 parameters (not working in SMBX <=1.3) - BOOL_ENTRY(nohammer), - BOOL_ENTRY(noshell), - STR_ENTRY(name), - STR_ENTRY(description), - STR_ENTRY(image), - STR_ENTRY(icon), - STR_ENTRY(script), - STR_ENTRY(group), - STR_ENTRY(category), - UINT_ENTRY(grid), - SINT_ENTRY(gridoffsetx), - SINT_ENTRY(gridoffsety), - UINT_ENTRY(gridalign), - BOOL_ENTRY(usedefaultcam), - }; - - //Read NPC.TXT File config + SINT_ENTRY(gfxoffsetx), + SINT_ENTRY(gfxoffsety), + UINT_ENTRY(width), + UINT_ENTRY(height), + UINT_ENTRY(gfxwidth), + UINT_ENTRY(gfxheight), + UINT_ENTRY(score), + UINT_ENTRY(health), + BOOL_ENTRY(playerblock), + BOOL_ENTRY(playerblocktop), + BOOL_ENTRY(npcblock), + BOOL_ENTRY(npcblocktop), + BOOL_ENTRY(grabside), + BOOL_ENTRY(grabtop), + BOOL_ENTRY(jumphurt), + BOOL_ENTRY(nohurt), + BOOL_ENTRY(noblockcollision), + BOOL_ENTRY(cliffturn), + BOOL_ENTRY(noyoshi), + BOOL_ENTRY(foreground), + FLT_ENTRY(speed), + BOOL_ENTRY(nofireball), + BOOL_ENTRY(nogravity), + UINT_ENTRY(frames), + UINT_ENTRY(framespeed), + UINT_ENTRY(framestyle), + BOOL_ENTRY(noiceball), + // Non-SMBX64 parameters (not working in SMBX <=1.3) + BOOL_ENTRY(nohammer), + BOOL_ENTRY(noshell), + STR_ENTRY(name), + STR_ENTRY(description), + STR_ENTRY(image), + STR_ENTRY(icon), + STR_ENTRY(script), + STR_ENTRY(group), + STR_ENTRY(category), + UINT_ENTRY(grid), + SINT_ENTRY(gridoffsetx), + SINT_ENTRY(gridoffsety), + UINT_ENTRY(gridalign), + BOOL_ENTRY(usedefaultcam), + }; + + //Read NPC.TXT File config #define NextLine(line) inf.readCVSLine(line); - do - { - NextLine(line) - if(IsEmpty(PGE_RemSubSTRING(line, " "))) - continue;//Skip empty strings + do + { + NextLine(line) + if(IsEmpty(PGE_RemSubSTRING(line, " "))) + continue;//Skip empty strings - params.clear(); + params.clear(); - // split the Parameter and value (example: chicken=2) + // split the Parameter and value (example: chicken=2) #ifdef PGE_FILES_QT - int splitSign = line.indexOf('='); - if(splitSign < 0) // Invalid line - { - if(doLog) - fileData.unknownLines += fromNum(inf.getCurrentLineNumber()) + ": " + line + " \n"; - continue; - } - params.push_back(line.mid(0, splitSign)); - params.push_back(line.mid(splitSign + 1, -1)); + int splitSign = line.indexOf('='); + if(splitSign < 0) // Invalid line + { + if(doLog) + fileData.unknownLines += fromNum(inf.getCurrentLineNumber()) + ": " + line + " \n"; + continue; + } + params.push_back(line.mid(0, splitSign)); + params.push_back(line.mid(splitSign + 1, -1)); #else - size_t splitSign = line.find('='); - if(splitSign == std::string::npos) - { - if(doLog) - fileData.unknownLines += fromNum(inf.getCurrentLineNumber()) + ": " + line + " \n"; - continue; - } - params.emplace_back(line.substr(0, splitSign)); - params.emplace_back(line.substr(splitSign + 1, std::string::npos)); + size_t splitSign = line.find('='); + if(splitSign == std::string::npos) + { + if(doLog) + fileData.unknownLines += fromNum(inf.getCurrentLineNumber()) + ": " + line + " \n"; + continue; + } + params.emplace_back(line.substr(0, splitSign)); + params.emplace_back(line.substr(splitSign + 1, std::string::npos)); #endif // Note: This whole condition is always false -// if(params.size() != 2) // If string does not contain strings with "=" as separator -// { -// if(doLog) -// fileData.unknownLines += fromNum(inf.getCurrentLineNumber()) + ": " + line + " \n"; -// if(doLog || (params.size() < 2)) -// continue; -// } - - params[0] = PGESTR_Trim(params[0]); - params[0] = PGESTR_Simpl(params[0]); - params[0] = PGE_RemSubSTRING(params[0], " "); //Delete spaces - params[0] = PGESTR_toLower(params[0]);//To lower case - - params[1] = PGESTR_Trim(params[1]); // Trim it! - - auto hand = paramsHandler.find(params[0]); - if(hand != paramsHandler.end()) - { - PGEMAPVAL(hand)(params[1]); - } - else - { - // Custom value - fileData.entries[params[0]] = params[1]; - if(doLog) //[DEPRECATED] Store unknown value into warnings list - fileData.unknownLines += fromNum(inf.getCurrentLineNumber()) + ": " + line + "\n"; +// if(params.size() != 2) // If string does not contain strings with "=" as separator +// { +// if(doLog) +// fileData.unknownLines += fromNum(inf.getCurrentLineNumber()) + ": " + line + " \n"; +// if(doLog || (params.size() < 2)) +// continue; +// } + + params[0] = PGESTR_Trim(params[0]); + params[0] = PGESTR_Simpl(params[0]); + params[0] = PGE_RemSubSTRING(params[0], " "); //Delete spaces + params[0] = PGESTR_toLower(params[0]);//To lower case + + params[1] = PGESTR_Trim(params[1]); // Trim it! + + auto hand = paramsHandler.find(params[0]); + if(hand != paramsHandler.end()) + { + PGEMAPVAL(hand)(params[1]); + } + else + { + // Custom value + fileData.entries[params[0]] = params[1]; + if(doLog) //[DEPRECATED] Store unknown value into warnings list + fileData.unknownLines += fromNum(inf.getCurrentLineNumber()) + ": " + line + "\n"; + } } + while(!inf.eof()); + + fileData.ReadFileValid = true; + return true; + } + catch(const std::exception& e) + { + fileData.errorString = e.what(); + fileData.ReadFileValid = false; + return false; } - while(!inf.eof()); - - fileData.ReadFileValid = true; - return true; - } - catch(const std::exception& e) - { - fileData.errorString = e.what(); - fileData.ReadFileValid = false; - return false; - } } diff --git a/src/smbx64/file_rw_wld.cpp b/src/smbx64/file_rw_wld.cpp index e1d1be7..f516e34 100644 --- a/src/smbx64/file_rw_wld.cpp +++ b/src/smbx64/file_rw_wld.cpp @@ -31,6 +31,8 @@ #include "smbx64_macro.h" #include "CSVUtils.h" +#include "mdx/mdx_world_file.h" + //********************************************************* //****************READ FILE FORMAT************************* //********************************************************* @@ -212,9 +214,10 @@ bool FileFormats::ReadSMBX64WldFileRaw(PGESTRING &rawdata, const PGESTRING &file return ReadSMBX64WldFile(file, FileData); } + + bool FileFormats::ReadSMBX64WldFile(PGE_FileFormats_misc::TextInput &in, WorldData &FileData) { - SMBX64_FileBegin(); PGESTRING filePath = in.getFilePath(); CreateWorldData(FileData); @@ -233,9 +236,17 @@ bool FileFormats::ReadSMBX64WldFile(PGE_FileFormats_misc::TextInput &in, WorldDa FileData.meta.untitled = false; FileData.meta.modified = false; - //Enable strict mode for SMBX WLD file format - FileData.meta.smbx64strict = true; + FileData.meta.ReadFileValid = true; + + return ReadSMBX64WldFile(in, PGEFL_make_load_callbacks(FileData)); +} + +bool FileFormats::ReadSMBX64WldFile(PGE_FileFormats_misc::TextInput &in, const WorldLoadCallbacks &cb) +{ + SMBX64_FileBegin(); + + WorldHead head; WorldTerrainTile tile; WorldScenery scen; WorldPathTile pathitem; @@ -248,72 +259,84 @@ bool FileFormats::ReadSMBX64WldFile(PGE_FileFormats_misc::TextInput &in, WorldDa //File format number nextLine(); SMBX64::ReadUInt(&file_format, line); - FileData.meta.RecentFormatVersion = file_format; + head.RecentFormat = WorldData::SMBX64; + head.RecentFormatVersion = file_format; //Episode title nextLine(); - SMBX64::ReadStr(&FileData.EpisodeTitle, line); + SMBX64::ReadStr(&head.EpisodeTitle, line); if(ge(55)) { + bool nocharacter1 = false; + bool nocharacter2 = false; + bool nocharacter3 = false; + bool nocharacter4 = false; + bool nocharacter5 = false; + nextLine(); - SMBX64::ReadCSVBool(&FileData.nocharacter1, line);//Edisode without Mario + SMBX64::ReadCSVBool(&nocharacter1, line);//Edisode without Mario nextLine(); - SMBX64::ReadCSVBool(&FileData.nocharacter2, line);//Edisode without Luigi + SMBX64::ReadCSVBool(&nocharacter2, line);//Edisode without Luigi nextLine(); - SMBX64::ReadCSVBool(&FileData.nocharacter3, line);//Edisode without Peach + SMBX64::ReadCSVBool(&nocharacter3, line);//Edisode without Peach nextLine(); - SMBX64::ReadCSVBool(&FileData.nocharacter4, line);//Edisode without Toad + SMBX64::ReadCSVBool(&nocharacter4, line);//Edisode without Toad if(ge(56)) { nextLine(); - SMBX64::ReadCSVBool(&FileData.nocharacter5, line);//Edisode without Link + SMBX64::ReadCSVBool(&nocharacter5, line);//Edisode without Link } //Convert into the bool array - FileData.nocharacter.push_back(FileData.nocharacter1); - FileData.nocharacter.push_back(FileData.nocharacter2); - FileData.nocharacter.push_back(FileData.nocharacter3); - FileData.nocharacter.push_back(FileData.nocharacter4); - FileData.nocharacter.push_back(FileData.nocharacter5); + head.nocharacter.push_back(nocharacter1); + head.nocharacter.push_back(nocharacter2); + head.nocharacter.push_back(nocharacter3); + head.nocharacter.push_back(nocharacter4); + head.nocharacter.push_back(nocharacter5); } if(ge(3)) { nextLine(); - SMBX64::ReadStr(&FileData.IntroLevel_file, line);//Autostart level + SMBX64::ReadStr(&head.IntroLevel_file, line);//Autostart level nextLine(); - SMBX64::ReadCSVBool(&FileData.HubStyledWorld, line); //Don't use world map on this episode + SMBX64::ReadCSVBool(&head.HubStyledWorld, line); //Don't use world map on this episode nextLine(); - SMBX64::ReadCSVBool(&FileData.restartlevel, line);//Restart level on playable character's death + SMBX64::ReadCSVBool(&head.restartlevel, line);//Restart level on playable character's death } if(ge(20)) { nextLine(); - SMBX64::ReadUInt(&FileData.stars, line);//Stars number + SMBX64::ReadUInt(&head.stars, line);//Stars number } if(file_format >= 17) { + PGESTRING author1, author2, author3, author4, author5; + nextLine(); - SMBX64::ReadStr(&FileData.author1, line); //Author 1 + SMBX64::ReadStr(&author1, line); //Author 1 nextLine(); - SMBX64::ReadStr(&FileData.author2, line); //Author 2 + SMBX64::ReadStr(&author2, line); //Author 2 nextLine(); - SMBX64::ReadStr(&FileData.author3, line); //Author 3 + SMBX64::ReadStr(&author3, line); //Author 3 nextLine(); - SMBX64::ReadStr(&FileData.author4, line); //Author 4 + SMBX64::ReadStr(&author4, line); //Author 4 nextLine(); - SMBX64::ReadStr(&FileData.author5, line); //Author 5 + SMBX64::ReadStr(&author5, line); //Author 5 - FileData.authors.clear(); - FileData.authors += (IsEmpty(FileData.author1)) ? "" : FileData.author1 + "\n"; - FileData.authors += (IsEmpty(FileData.author2)) ? "" : FileData.author2 + "\n"; - FileData.authors += (IsEmpty(FileData.author3)) ? "" : FileData.author3 + "\n"; - FileData.authors += (IsEmpty(FileData.author4)) ? "" : FileData.author4 + "\n"; - FileData.authors += (IsEmpty(FileData.author5)) ? "" : FileData.author5; + head.authors.clear(); + head.authors += (IsEmpty(author1)) ? "" : author1 + "\n"; + head.authors += (IsEmpty(author2)) ? "" : author2 + "\n"; + head.authors += (IsEmpty(author3)) ? "" : author3 + "\n"; + head.authors += (IsEmpty(author4)) ? "" : author4 + "\n"; + head.authors += (IsEmpty(author5)) ? "" : author5; } + if(cb.load_head) + cb.load_head(cb.userdata, head); + ////////////Tiles Data////////// nextLine(); @@ -326,11 +349,9 @@ bool FileFormats::ReadSMBX64WldFile(PGE_FileFormats_misc::TextInput &in, WorldDa nextLine(); SMBX64::ReadUInt(&tile.id, line);//Tile ID - tile.meta.array_id = FileData.tile_array_id; - FileData.tile_array_id++; - tile.meta.index = (unsigned int)FileData.tiles.size(); //Apply element index + if(cb.load_tile) + cb.load_tile(cb.userdata, tile); - FileData.tiles.push_back(tile); nextLine(); } @@ -345,11 +366,8 @@ bool FileFormats::ReadSMBX64WldFile(PGE_FileFormats_misc::TextInput &in, WorldDa nextLine(); SMBX64::ReadUInt(&scen.id, line);//Scenery ID - scen.meta.array_id = FileData.scene_array_id; - FileData.scene_array_id++; - scen.meta.index = (unsigned int)FileData.scenery.size(); //Apply element index - - FileData.scenery.push_back(scen); + if(cb.load_scene) + cb.load_scene(cb.userdata, scen); nextLine(); } @@ -365,11 +383,8 @@ bool FileFormats::ReadSMBX64WldFile(PGE_FileFormats_misc::TextInput &in, WorldDa nextLine(); SMBX64::ReadUInt(&pathitem.id, line); //Path ID - pathitem.meta.array_id = FileData.path_array_id; - FileData.path_array_id++; - pathitem.meta.index = (unsigned int)FileData.paths.size(); //Apply element index - - FileData.paths.push_back(pathitem); + if(cb.load_path) + cb.load_path(cb.userdata, pathitem); nextLine(); } @@ -424,11 +439,8 @@ bool FileFormats::ReadSMBX64WldFile(PGE_FileFormats_misc::TextInput &in, WorldDa lvlitem.gamestart = true; } - lvlitem.meta.array_id = FileData.level_array_id; - FileData.level_array_id++; - lvlitem.meta.index = (unsigned int)FileData.levels.size(); //Apply element index - - FileData.levels.push_back(lvlitem); + if(cb.load_level) + cb.load_level(cb.userdata, lvlitem); nextLine(); } @@ -444,35 +456,33 @@ bool FileFormats::ReadSMBX64WldFile(PGE_FileFormats_misc::TextInput &in, WorldDa nextLine(); SMBX64::ReadUInt(&musicbox.id, line);//MusicBox ID - musicbox.meta.array_id = FileData.musicbox_array_id; - FileData.musicbox_array_id++; - musicbox.meta.index = (unsigned int)FileData.music.size(); //Apply element index - - FileData.music.push_back(musicbox); + if(cb.load_music) + cb.load_music(cb.userdata, musicbox); nextLine(); } nextLine(); // Read last line ///////////////////////////////////////EndFile/////////////////////////////////////// - FileData.meta.ReadFileValid = true; return true; } - catch(const std::exception &err) + catch(const PGE_FileFormats_misc::callback_interrupt& e) { - if(file_format > 0) - FileData.meta.ERROR_info = "Detected file format: SMBX-" + fromNum(file_format) + " is invalid\n"; - else - FileData.meta.ERROR_info = "It is not an SMBX world map file\n"; -#ifdef PGE_FILES_QT - FileData.meta.ERROR_info += QString::fromStdString(exception_to_pretty_string(err)); -#else - FileData.meta.ERROR_info += exception_to_pretty_string(err); -#endif - FileData.meta.ERROR_linenum = in.getCurrentLineNumber(); - FileData.meta.ERROR_linedata = std::move(line); - FileData.meta.ReadFileValid = false; - PGE_CutLength(FileData.meta.ERROR_linedata, 50); - PGE_FilterBinary(FileData.meta.ERROR_linedata); + // not an error! + return true; + } + catch(const std::exception& e) + { + if(cb.on_error) + { + FileFormatsError error; + if(file_format > 0) + error.ERROR_info = "Detected file format: SMBX-" + fromNum(file_format) + " is invalid\n"; + else + error.ERROR_info = "It is not an SMBX world map file\n"; + error.add_exc_info(e, in.getCurrentLineNumber(), std::move(line)); + cb.on_error(cb.userdata, error); + } + return false; } } diff --git a/wld_filedata.h b/wld_filedata.h index 1da3271..8b52248 100644 --- a/wld_filedata.h +++ b/wld_filedata.h @@ -34,6 +34,7 @@ #define WLD_FILEDATA_H #include "pge_file_lib_globs.h" +#include "pge_base_callbacks.h" #include "meta_filedata.h" #ifndef DEFAULT_LAYER_NAME @@ -477,10 +478,96 @@ struct WorldItemSetup38A PGELIST data; }; +/*! + * \brief World map header data structure. Contains all available settings for the map. + */ +struct WorldHead +{ + //! Title of the episode + PGESTRING EpisodeTitle; + //! List of disabled playable characters (boolean array by ID of each playable character) + PGELIST nocharacter; + + PGESTRING IntroLevel_file; + PGESTRING GameOverLevel_file; + bool HubStyledWorld = false; + bool restartlevel = false; + + //! Cached total number of available stars on this episode + unsigned int stars = 0; + + //! Episode credits (full text area) + PGESTRING authors; + //! Credits scene background music + PGESTRING authors_music; + + //! World map wide policy of per-level stars count displaying + int starsShowPolicy = -1; + + //! JSON-like string with a custom properties (without master brackets, like "param":"value,["subparam":value]) + PGESTRING custom_params; + + //! A config pack identify string. + PGESTRING configPackId; + + //! Minimum engine version for reading + unsigned int engineFeatureLevel = 0; + + //! Recently used (open or save) file format + int RecentFormat = 0; + //! Recently used format version (for SMBX1...64 files only) + unsigned int RecentFormatVersion = 0; +}; + +struct WorldHead38A +{ + PGESTRING GameOverLevel_file; + + //! This episode can be played in the single player only + bool restrictSinglePlayer = false; + //! Disable ability to toggle a playabele character on the world map + bool restrictCharacterSwitch = false; + //! Use stronger securty on the game save files + bool restrictSecureGameSave = false; + //! Don't show entreance screen on each entering into the level + bool disableEnterScreen = false; + + enum CheatsPolicy + { + CHEATS_DENY_IN_LIST = false, + CHEATS_ALLOW_IN_LIST = true + }; + //! If unchecked - allow all cheats except listed, If checked - deny all except listed + bool cheatsPolicy = CHEATS_DENY_IN_LIST; + //! List of cheat codes (granted or forbidden dependent on restrictNoCheats flag state) + PGESTRINGList cheatsList; + + enum SaveMode + { + SAVE_RESUME_AT_INTRO = -1, + SAVE_RESUME_AT_WORLD_MAP = 0, + SAVE_RESUME_AT_RECENT_LEVEL = 1, + }; + //! Policy where resume game on save load + int saveResumePolicy = SAVE_RESUME_AT_WORLD_MAP; + //! Automatically save game on level completing + bool saveAuto = false; + //! Enable save locker + bool saveLocker = false; + //! Save locker expression + PGESTRING saveLockerEx; + //! Message box shown on save locking + PGESTRING saveLockerMsg; + //! Always show any closed cells (overwise closed cells are will be hidden until player will open them) + bool showEverything = false; + //! 38A Inventory limit + unsigned long inventoryLimit = 0; +}; + /** * @brief World map data structure */ -struct WorldData +struct WorldData : public WorldHead38A { //! Helper meta-data FileFormatMeta meta; @@ -533,51 +620,11 @@ struct WorldData } PGESTRING IntroLevel_file; - PGESTRING GameOverLevel_file; bool HubStyledWorld = false; bool restartlevel = false; - //! This episode can be played in the single player only - bool restrictSinglePlayer = false; - //! Disable ability to toggle a playabele character on the world map - bool restrictCharacterSwitch = false; - //! Use stronger securty on the game save files - bool restrictSecureGameSave = false; - //! Don't show entreance screen on each entering into the level - bool disableEnterScreen = false; - - enum CheatsPolicy - { - CHEATS_DENY_IN_LIST = false, - CHEATS_ALLOW_IN_LIST = true - }; - //! If unchecked - allow all cheats except listed, If checked - deny all except listed - bool cheatsPolicy = CHEATS_DENY_IN_LIST; - //! List of cheat codes (granted or forbidden dependent on restrictNoCheats flag state) - PGESTRINGList cheatsList; - - enum SaveMode - { - SAVE_RESUME_AT_INTRO = -1, - SAVE_RESUME_AT_WORLD_MAP = 0, - SAVE_RESUME_AT_RECENT_LEVEL = 1, - }; - //! Policy where resume game on save load - int saveResumePolicy = SAVE_RESUME_AT_WORLD_MAP; - //! Automatically save game on level completing - bool saveAuto = false; - //! Enable save locker - bool saveLocker = false; - //! Save locker expression - PGESTRING saveLockerEx; - //! Message box shown on save locking - PGESTRING saveLockerMsg; - //! Always show any closed cells (overwise closed cells are will be hidden until player will open them) - bool showEverything = false; //! Cached total number of available stars on this episode unsigned int stars = 0; - //! 38A Inventory limit - unsigned long inventoryLimit = 0; enum StarsShowPolicy { @@ -657,4 +704,40 @@ struct WorldData int currentMusic = 0; }; +struct WorldLoadCallbacks : PGE_FileFormats_misc::LoadCallbacks +{ + callback load_head = nullptr; + callback load_bookmark = nullptr; + callback load_crash_data = nullptr; + callback load_tile = nullptr; + callback load_scene = nullptr; + callback load_path = nullptr; + callback load_music = nullptr; + callback load_arearect = nullptr; + callback load_level = nullptr; + callback load_layer38a = nullptr; + callback load_event38a = nullptr; + callback load_config38a = nullptr; + callback load_head38a = nullptr; + callback load_junk_line = nullptr; +}; + +struct WorldSaveCallbacks : PGE_FileFormats_misc::SaveCallbacks +{ + callback save_head = nullptr; + callback save_bookmark = nullptr; + callback save_crash_data = nullptr; + callback save_tile = nullptr; + callback save_scene = nullptr; + callback save_path = nullptr; + callback save_music = nullptr; + callback save_arearect = nullptr; + callback save_level = nullptr; + callback save_layer38a = nullptr; + callback save_event38a = nullptr; + callback save_config38a = nullptr; + callback save_head38a = nullptr; + callback save_junk_line = nullptr; +}; + #endif // WLD_FILEDATA_H