diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f869712 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.cpp text eol=crlf +*.h text eol=crlf diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a632005 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,30 @@ +name: Build ESPTK + +on: + push: + branches: [master] + pull_request: + types: [opened, synchronize, reopened] + +jobs: + build: + runs-on: windows-2022 + steps: + - uses: actions/checkout@v4 + + - name: Configure ESPTK build + shell: pwsh + run: | + cmake --preset vs2022-windows "-DCMAKE_INSTALL_PREFIX=install" + + - name: Build ESPTK + run: cmake --build vsbuild --config RelWithDebInfo + + - name: Install ESPTK + run: cmake --install vsbuild --config RelWithDebInfo + + - name: Upload ESPTK artifact + uses: actions/upload-artifact@master + with: + name: esptk + path: ./install diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 0000000..8bd995c --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,16 @@ +name: Lint ESPTK + +on: + push: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check format + uses: ModOrganizer2/check-formatting-action@master + with: + check-path: "." diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3103a1f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-merge-conflict + - id: check-case-conflict + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v19.1.5 + hooks: + - id: clang-format + 'types_or': [c++, c] + +ci: + autofix_commit_msg: "[pre-commit.ci] Auto fixes from pre-commit.com hooks." + autofix_prs: true + autoupdate_commit_msg: "[pre-commit.ci] Pre-commit autoupdate." + autoupdate_schedule: quarterly + submodules: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e21291..8ecf141 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,26 @@ cmake_minimum_required(VERSION 3.16) -if(DEFINED DEPENDENCIES_DIR) - include(${DEPENDENCIES_DIR}/modorganizer_super/cmake_common/mo2.cmake) -else() - include(${CMAKE_CURRENT_LIST_DIR}/../cmake_common/mo2.cmake) -endif() +include(CMakePackageConfigHelpers) project(esptk) + add_subdirectory(src) + +configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/config.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/mo2-esptk-config.cmake" + INSTALL_DESTINATION "lib/cmake/mo2-esptk" + NO_SET_AND_CHECK_MACRO + NO_CHECK_REQUIRED_COMPONENTS_MACRO +) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/mo2-esptk-config-version.cmake" + VERSION "1.3.12" + COMPATIBILITY AnyNewerVersion +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/mo2-esptk-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/mo2-esptk-config-version.cmake + DESTINATION lib/cmake/mo2-esptk +) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..fb2f5ab --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,36 @@ +{ + "configurePresets": [ + { + "errors": { + "deprecated": true + }, + "hidden": true, + "name": "cmake-dev", + "warnings": { + "deprecated": true, + "dev": true + } + }, + { + "binaryDir": "${sourceDir}/vsbuild", + "architecture": { + "strategy": "set", + "value": "x64" + }, + "cacheVariables": { + "CMAKE_CXX_FLAGS": "/EHsc /MP /W4" + }, + "generator": "Visual Studio 17 2022", + "inherits": ["cmake-dev"], + "name": "vs2022-windows", + "toolset": "v143" + } + ], + "buildPresets": [ + { + "name": "vs2022-windows", + "configurePreset": "vs2022-windows" + } + ], + "version": 4 +} diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 40f620c..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,36 +0,0 @@ -version: 1.0.{build} -skip_branch_with_pr: true -image: Visual Studio 2019 -environment: - WEBHOOK_URL: - secure: gOKbXaZM9ImtMD5XrYITvdyZUW/az082G9OIN1EC1Vbg57wBaeLhi49uGjxPw5GVujHku6kxN6ab89zhbS5GVeluR76GM83IbKV4Sh7udXzoYZZdg6YudtYHzdhCgUeiedpswbuczTq9ceIkkfSEWZuh/lMAAVVwvcGsJAnoPFw= -build_script: -- pwsh: >- - $ErrorActionPreference = 'Stop' - - git clone --depth=1 --no-single-branch https://github.com/ModOrganizer2/modorganizer-umbrella.git c:\projects\modorganizer-umbrella - - New-Item -ItemType Directory -Path c:\projects\modorganizer-build - - cd c:\projects\modorganizer-umbrella - - ($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH -eq $null) ? ($branch = $env:APPVEYOR_REPO_BRANCH) : ($branch = $env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH) - - git checkout $(git show-ref --verify --quiet refs/remotes/origin/${branch} || echo '-b') ${branch} - - C:\Python37-x64\python.exe unimake.py -d c:\projects\modorganizer-build -s Appveyor_Build=True ${env:APPVEYOR_PROJECT_NAME} - - if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) } -artifacts: -- path: vsbuild\src\RelWithDebInfo\esptk.lib - name: esptk_lib -on_success: - - ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER - - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 - - ps: ./send.ps1 success $env:WEBHOOK_URL -on_failure: - - ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER - - ps: Push-AppveyorArtifact ${env:APPVEYOR_BUILD_FOLDER}\stdout.log - - ps: Push-AppveyorArtifact ${env:APPVEYOR_BUILD_FOLDER}\stderr.log - - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 - - ps: ./send.ps1 failure $env:WEBHOOK_URL \ No newline at end of file diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in new file mode 100644 index 0000000..b04f9d4 --- /dev/null +++ b/cmake/config.cmake.in @@ -0,0 +1,3 @@ +@PACKAGE_INIT@ + +include ( "${CMAKE_CURRENT_LIST_DIR}/mo2-esptk-targets.cmake" ) diff --git a/src/espexceptions.h b/include/esptk/espexceptions.h similarity index 95% rename from src/espexceptions.h rename to include/esptk/espexceptions.h index 368ba11..9830ed6 100644 --- a/src/espexceptions.h +++ b/include/esptk/espexceptions.h @@ -1,23 +1,23 @@ -#ifndef ESPEXCEPTIONS_H -#define ESPEXCEPTIONS_H - -#include - -namespace ESP -{ - -class InvalidRecordException : public std::runtime_error -{ -public: - InvalidRecordException(const std::string& message) : std::runtime_error(message) {} -}; - -class InvalidFileException : public std::runtime_error -{ -public: - InvalidFileException(const std::string& message) : std::runtime_error(message) {} -}; - -} // namespace ESP - -#endif // ESPEXCEPTIONS_H +#ifndef ESPEXCEPTIONS_H +#define ESPEXCEPTIONS_H + +#include + +namespace ESP +{ + +class InvalidRecordException : public std::runtime_error +{ +public: + InvalidRecordException(const std::string& message) : std::runtime_error(message) {} +}; + +class InvalidFileException : public std::runtime_error +{ +public: + InvalidFileException(const std::string& message) : std::runtime_error(message) {} +}; + +} // namespace ESP + +#endif // ESPEXCEPTIONS_H diff --git a/src/espfile.h b/include/esptk/espfile.h similarity index 95% rename from src/espfile.h rename to include/esptk/espfile.h index 98b9ec0..04be76e 100644 --- a/src/espfile.h +++ b/include/esptk/espfile.h @@ -1,72 +1,72 @@ -#ifndef ESPFILE_H -#define ESPFILE_H - -#include "record.h" -#include "tes3record.h" -#include -#include -#include - -namespace ESP -{ - -class SubRecord; - -class File -{ -public: - File(const std::string& fileName); - File(const std::wstring& fileName); - - Record readRecord(); - - bool isMaster() const; - bool isLight(bool overlaySupport = false) const; - bool isMedium() const; - bool isOverlay() const; - bool isBlueprint() const; - bool isDummy() const; - uint16_t formVersion() const { return m_MainRecord.formVersion(); }; - float headerVersion() const { return m_Header.version; } - std::string author() const { return m_Author; } - std::string description() const { return m_Description; } - std::set masters() const { return m_Masters; } - -private: - void init(); - - void onHEDR(const SubRecord& rec); - void onMAST(const SubRecord& rec); - void onCNAM(const SubRecord& rec); - void onSNAM(const SubRecord& rec); - -private: - std::ifstream m_File; - - struct - { - float version; - int32_t numRecords; - uint32_t nextObjectId; - } m_Header; - - struct - { - float version; - uint32_t unknown; - char author[32]; - char description[256]; - uint32_t numRecords; - } m_TES3Header; - - Record m_MainRecord; - - std::string m_Author; - std::string m_Description; - - std::set m_Masters; -}; - -} // namespace ESP - -#endif // ESPFILE_H +#ifndef ESPFILE_H +#define ESPFILE_H + +#include "record.h" +#include "tes3record.h" +#include +#include +#include + +namespace ESP +{ + +class SubRecord; + +class File +{ +public: + File(const std::string& fileName); + File(const std::wstring& fileName); + + Record readRecord(); + + bool isMaster() const; + bool isLight(bool overlaySupport = false) const; + bool isMedium() const; + bool isOverlay() const; + bool isBlueprint() const; + bool isDummy() const; + uint16_t formVersion() const { return m_MainRecord.formVersion(); }; + float headerVersion() const { return m_Header.version; } + std::string author() const { return m_Author; } + std::string description() const { return m_Description; } + std::set masters() const { return m_Masters; } + +private: + void init(); + + void onHEDR(const SubRecord& rec); + void onMAST(const SubRecord& rec); + void onCNAM(const SubRecord& rec); + void onSNAM(const SubRecord& rec); + +private: + std::ifstream m_File; + + struct + { + float version; + int32_t numRecords; + uint32_t nextObjectId; + } m_Header; + + struct + { + float version; + uint32_t unknown; + char author[32]; + char description[256]; + uint32_t numRecords; + } m_TES3Header; + + Record m_MainRecord; + + std::string m_Author; + std::string m_Description; + + std::set m_Masters; +}; + +} // namespace ESP + +#endif // ESPFILE_H diff --git a/src/esptypes.h b/include/esptk/esptypes.h similarity index 93% rename from src/esptypes.h rename to include/esptk/esptypes.h index 8019b63..908f6aa 100644 --- a/src/esptypes.h +++ b/include/esptk/esptypes.h @@ -1,19 +1,19 @@ -#ifndef ESPTYPES_H -#define ESPTYPES_H - -#include - -template -static T readType(std::istream& stream) -{ - union - { - char buffer[sizeof(T)]; - T value; - }; - memset(buffer, 0x42, sizeof(T)); - stream.read(buffer, sizeof(T)); - return value; -} - -#endif // ESPTYPES_H +#ifndef ESPTYPES_H +#define ESPTYPES_H + +#include + +template +static T readType(std::istream& stream) +{ + union + { + char buffer[sizeof(T)]; + T value; + }; + memset(buffer, 0x42, sizeof(T)); + stream.read(buffer, sizeof(T)); + return value; +} + +#endif // ESPTYPES_H diff --git a/src/record.h b/include/esptk/record.h similarity index 95% rename from src/record.h rename to include/esptk/record.h index cbbc0a3..3a2c996 100644 --- a/src/record.h +++ b/include/esptk/record.h @@ -1,60 +1,60 @@ -#ifndef RECORD_H -#define RECORD_H - -#include -#include -#include - -namespace ESP -{ - -/** - * @brief record storage class without record-specific information - */ -class Record -{ -public: - enum EFlag - { - FLAG_MASTER = 0x00000001, - FLAG_LIGHT_ALTERNATE = 0x00000100, // SF light flag (FE/FF memory space) - FLAG_LIGHT = 0x00000200, // SSE & FO4 light flag (FE/FF memory space) - FLAG_OVERLAY = 0x00000200, // SF overlay flag (does not claim new memory space, - // overrules light flag) - FLAG_MEDIUM = 0x00000400, // SF Creation update, indicates medium plugin type (FD - // memory space) - FLAG_BLUEPRINT = - 0x0000800, // SF blueprint flag (force loads after all other plugins) - FLAG_COMPRESSED = 0x00040000 - }; - -public: - Record(); - - bool readFrom(std::istream& stream); - - bool flagSet(EFlag flag) const; - - uint16_t formVersion() const { return m_FormVersion; } - - const std::vector& data() const { return m_Data; } - -private: - struct Header - { - char type[4]; - uint32_t dataSize; - uint32_t flags; - uint32_t id; - uint32_t revision; - } m_Header; - - uint16_t m_FormVersion; - std::vector m_Data; - - bool m_OblivionStyle; -}; - -} // namespace ESP - -#endif // RECORD_H +#ifndef RECORD_H +#define RECORD_H + +#include +#include +#include + +namespace ESP +{ + +/** + * @brief record storage class without record-specific information + */ +class Record +{ +public: + enum EFlag + { + FLAG_MASTER = 0x00000001, + FLAG_LIGHT_ALTERNATE = 0x00000100, // SF light flag (FE/FF memory space) + FLAG_LIGHT = 0x00000200, // SSE & FO4 light flag (FE/FF memory space) + FLAG_OVERLAY = 0x00000200, // SF overlay flag (does not claim new memory space, + // overrules light flag) + FLAG_MEDIUM = 0x00000400, // SF Creation update, indicates medium plugin type (FD + // memory space) + FLAG_BLUEPRINT = + 0x0000800, // SF blueprint flag (force loads after all other plugins) + FLAG_COMPRESSED = 0x00040000 + }; + +public: + Record(); + + bool readFrom(std::istream& stream); + + bool flagSet(EFlag flag) const; + + uint16_t formVersion() const { return m_FormVersion; } + + const std::vector& data() const { return m_Data; } + +private: + struct Header + { + char type[4]; + uint32_t dataSize; + uint32_t flags; + uint32_t id; + uint32_t revision; + } m_Header; + + uint16_t m_FormVersion; + std::vector m_Data; + + bool m_OblivionStyle; +}; + +} // namespace ESP + +#endif // RECORD_H diff --git a/src/subrecord.h b/include/esptk/subrecord.h similarity index 93% rename from src/subrecord.h rename to include/esptk/subrecord.h index e425a59..f9eb36c 100644 --- a/src/subrecord.h +++ b/include/esptk/subrecord.h @@ -1,44 +1,44 @@ -#ifndef SUBRECORD_H -#define SUBRECORD_H - -#include -#include -#include - -namespace ESP -{ - -/** - * @brief sub-record storage class without record-specific information - */ -class SubRecord -{ -public: - enum EType - { - TYPE_UNKNOWN, - TYPE_HEDR, - TYPE_CNAM, - TYPE_SNAM, - TYPE_MAST, - TYPE_ONAM - }; - - static const int NUM_TYPES = TYPE_ONAM; - -public: - SubRecord(); - - bool readFrom(std::istream& stream, uint32_t sizeOverride = 0UL); - - EType type() const { return m_Type; } - const std::vector& data() const { return m_Data; } - -private: - EType m_Type; - std::vector m_Data; -}; - -} // namespace ESP - -#endif // SUBRECORD_H +#ifndef SUBRECORD_H +#define SUBRECORD_H + +#include +#include +#include + +namespace ESP +{ + +/** + * @brief sub-record storage class without record-specific information + */ +class SubRecord +{ +public: + enum EType + { + TYPE_UNKNOWN, + TYPE_HEDR, + TYPE_CNAM, + TYPE_SNAM, + TYPE_MAST, + TYPE_ONAM + }; + + static const int NUM_TYPES = TYPE_ONAM; + +public: + SubRecord(); + + bool readFrom(std::istream& stream, uint32_t sizeOverride = 0UL); + + EType type() const { return m_Type; } + const std::vector& data() const { return m_Data; } + +private: + EType m_Type; + std::vector m_Data; +}; + +} // namespace ESP + +#endif // SUBRECORD_H diff --git a/src/tes3record.h b/include/esptk/tes3record.h similarity index 100% rename from src/tes3record.h rename to include/esptk/tes3record.h diff --git a/src/tes3subrecord.h b/include/esptk/tes3subrecord.h similarity index 100% rename from src/tes3subrecord.h rename to include/esptk/tes3subrecord.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1c49e1c..8f2b2d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,55 @@ cmake_minimum_required(VERSION 3.16) add_library(esptk STATIC) -mo2_configure_library(esptk PRIVATE_DEPENDS boost) -mo2_install_target(esptk) + +target_sources(esptk + PRIVATE + espfile.cpp + record.cpp + subrecord.cpp + tes3record.cpp + tes3subrecord.cpp + PUBLIC + FILE_SET HEADERS + BASE_DIRS ${CMAKE_CURRENT_LIST_DIR}/../include + FILES + ${CMAKE_CURRENT_LIST_DIR}/../include/esptk/espexceptions.h + ${CMAKE_CURRENT_LIST_DIR}/../include/esptk/espfile.h + ${CMAKE_CURRENT_LIST_DIR}/../include/esptk/esptypes.h + ${CMAKE_CURRENT_LIST_DIR}/../include/esptk/record.h + ${CMAKE_CURRENT_LIST_DIR}/../include/esptk/subrecord.h + ${CMAKE_CURRENT_LIST_DIR}/../include/esptk/tes3record.h + ${CMAKE_CURRENT_LIST_DIR}/../include/esptk/tes3subrecord.h +) + + +set_target_properties(esptk PROPERTIES CXX_STANDARD 20) + +# for building +target_include_directories(esptk PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../include/esptk) + +if (MSVC) + target_compile_options(esptk + PRIVATE + "/MP" + "/Wall" + "/external:anglebrackets" + "/external:W0" + ) + target_link_options(esptk + PRIVATE + $<$:/LTCG /INCREMENTAL:NO /OPT:REF /OPT:ICF> + ) + + set_target_properties(esptk PROPERTIES VS_STARTUP_PROJECT esptk) +endif() + +add_library(mo2::esptk ALIAS esptk) + +# isntall +install(TARGETS esptk EXPORT esptkTargets FILE_SET HEADERS) +install(EXPORT esptkTargets + FILE mo2-esptk-targets.cmake + NAMESPACE mo2:: + DESTINATION lib/cmake/mo2-esptk +) diff --git a/src/espfile.cpp b/src/espfile.cpp index 4463479..a849187 100644 --- a/src/espfile.cpp +++ b/src/espfile.cpp @@ -1,178 +1,178 @@ -#include "espfile.h" -#include "espexceptions.h" -#include "subrecord.h" -#include "tes3subrecord.h" -#include -#include - -ESP::File::File(const std::string& fileName) -{ - m_File.open(fileName, std::fstream::in | std::fstream::binary); - init(); -} - -ESP::File::File(const std::wstring& fileName) -{ - m_File.open(fileName, std::fstream::in | std::fstream::binary); - init(); -} - -class membuf : public std::basic_streambuf -{ -public: - membuf(const char* start, size_t size) - { - // baad me! this is intended for an istream only so we're not modifying - char* startMod = const_cast(start); - setg(startMod, startMod, startMod + size); - } -}; - -void ESP::File::init() -{ - if (!m_File.is_open()) { - throw ESP::InvalidFileException("file not found"); - } - m_File.exceptions(std::ios_base::badbit); - - uint8_t type[4]; - if (!m_File.read(reinterpret_cast(type), 4)) { - throw ESP::InvalidFileException("file incomplete"); - } - if (memcmp(type, "TES3", 4) == 0) { - ESP::TES3Record rec; - rec.readFrom(m_File); - - while (!m_File.eof() && !m_File.fail()) { - ESP::TES3SubRecord subRec; - bool success = subRec.readFrom(m_File); - int headerSize = sizeof(m_TES3Header); - if (success) { - if (subRec.type() != TES3SubRecord::TYPE_UNKNOWN) { - switch (subRec.type()) { - case TES3SubRecord::TYPE_HEDR: - if (subRec.data().size() != sizeof(m_TES3Header)) { - printf("invalid header size\n"); - m_Header.version = 0.0f; - m_Header.numRecords = 1; // prevent this esp appear like a dummy - } else { - memcpy(&m_TES3Header, &subRec.data()[0], sizeof(m_TES3Header)); - } - m_Header.version = m_TES3Header.version; - m_Header.numRecords = m_TES3Header.numRecords; - m_Author = reinterpret_cast(m_TES3Header.author); - m_Description = reinterpret_cast(m_TES3Header.description); - break; - case TES3SubRecord::TYPE_MAST: - if (subRec.data().size() > 0) - m_Masters.insert(reinterpret_cast(&subRec.data()[0])); - break; - } - } - } - } - } else if (memcmp(type, "TES4", 4) == 0) { - m_File.seekg(0); - - m_MainRecord = readRecord(); - - const std::vector& data = m_MainRecord.data(); - membuf buf(reinterpret_cast(&data[0]), data.size()); - - std::istream stream(&buf); - while (!stream.eof() && !stream.fail()) { - SubRecord rec; - bool success = rec.readFrom(stream); - if (success) { - if (rec.type() != SubRecord::TYPE_UNKNOWN) { - switch (rec.type()) { - case SubRecord::TYPE_HEDR: - onHEDR(rec); - break; - case SubRecord::TYPE_MAST: - onMAST(rec); - break; - case SubRecord::TYPE_CNAM: - onCNAM(rec); - break; - case SubRecord::TYPE_SNAM: - onSNAM(rec); - break; - } - } - } - } - } else { - throw ESP::InvalidFileException("invalid file type"); - } -} - -void ESP::File::onHEDR(const SubRecord& rec) -{ - if (rec.data().size() != sizeof(m_Header)) { - printf("invalid header size\n"); - m_Header.version = 0.0f; - m_Header.numRecords = 1; // prevent this esp appear like a dummy - } else { - memcpy(&m_Header, &rec.data()[0], sizeof(m_Header)); - } -} - -void ESP::File::onMAST(const SubRecord& rec) -{ - if (rec.data().size() > 0) - m_Masters.insert(reinterpret_cast(&rec.data()[0])); -} - -void ESP::File::onCNAM(const SubRecord& rec) -{ - if (rec.data().size() > 0) - m_Author = reinterpret_cast(&rec.data()[0]); -} - -void ESP::File::onSNAM(const SubRecord& rec) -{ - if (rec.data().size() > 0) - m_Description = reinterpret_cast(&rec.data()[0]); -} - -ESP::Record ESP::File::readRecord() -{ - ESP::Record rec; - rec.readFrom(m_File); - return rec; -} - -bool ESP::File::isMaster() const -{ - return m_MainRecord.flagSet(Record::FLAG_MASTER); -} - -bool ESP::File::isLight(bool overlaySupport) const -{ - if (overlaySupport) { - return m_MainRecord.flagSet(Record::FLAG_LIGHT_ALTERNATE); - } else { - return m_MainRecord.flagSet(Record::FLAG_LIGHT); - } -} - -bool ESP::File::isMedium() const -{ - return m_MainRecord.flagSet(Record::FLAG_MEDIUM); -} - -bool ESP::File::isOverlay() const -{ - return m_MainRecord.flagSet(Record::FLAG_OVERLAY); -} - -bool ESP::File::isBlueprint() const -{ - return m_MainRecord.flagSet(Record::FLAG_BLUEPRINT); -} - -bool ESP::File::isDummy() const -{ - return m_Header.numRecords == 0; -} +#include "espfile.h" +#include "espexceptions.h" +#include "subrecord.h" +#include "tes3subrecord.h" +#include +#include + +ESP::File::File(const std::string& fileName) +{ + m_File.open(fileName, std::fstream::in | std::fstream::binary); + init(); +} + +ESP::File::File(const std::wstring& fileName) +{ + m_File.open(fileName, std::fstream::in | std::fstream::binary); + init(); +} + +class membuf : public std::basic_streambuf +{ +public: + membuf(const char* start, size_t size) + { + // baad me! this is intended for an istream only so we're not modifying + char* startMod = const_cast(start); + setg(startMod, startMod, startMod + size); + } +}; + +void ESP::File::init() +{ + if (!m_File.is_open()) { + throw ESP::InvalidFileException("file not found"); + } + m_File.exceptions(std::ios_base::badbit); + + uint8_t type[4]; + if (!m_File.read(reinterpret_cast(type), 4)) { + throw ESP::InvalidFileException("file incomplete"); + } + if (memcmp(type, "TES3", 4) == 0) { + ESP::TES3Record rec; + rec.readFrom(m_File); + + while (!m_File.eof() && !m_File.fail()) { + ESP::TES3SubRecord subRec; + bool success = subRec.readFrom(m_File); + int headerSize = sizeof(m_TES3Header); + if (success) { + if (subRec.type() != TES3SubRecord::TYPE_UNKNOWN) { + switch (subRec.type()) { + case TES3SubRecord::TYPE_HEDR: + if (subRec.data().size() != sizeof(m_TES3Header)) { + printf("invalid header size\n"); + m_Header.version = 0.0f; + m_Header.numRecords = 1; // prevent this esp appear like a dummy + } else { + memcpy(&m_TES3Header, &subRec.data()[0], sizeof(m_TES3Header)); + } + m_Header.version = m_TES3Header.version; + m_Header.numRecords = m_TES3Header.numRecords; + m_Author = reinterpret_cast(m_TES3Header.author); + m_Description = reinterpret_cast(m_TES3Header.description); + break; + case TES3SubRecord::TYPE_MAST: + if (subRec.data().size() > 0) + m_Masters.insert(reinterpret_cast(&subRec.data()[0])); + break; + } + } + } + } + } else if (memcmp(type, "TES4", 4) == 0) { + m_File.seekg(0); + + m_MainRecord = readRecord(); + + const std::vector& data = m_MainRecord.data(); + membuf buf(reinterpret_cast(&data[0]), data.size()); + + std::istream stream(&buf); + while (!stream.eof() && !stream.fail()) { + SubRecord rec; + bool success = rec.readFrom(stream); + if (success) { + if (rec.type() != SubRecord::TYPE_UNKNOWN) { + switch (rec.type()) { + case SubRecord::TYPE_HEDR: + onHEDR(rec); + break; + case SubRecord::TYPE_MAST: + onMAST(rec); + break; + case SubRecord::TYPE_CNAM: + onCNAM(rec); + break; + case SubRecord::TYPE_SNAM: + onSNAM(rec); + break; + } + } + } + } + } else { + throw ESP::InvalidFileException("invalid file type"); + } +} + +void ESP::File::onHEDR(const SubRecord& rec) +{ + if (rec.data().size() != sizeof(m_Header)) { + printf("invalid header size\n"); + m_Header.version = 0.0f; + m_Header.numRecords = 1; // prevent this esp appear like a dummy + } else { + memcpy(&m_Header, &rec.data()[0], sizeof(m_Header)); + } +} + +void ESP::File::onMAST(const SubRecord& rec) +{ + if (rec.data().size() > 0) + m_Masters.insert(reinterpret_cast(&rec.data()[0])); +} + +void ESP::File::onCNAM(const SubRecord& rec) +{ + if (rec.data().size() > 0) + m_Author = reinterpret_cast(&rec.data()[0]); +} + +void ESP::File::onSNAM(const SubRecord& rec) +{ + if (rec.data().size() > 0) + m_Description = reinterpret_cast(&rec.data()[0]); +} + +ESP::Record ESP::File::readRecord() +{ + ESP::Record rec; + rec.readFrom(m_File); + return rec; +} + +bool ESP::File::isMaster() const +{ + return m_MainRecord.flagSet(Record::FLAG_MASTER); +} + +bool ESP::File::isLight(bool overlaySupport) const +{ + if (overlaySupport) { + return m_MainRecord.flagSet(Record::FLAG_LIGHT_ALTERNATE); + } else { + return m_MainRecord.flagSet(Record::FLAG_LIGHT); + } +} + +bool ESP::File::isMedium() const +{ + return m_MainRecord.flagSet(Record::FLAG_MEDIUM); +} + +bool ESP::File::isOverlay() const +{ + return m_MainRecord.flagSet(Record::FLAG_OVERLAY); +} + +bool ESP::File::isBlueprint() const +{ + return m_MainRecord.flagSet(Record::FLAG_BLUEPRINT); +} + +bool ESP::File::isDummy() const +{ + return m_Header.numRecords == 0; +} diff --git a/src/record.cpp b/src/record.cpp index b39ab0c..84c92b6 100644 --- a/src/record.cpp +++ b/src/record.cpp @@ -1,37 +1,37 @@ -#include "record.h" -#include "espexceptions.h" - -ESP::Record::Record() : m_Header(), m_FormVersion(), m_Data(), m_OblivionStyle(false) {} - -bool ESP::Record::flagSet(ESP::Record::EFlag flag) const -{ - return (m_Header.flags & flag) != 0; -} - -bool ESP::Record::readFrom(std::istream& stream) -{ - if (!stream.read(reinterpret_cast(&m_Header), sizeof(Header))) { - if (stream.gcount() == 0) { - return false; - } else { - throw ESP::InvalidRecordException("record incomplete"); - } - } - - char buf[4]; - stream.read(buf, 4); - if (memcmp(buf, "HEDR", 4) == 0) { - m_OblivionStyle = true; - stream.seekg(-4, std::istream::cur); - // Oblivion-style plugins don't have a form version - } else { - memcpy(&m_FormVersion, buf, sizeof(uint16_t)); - } - - m_Data.resize(m_Header.dataSize); - stream.read(reinterpret_cast(&m_Data[0]), m_Header.dataSize); - if (!stream) { - throw ESP::InvalidRecordException("record incomplete"); - } - return true; -} +#include "record.h" +#include "espexceptions.h" + +ESP::Record::Record() : m_Header(), m_FormVersion(), m_Data(), m_OblivionStyle(false) {} + +bool ESP::Record::flagSet(ESP::Record::EFlag flag) const +{ + return (m_Header.flags & flag) != 0; +} + +bool ESP::Record::readFrom(std::istream& stream) +{ + if (!stream.read(reinterpret_cast(&m_Header), sizeof(Header))) { + if (stream.gcount() == 0) { + return false; + } else { + throw ESP::InvalidRecordException("record incomplete"); + } + } + + char buf[4]; + stream.read(buf, 4); + if (memcmp(buf, "HEDR", 4) == 0) { + m_OblivionStyle = true; + stream.seekg(-4, std::istream::cur); + // Oblivion-style plugins don't have a form version + } else { + memcpy(&m_FormVersion, buf, sizeof(uint16_t)); + } + + m_Data.resize(m_Header.dataSize); + stream.read(reinterpret_cast(&m_Data[0]), m_Header.dataSize); + if (!stream) { + throw ESP::InvalidRecordException("record incomplete"); + } + return true; +} diff --git a/src/subrecord.cpp b/src/subrecord.cpp index aa3b2fb..a1ab078 100644 --- a/src/subrecord.cpp +++ b/src/subrecord.cpp @@ -1,57 +1,56 @@ -#include "subrecord.h" -#include "espexceptions.h" -#include "esptypes.h" -#include -#include -#include -#include - -using namespace boost::assign; - -ESP::SubRecord::SubRecord() : m_Type(TYPE_UNKNOWN), m_Data() {} - -bool ESP::SubRecord::readFrom(std::istream& stream, uint32_t sizeOverride) -{ - static std::unordered_map s_TypeMap = - map_list_of("HEDR", TYPE_HEDR)("CNAM", TYPE_CNAM)("MAST", TYPE_MAST)( - "ONAM", TYPE_ONAM)("SNAM", TYPE_SNAM); - - char typeString[5]; - if (!stream.read(typeString, 4)) { - if (stream.gcount() == 0) { - return false; - } else { - throw ESP::InvalidRecordException("sub-record incomplete (unknown type)"); - } - } - if (stream.gcount() != 4) { - throw ESP::InvalidRecordException( - std::string("sub-record type incomplete (invalid type ") + typeString + ")"); - } - typeString[4] = '\0'; // not sure if this is required, shouldn't be - auto iter = s_TypeMap.find(std::string(typeString)); - if (iter != s_TypeMap.end()) { - m_Type = iter->second; - } else if (strncmp(typeString, "XXXX", 4) == 0) { - if (readType(stream) != 4) { - throw ESP::InvalidRecordException( - "XXXX record is supposed to be 4 bytes in size"); - } - return readFrom(stream, readType(stream)); - } else { - m_Type = TYPE_UNKNOWN; - } - - uint32_t dataSize = readType(stream); - if (sizeOverride != 0UL) { - dataSize = sizeOverride; - } - m_Data.resize(dataSize); - - stream.read(reinterpret_cast(&m_Data[0]), dataSize); - if (!stream) { - throw ESP::InvalidRecordException(std::string("sub-record incomplete: ") + - typeString); - } - return true; -} +#include "subrecord.h" +#include "espexceptions.h" +#include "esptypes.h" +#include +#include +#include + +ESP::SubRecord::SubRecord() : m_Type(TYPE_UNKNOWN), m_Data() {} + +bool ESP::SubRecord::readFrom(std::istream& stream, uint32_t sizeOverride) +{ + static std::unordered_map s_TypeMap{{"HEDR", TYPE_HEDR}, + {"CNAM", TYPE_CNAM}, + {"MAST", TYPE_MAST}, + {"ONAM", TYPE_ONAM}, + {"SNAM", TYPE_SNAM}}; + + char typeString[5]; + if (!stream.read(typeString, 4)) { + if (stream.gcount() == 0) { + return false; + } else { + throw ESP::InvalidRecordException("sub-record incomplete (unknown type)"); + } + } + if (stream.gcount() != 4) { + throw ESP::InvalidRecordException( + std::string("sub-record type incomplete (invalid type ") + typeString + ")"); + } + typeString[4] = '\0'; // not sure if this is required, shouldn't be + auto iter = s_TypeMap.find(std::string(typeString)); + if (iter != s_TypeMap.end()) { + m_Type = iter->second; + } else if (strncmp(typeString, "XXXX", 4) == 0) { + if (readType(stream) != 4) { + throw ESP::InvalidRecordException( + "XXXX record is supposed to be 4 bytes in size"); + } + return readFrom(stream, readType(stream)); + } else { + m_Type = TYPE_UNKNOWN; + } + + uint32_t dataSize = readType(stream); + if (sizeOverride != 0UL) { + dataSize = sizeOverride; + } + m_Data.resize(dataSize); + + stream.read(reinterpret_cast(&m_Data[0]), dataSize); + if (!stream) { + throw ESP::InvalidRecordException(std::string("sub-record incomplete: ") + + typeString); + } + return true; +} diff --git a/src/tes3subrecord.cpp b/src/tes3subrecord.cpp index 94ac31c..46afded 100644 --- a/src/tes3subrecord.cpp +++ b/src/tes3subrecord.cpp @@ -1,19 +1,16 @@ #include "tes3subrecord.h" #include "espexceptions.h" #include "esptypes.h" -#include #include #include #include -using namespace boost::assign; - ESP::TES3SubRecord::TES3SubRecord() : m_Type(TYPE_UNKNOWN), m_Data() {} bool ESP::TES3SubRecord::readFrom(std::istream& stream, uint32_t sizeOverride) { - static std::unordered_map s_TypeMap = - map_list_of("HEDR", TYPE_HEDR)("MAST", TYPE_MAST)("DATA", TYPE_DATA); + static std::unordered_map s_TypeMap = { + {"HEDR", TYPE_HEDR}, {"MAST", TYPE_MAST}, {"DATA", TYPE_DATA}}; char typeString[5]; if (!stream.read(typeString, 4)) {