From 5fc6598b472b801e67a20f11336e2e308aac7787 Mon Sep 17 00:00:00 2001 From: Iliyas Jorio Date: Wed, 11 Nov 2020 21:06:52 +0100 Subject: [PATCH] Import source from Nanosaur port --- CMakeLists.txt | 87 + src/CompilerSupport/filesystem.h | 9 + .../filesystem_implementation.hpp | 5689 +++++++++++++++++ src/CompilerSupport/span.h | 8 + src/CompilerSupport/span_implementation.hpp | 611 ++ src/Files/Files.cpp | 337 + src/Files/HostVolume.cpp | 343 + src/Files/HostVolume.h | 44 + src/Files/Resources.cpp | 304 + src/Files/Volume.h | 59 + src/Graphics/ARGBPixmap.cpp | 73 + src/Graphics/Color.cpp | 27 + src/Graphics/Graphics.cpp | 641 ++ src/Graphics/PICT.cpp | 466 ++ src/Graphics/SysFont.h | 122 + src/Graphics/SystemPalettes.cpp | 43 + src/Input/SDLInput.cpp | 191 + src/Memory/Memory.cpp | 185 + src/Platform/Windows/PommeWindows.cpp | 17 + src/Platform/Windows/PommeWindows.h | 10 + src/Pomme.cpp | 82 + src/Pomme.h | 336 + src/PommeDebug.cpp | 57 + src/PommeDebug.h | 51 + src/PommeEnums.h | 397 ++ src/PommeFiles.h | 41 + src/PommeGraphics.h | 66 + src/PommeInit.h | 16 + src/PommeInput.h | 6 + src/PommeSound.h | 70 + src/PommeTime.h | 6 + src/PommeTypes.h | 258 + src/PommeVideo.h | 29 + src/Sound/AIFF.cpp | 88 + src/Sound/IMA4.cpp | 161 + src/Sound/MACE.cpp | 229 + src/Sound/SoundManager.cpp | 782 +++ src/Sound/cmixer.cpp | 649 ++ src/Sound/cmixer.h | 155 + src/Sound/xlaw.cpp | 91 + src/Time/TimeManager.cpp | 42 + src/Utilities/BigEndianIStream.cpp | 85 + src/Utilities/BigEndianIStream.h | 62 + src/Utilities/FixedPool.h | 46 + src/Utilities/GrowablePool.h | 90 + src/Utilities/IEEEExtended.cpp | 126 + src/Utilities/IEEEExtended.h | 5 + src/Utilities/StringUtils.cpp | 28 + src/Utilities/StringUtils.h | 15 + src/Utilities/memstream.cpp | 80 + src/Utilities/memstream.h | 34 + src/Utilities/structpack.cpp | 122 + src/Utilities/structpack.h | 53 + src/Video/Cinepak.cpp | 400 ++ src/Video/Cinepak.h | 42 + src/Video/moov.cpp | 422 ++ 56 files changed, 14488 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 src/CompilerSupport/filesystem.h create mode 100644 src/CompilerSupport/filesystem_implementation.hpp create mode 100644 src/CompilerSupport/span.h create mode 100644 src/CompilerSupport/span_implementation.hpp create mode 100644 src/Files/Files.cpp create mode 100644 src/Files/HostVolume.cpp create mode 100644 src/Files/HostVolume.h create mode 100644 src/Files/Resources.cpp create mode 100644 src/Files/Volume.h create mode 100644 src/Graphics/ARGBPixmap.cpp create mode 100644 src/Graphics/Color.cpp create mode 100644 src/Graphics/Graphics.cpp create mode 100644 src/Graphics/PICT.cpp create mode 100644 src/Graphics/SysFont.h create mode 100644 src/Graphics/SystemPalettes.cpp create mode 100644 src/Input/SDLInput.cpp create mode 100644 src/Memory/Memory.cpp create mode 100644 src/Platform/Windows/PommeWindows.cpp create mode 100644 src/Platform/Windows/PommeWindows.h create mode 100644 src/Pomme.cpp create mode 100644 src/Pomme.h create mode 100644 src/PommeDebug.cpp create mode 100644 src/PommeDebug.h create mode 100644 src/PommeEnums.h create mode 100644 src/PommeFiles.h create mode 100644 src/PommeGraphics.h create mode 100644 src/PommeInit.h create mode 100644 src/PommeInput.h create mode 100644 src/PommeSound.h create mode 100644 src/PommeTime.h create mode 100644 src/PommeTypes.h create mode 100644 src/PommeVideo.h create mode 100644 src/Sound/AIFF.cpp create mode 100644 src/Sound/IMA4.cpp create mode 100644 src/Sound/MACE.cpp create mode 100644 src/Sound/SoundManager.cpp create mode 100644 src/Sound/cmixer.cpp create mode 100644 src/Sound/cmixer.h create mode 100644 src/Sound/xlaw.cpp create mode 100644 src/Time/TimeManager.cpp create mode 100644 src/Utilities/BigEndianIStream.cpp create mode 100644 src/Utilities/BigEndianIStream.h create mode 100644 src/Utilities/FixedPool.h create mode 100644 src/Utilities/GrowablePool.h create mode 100644 src/Utilities/IEEEExtended.cpp create mode 100644 src/Utilities/IEEEExtended.h create mode 100644 src/Utilities/StringUtils.cpp create mode 100644 src/Utilities/StringUtils.h create mode 100644 src/Utilities/memstream.cpp create mode 100644 src/Utilities/memstream.h create mode 100644 src/Utilities/structpack.cpp create mode 100644 src/Utilities/structpack.h create mode 100644 src/Video/Cinepak.cpp create mode 100644 src/Video/Cinepak.h create mode 100644 src/Video/moov.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..af7b823 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,87 @@ +cmake_minimum_required(VERSION 3.17) +set(CMAKE_CXX_STANDARD 20) +project(Pomme CXX) + +set(POMME_SRCDIR src) + +set(POMME_SOURCES + ${POMME_SRCDIR}/Pomme.cpp + ${POMME_SRCDIR}/Pomme.h + ${POMME_SRCDIR}/PommeDebug.cpp + ${POMME_SRCDIR}/PommeDebug.h + ${POMME_SRCDIR}/PommeEnums.h + ${POMME_SRCDIR}/PommeFiles.h + ${POMME_SRCDIR}/PommeGraphics.h + ${POMME_SRCDIR}/PommeInit.h + ${POMME_SRCDIR}/PommeInput.h + ${POMME_SRCDIR}/PommeSound.h + ${POMME_SRCDIR}/PommeTime.h + ${POMME_SRCDIR}/PommeTypes.h + ${POMME_SRCDIR}/PommeVideo.h + ${POMME_SRCDIR}/Files/Files.cpp + ${POMME_SRCDIR}/Files/HostVolume.cpp + ${POMME_SRCDIR}/Files/HostVolume.h + ${POMME_SRCDIR}/Files/Resources.cpp + ${POMME_SRCDIR}/Files/Volume.h + ${POMME_SRCDIR}/Graphics/ARGBPixmap.cpp + ${POMME_SRCDIR}/Graphics/Color.cpp + ${POMME_SRCDIR}/Graphics/Graphics.cpp + ${POMME_SRCDIR}/Graphics/PICT.cpp + ${POMME_SRCDIR}/Graphics/SysFont.h + ${POMME_SRCDIR}/Graphics/SystemPalettes.cpp + ${POMME_SRCDIR}/Input/SDLInput.cpp + ${POMME_SRCDIR}/Memory/Memory.cpp + ${POMME_SRCDIR}/Sound/AIFF.cpp + ${POMME_SRCDIR}/Sound/cmixer.cpp + ${POMME_SRCDIR}/Sound/cmixer.h + ${POMME_SRCDIR}/Sound/IMA4.cpp + ${POMME_SRCDIR}/Sound/MACE.cpp + ${POMME_SRCDIR}/Sound/SoundManager.cpp + ${POMME_SRCDIR}/Sound/xlaw.cpp + ${POMME_SRCDIR}/Time/TimeManager.cpp + ${POMME_SRCDIR}/Utilities/BigEndianIStream.cpp + ${POMME_SRCDIR}/Utilities/FixedPool.h + ${POMME_SRCDIR}/Utilities/GrowablePool.h + ${POMME_SRCDIR}/Utilities/IEEEExtended.cpp + ${POMME_SRCDIR}/Utilities/IEEEExtended.h + ${POMME_SRCDIR}/Utilities/memstream.cpp + ${POMME_SRCDIR}/Utilities/memstream.h + ${POMME_SRCDIR}/Utilities/StringUtils.cpp + ${POMME_SRCDIR}/Utilities/StringUtils.h + ${POMME_SRCDIR}/Utilities/structpack.cpp + ${POMME_SRCDIR}/Utilities/structpack.h + ${POMME_SRCDIR}/Video/Cinepak.cpp + ${POMME_SRCDIR}/Video/Cinepak.h + ${POMME_SRCDIR}/Video/moov.cpp + $<$:${POMME_SRCDIR}/Platform/Windows/PommeWindows.cpp> + $<$:${POMME_SRCDIR}/Platform/Windows/PommeWindows.h> +) + +add_library(${PROJECT_NAME} ${POMME_SOURCES}) + +find_package(SDL2 REQUIRED) + +target_include_directories(${PROJECT_NAME} PRIVATE + ${SDL2_INCLUDE_DIRS} + ${POMME_SRCDIR} +) + +if(MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE + /EHa + /W4 + /wd4100 # unreferenced formal parameter + /wd4201 # nonstandard extension + /wd4244 # conversion from double to float + /wd4458 # declaration of variable hides class member + ) +else() + target_compile_options(${PROJECT_NAME} PRIVATE + -Wall + -Wextra + -Wshadow + -Wno-multichar + -Wno-unused-parameter + -fexceptions + ) +endif() diff --git a/src/CompilerSupport/filesystem.h b/src/CompilerSupport/filesystem.h new file mode 100644 index 0000000..55bbbd0 --- /dev/null +++ b/src/CompilerSupport/filesystem.h @@ -0,0 +1,9 @@ +#pragma once + +#if defined(__cplusplus) && __cplusplus >= 201703L && defined(__has_include) && __has_include() + #include + namespace fs = std::filesystem; +#else + #include "CompilerSupport/filesystem_implementation.hpp" + namespace fs = ghc::filesystem; +#endif diff --git a/src/CompilerSupport/filesystem_implementation.hpp b/src/CompilerSupport/filesystem_implementation.hpp new file mode 100644 index 0000000..06a0cbe --- /dev/null +++ b/src/CompilerSupport/filesystem_implementation.hpp @@ -0,0 +1,5689 @@ +//--------------------------------------------------------------------------------------- +// +// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14/C++17 +// +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2018, Steffen Schümann +// +// 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. +// +//--------------------------------------------------------------------------------------- +// +// To dynamically select std::filesystem where available, you could use: +// +// #if defined(__cplusplus) && __cplusplus >= 201703L && defined(__has_include) && __has_include() +// #include +// namespace fs = std::filesystem; +// #else +// #include +// namespace fs = ghc::filesystem; +// #endif +// +//--------------------------------------------------------------------------------------- +#ifndef GHC_FILESYSTEM_H +#define GHC_FILESYSTEM_H + +// #define BSD manifest constant only in +// sys/param.h +#ifndef _WIN32 +#include +#endif + +#ifndef GHC_OS_DETECTED +#if defined(__APPLE__) && defined(__MACH__) +#define GHC_OS_MACOS +#elif defined(__linux__) +#define GHC_OS_LINUX +#if defined(__ANDROID__) +#define GHC_OS_ANDROID +#endif +#elif defined(_WIN64) +#define GHC_OS_WINDOWS +#define GHC_OS_WIN64 +#elif defined(_WIN32) +#define GHC_OS_WINDOWS +#define GHC_OS_WIN32 +#elif defined(__svr4__) +#define GHC_OS_SYS5R4 +#elif defined(BSD) +#define GHC_OS_BSD +#elif defined(__EMSCRIPTEN__) +#define GHC_OS_WEB +#include +#else +#error "Operating system currently not supported!" +#endif +#define GHC_OS_DETECTED +#endif + +#if defined(GHC_FILESYSTEM_IMPLEMENTATION) +#define GHC_EXPAND_IMPL +#define GHC_INLINE +#ifdef GHC_OS_WINDOWS +#define GHC_FS_API +#define GHC_FS_API_CLASS +#else +#define GHC_FS_API __attribute__((visibility("default"))) +#define GHC_FS_API_CLASS __attribute__((visibility("default"))) +#endif +#elif defined(GHC_FILESYSTEM_FWD) +#define GHC_INLINE +#ifdef GHC_OS_WINDOWS +#define GHC_FS_API extern +#define GHC_FS_API_CLASS +#else +#define GHC_FS_API extern +#define GHC_FS_API_CLASS +#endif +#else +#define GHC_EXPAND_IMPL +#define GHC_INLINE inline +#define GHC_FS_API +#define GHC_FS_API_CLASS +#endif + +#ifdef GHC_EXPAND_IMPL + +#ifdef GHC_OS_WINDOWS +#include +// additional includes +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef GHC_OS_ANDROID +#include +#if __ANDROID_API__ < 12 +#include +#endif +#include +#define statvfs statfs +#else +#include +#endif +#if !defined(__ANDROID__) || __ANDROID_API__ >= 26 +#include +#endif +#endif +#ifdef GHC_OS_MACOS +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#else // GHC_EXPAND_IMPL +#include +#include +#include +#include +#include +#include +#include +#ifdef GHC_OS_WINDOWS +#include +#endif +#endif // GHC_EXPAND_IMPL + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Behaviour Switches (see README.md, should match the config in test/filesystem_test.cpp): +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2682 disables the since then invalid use of the copy option create_symlinks on directories +// configure LWG conformance () +#define LWG_2682_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2395 makes crate_directory/create_directories not emit an error if there is a regular +// file with that name, it is superceded by P1164R1, so only activate if really needed +// #define LWG_2935_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2936 enables new element wise (more expensive) path comparison +// * if this->root_name().native().compare(p.root_name().native()) != 0 return result +// * if this->has_root_directory() and !p.has_root_directory() return -1 +// * if !this->has_root_directory() and p.has_root_directory() return -1 +// * else result of element wise comparison of path iteration where first comparison is != 0 or 0 +// if all comparisons are 0 (on Windows this implementation does case insensitive root_name() +// comparison) +// #define LWG_2936_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2937 enforces that fs::equivalent emits an error, if !fs::exists(p1)||!exists(p2) +#define LWG_2937_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// UTF8-Everywhere is the original behaviour of ghc::filesystem. With this define you can +// enable the more standard conforming implementation option that uses wstring on Windows +// as ghc::filesystem::string_type. +// #define GHC_WIN_WSTRING_STRING_TYPE +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Raise errors/exceptions when invalid unicode codepoints or UTF-8 sequences are found, +// instead of replacing them with the unicode replacement character (U+FFFD). +// #define GHC_RAISE_UNICODE_ERRORS +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// ghc::filesystem version in decimal (major * 10000 + minor * 100 + patch) +#define GHC_FILESYSTEM_VERSION 10306L + +#if !defined(GHC_WITH_EXCEPTIONS) && (defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)) +#define GHC_WITH_EXCEPTIONS +#endif +#if !defined(GHC_WITH_EXCEPTIONS) && defined(GHC_RAISE_UNICODE_ERRORS) +#error "Can't raise unicode errors whith exception support disabled" +#endif + +namespace ghc { +namespace filesystem { + +// temporary existing exception type for yet unimplemented parts +class GHC_FS_API_CLASS not_implemented_exception : public std::logic_error +{ +public: + not_implemented_exception() + : std::logic_error("function not implemented yet.") + { + } +}; + +template +class path_helper_base +{ +public: + using value_type = char_type; +#ifdef GHC_OS_WINDOWS + static constexpr value_type preferred_separator = '\\'; +#else + static constexpr value_type preferred_separator = '/'; +#endif +}; + +#if __cplusplus < 201703L +template +constexpr char_type path_helper_base::preferred_separator; +#endif + + +#ifdef GHC_OS_WINDOWS +class path; +namespace detail { +bool has_executable_extension(const path& p); +} +#endif + + + // 30.10.8 class path +class GHC_FS_API_CLASS path +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_WSTRING_STRING_TYPE) +#define GHC_USE_WCHAR_T + : private path_helper_base +{ +public: + using path_helper_base::value_type; +#else + : private path_helper_base +{ +public: + using path_helper_base::value_type; +#endif + using string_type = std::basic_string; + using path_helper_base::preferred_separator; + + // 30.10.10.1 enumeration format + /// The path format in wich the constructor argument is given. + enum format { + generic_format, ///< The generic format, internally used by + ///< ghc::filesystem::path with slashes + native_format, ///< The format native to the current platform this code + ///< is build for + auto_format, ///< Try to auto-detect the format, fallback to native + }; + + template + struct _is_basic_string : std::false_type + { + }; + template + struct _is_basic_string> : std::true_type + { + }; +#ifdef __cpp_lib_string_view + template + struct _is_basic_string> : std::true_type + { + }; +#endif + + template + using path_type = typename std::enable_if::value, path>::type; +#ifdef GHC_USE_WCHAR_T + template + using path_from_string = typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value, + path>::type; + template + using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value, path>::type; +#else + template + using path_from_string = typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value, path>::type; + template + using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; +#endif + // 30.10.8.4.1 constructors and destructor + path() noexcept; + path(const path& p); + path(path&& p) noexcept; + path(string_type&& source, format fmt = auto_format); + template > + path(const Source& source, format fmt = auto_format); + template + path(InputIterator first, InputIterator last, format fmt = auto_format); +#ifdef GHC_WITH_EXCEPTIONS + template > + path(const Source& source, const std::locale& loc, format fmt = auto_format); + template + path(InputIterator first, InputIterator last, const std::locale& loc, format fmt = auto_format); +#endif + ~path(); + + // 30.10.8.4.2 assignments + path& operator=(const path& p); + path& operator=(path&& p) noexcept; + path& operator=(string_type&& source); + path& assign(string_type&& source); + template + path& operator=(const Source& source); + template + path& assign(const Source& source); + template + path& assign(InputIterator first, InputIterator last); + + // 30.10.8.4.3 appends + path& operator/=(const path& p); + template + path& operator/=(const Source& source); + template + path& append(const Source& source); + template + path& append(InputIterator first, InputIterator last); + + // 30.10.8.4.4 concatenation + path& operator+=(const path& x); + path& operator+=(const string_type& x); +#ifdef __cpp_lib_string_view + path& operator+=(std::basic_string_view x); +#endif + path& operator+=(const value_type* x); + path& operator+=(value_type x); + template + path_from_string& operator+=(const Source& x); + template + path_type_EcharT& operator+=(EcharT x); + template + path& concat(const Source& x); + template + path& concat(InputIterator first, InputIterator last); + + // 30.10.8.4.5 modifiers + void clear() noexcept; + path& make_preferred(); + path& remove_filename(); + path& replace_filename(const path& replacement); + path& replace_extension(const path& replacement = path()); + void swap(path& rhs) noexcept; + + // 30.10.8.4.6 native format observers + const string_type& native() const; // this implementation doesn't support noexcept for native() + const value_type* c_str() const; // this implementation doesn't support noexcept for c_str() + operator string_type() const; + template , class Allocator = std::allocator> + std::basic_string string(const Allocator& a = Allocator()) const; + std::string string() const; + std::wstring wstring() const; + std::string u8string() const; + std::u16string u16string() const; + std::u32string u32string() const; + + // 30.10.8.4.7 generic format observers + template , class Allocator = std::allocator> + std::basic_string generic_string(const Allocator& a = Allocator()) const; + const std::string& generic_string() const; // this is different from the standard, that returns by value + std::wstring generic_wstring() const; + std::string generic_u8string() const; + std::u16string generic_u16string() const; + std::u32string generic_u32string() const; + + // 30.10.8.4.8 compare + int compare(const path& p) const noexcept; + int compare(const string_type& s) const; +#ifdef __cpp_lib_string_view + int compare(std::basic_string_view s) const; +#endif + int compare(const value_type* s) const; + + // 30.10.8.4.9 decomposition + path root_name() const; + path root_directory() const; + path root_path() const; + path relative_path() const; + path parent_path() const; + path filename() const; + path stem() const; + path extension() const; + + // 30.10.8.4.10 query + bool empty() const noexcept; + bool has_root_name() const; + bool has_root_directory() const; + bool has_root_path() const; + bool has_relative_path() const; + bool has_parent_path() const; + bool has_filename() const; + bool has_stem() const; + bool has_extension() const; + bool is_absolute() const; + bool is_relative() const; + + // 30.10.8.4.11 generation + path lexically_normal() const; + path lexically_relative(const path& base) const; + path lexically_proximate(const path& base) const; + + // 30.10.8.5 iterators + class iterator; + using const_iterator = iterator; + iterator begin() const; + iterator end() const; + +private: + using impl_value_type = std::string::value_type; + using impl_string_type = std::basic_string; + friend class directory_iterator; + void append_name(const char* name); + static constexpr impl_value_type generic_separator = '/'; + template + class input_iterator_range + { + public: + typedef InputIterator iterator; + typedef InputIterator const_iterator; + typedef typename InputIterator::difference_type difference_type; + + input_iterator_range(const InputIterator& first, const InputIterator& last) + : _first(first) + , _last(last) + { + } + + InputIterator begin() const { return _first; } + InputIterator end() const { return _last; } + + private: + InputIterator _first; + InputIterator _last; + }; + friend void swap(path& lhs, path& rhs) noexcept; + friend size_t hash_value(const path& p) noexcept; + string_type::size_type root_name_length() const noexcept; + static void postprocess_path_with_format(impl_string_type& p, format fmt); + impl_string_type _path; +#ifdef GHC_OS_WINDOWS + friend bool detail::has_executable_extension(const path& p); + impl_string_type native_impl() const; + mutable string_type _native_cache; +#else + const impl_string_type& native_impl() const; +#endif +}; + +// 30.10.8.6 path non-member functions +GHC_FS_API void swap(path& lhs, path& rhs) noexcept; +GHC_FS_API size_t hash_value(const path& p) noexcept; +GHC_FS_API bool operator==(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator!=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator<(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator<=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator>(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator>=(const path& lhs, const path& rhs) noexcept; + +GHC_FS_API path operator/(const path& lhs, const path& rhs); + +// 30.10.8.6.1 path inserter and extractor +template +std::basic_ostream& operator<<(std::basic_ostream& os, const path& p); +template +std::basic_istream& operator>>(std::basic_istream& is, path& p); + +// 30.10.8.6.2 path factory functions +template > +path u8path(const Source& source); +template +path u8path(InputIterator first, InputIterator last); + +// 30.10.9 class filesystem_error +class GHC_FS_API_CLASS filesystem_error : public std::system_error +{ +public: + filesystem_error(const std::string& what_arg, std::error_code ec); + filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec); + filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec); + const path& path1() const noexcept; + const path& path2() const noexcept; + const char* what() const noexcept override; + +private: + std::string _what_arg; + std::error_code _ec; + path _p1, _p2; +}; + +class GHC_FS_API_CLASS path::iterator +{ +public: + using value_type = const path; + using difference_type = std::ptrdiff_t; + using pointer = const path*; + using reference = const path&; + using iterator_category = std::bidirectional_iterator_tag; + + iterator(); + iterator(const impl_string_type::const_iterator& first, const impl_string_type::const_iterator& last, const impl_string_type::const_iterator& pos); + iterator& operator++(); + iterator operator++(int); + iterator& operator--(); + iterator operator--(int); + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const; + reference operator*() const; + pointer operator->() const; + +private: + impl_string_type::const_iterator increment(const std::string::const_iterator& pos) const; + impl_string_type::const_iterator decrement(const std::string::const_iterator& pos) const; + void updateCurrent(); + impl_string_type::const_iterator _first; + impl_string_type::const_iterator _last; + impl_string_type::const_iterator _root; + impl_string_type::const_iterator _iter; + path _current; +}; + +struct space_info +{ + uintmax_t capacity; + uintmax_t free; + uintmax_t available; +}; + +// 30.10.10, enumerations +enum class file_type { + none, + not_found, + regular, + directory, + symlink, + block, + character, + fifo, + socket, + unknown, +}; + +enum class perms : uint16_t { + none = 0, + + owner_read = 0400, + owner_write = 0200, + owner_exec = 0100, + owner_all = 0700, + + group_read = 040, + group_write = 020, + group_exec = 010, + group_all = 070, + + others_read = 04, + others_write = 02, + others_exec = 01, + others_all = 07, + + all = 0777, + set_uid = 04000, + set_gid = 02000, + sticky_bit = 01000, + + mask = 07777, + unknown = 0xffff +}; + +enum class perm_options : uint16_t { + replace = 3, + add = 1, + remove = 2, + nofollow = 4, +}; + +enum class copy_options : uint16_t { + none = 0, + + skip_existing = 1, + overwrite_existing = 2, + update_existing = 4, + + recursive = 8, + + copy_symlinks = 0x10, + skip_symlinks = 0x20, + + directories_only = 0x40, + create_symlinks = 0x80, +#ifndef GHC_OS_WEB + create_hard_links = 0x100 +#endif +}; + +enum class directory_options : uint16_t { + none = 0, + follow_directory_symlink = 1, + skip_permission_denied = 2, +}; + +// 30.10.11 class file_status +class GHC_FS_API_CLASS file_status +{ +public: + // 30.10.11.1 constructors and destructor + file_status() noexcept; + explicit file_status(file_type ft, perms prms = perms::unknown) noexcept; + file_status(const file_status&) noexcept; + file_status(file_status&&) noexcept; + ~file_status(); + // assignments: + file_status& operator=(const file_status&) noexcept; + file_status& operator=(file_status&&) noexcept; + // 30.10.11.3 modifiers + void type(file_type ft) noexcept; + void permissions(perms prms) noexcept; + // 30.10.11.2 observers + file_type type() const noexcept; + perms permissions() const noexcept; + +private: + file_type _type; + perms _perms; +}; + +using file_time_type = std::chrono::time_point; + +// 30.10.12 Class directory_entry +class GHC_FS_API_CLASS directory_entry +{ +public: + // 30.10.12.1 constructors and destructor + directory_entry() noexcept = default; + directory_entry(const directory_entry&) = default; + directory_entry(directory_entry&&) noexcept = default; +#ifdef GHC_WITH_EXCEPTIONS + explicit directory_entry(const path& p); +#endif + directory_entry(const path& p, std::error_code& ec); + ~directory_entry(); + + // assignments: + directory_entry& operator=(const directory_entry&) = default; + directory_entry& operator=(directory_entry&&) noexcept = default; + + // 30.10.12.2 modifiers +#ifdef GHC_WITH_EXCEPTIONS + void assign(const path& p); +#endif + void assign(const path& p, std::error_code& ec); +#ifdef GHC_WITH_EXCEPTIONS + void replace_filename(const path& p); +#endif + void replace_filename(const path& p, std::error_code& ec); +#ifdef GHC_WITH_EXCEPTIONS + void refresh(); +#endif + void refresh(std::error_code& ec) noexcept; + + // 30.10.12.3 observers + const filesystem::path& path() const noexcept; + operator const filesystem::path&() const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool exists() const; +#endif + bool exists(std::error_code& ec) const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool is_block_file() const; +#endif + bool is_block_file(std::error_code& ec) const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool is_character_file() const; +#endif + bool is_character_file(std::error_code& ec) const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool is_directory() const; +#endif + bool is_directory(std::error_code& ec) const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool is_fifo() const; +#endif + bool is_fifo(std::error_code& ec) const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool is_other() const; +#endif + bool is_other(std::error_code& ec) const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool is_regular_file() const; +#endif + bool is_regular_file(std::error_code& ec) const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool is_socket() const; +#endif + bool is_socket(std::error_code& ec) const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool is_symlink() const; +#endif + bool is_symlink(std::error_code& ec) const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + uintmax_t file_size() const; +#endif + uintmax_t file_size(std::error_code& ec) const noexcept; + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS + uintmax_t hard_link_count() const; +#endif + uintmax_t hard_link_count(std::error_code& ec) const noexcept; +#endif + +#ifdef GHC_WITH_EXCEPTIONS + file_time_type last_write_time() const; +#endif + file_time_type last_write_time(std::error_code& ec) const noexcept; + +#ifdef GHC_WITH_EXCEPTIONS + file_status status() const; +#endif + file_status status(std::error_code& ec) const noexcept; + +#ifdef GHC_WITH_EXCEPTIONS + file_status symlink_status() const; +#endif + file_status symlink_status(std::error_code& ec) const noexcept; + bool operator<(const directory_entry& rhs) const noexcept; + bool operator==(const directory_entry& rhs) const noexcept; + bool operator!=(const directory_entry& rhs) const noexcept; + bool operator<=(const directory_entry& rhs) const noexcept; + bool operator>(const directory_entry& rhs) const noexcept; + bool operator>=(const directory_entry& rhs) const noexcept; + +private: + friend class directory_iterator; + filesystem::path _path; + file_status _status; + file_status _symlink_status; + uintmax_t _file_size = 0; +#ifndef GHC_OS_WINDOWS + uintmax_t _hard_link_count = 0; +#endif + time_t _last_write_time = 0; +}; + +// 30.10.13 Class directory_iterator +class GHC_FS_API_CLASS directory_iterator +{ +public: + class GHC_FS_API_CLASS proxy + { + public: + const directory_entry& operator*() const& noexcept { return _dir_entry; } + directory_entry operator*() && noexcept { return std::move(_dir_entry); } + + private: + explicit proxy(const directory_entry& dir_entry) + : _dir_entry(dir_entry) + { + } + friend class directory_iterator; + friend class recursive_directory_iterator; + directory_entry _dir_entry; + }; + using iterator_category = std::input_iterator_tag; + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const directory_entry*; + using reference = const directory_entry&; + + // 30.10.13.1 member functions + directory_iterator() noexcept; +#ifdef GHC_WITH_EXCEPTIONS + explicit directory_iterator(const path& p); + directory_iterator(const path& p, directory_options options); +#endif + directory_iterator(const path& p, std::error_code& ec) noexcept; + directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; + directory_iterator(const directory_iterator& rhs); + directory_iterator(directory_iterator&& rhs) noexcept; + ~directory_iterator(); + directory_iterator& operator=(const directory_iterator& rhs); + directory_iterator& operator=(directory_iterator&& rhs) noexcept; + const directory_entry& operator*() const; + const directory_entry* operator->() const; +#ifdef GHC_WITH_EXCEPTIONS + directory_iterator& operator++(); +#endif + directory_iterator& increment(std::error_code& ec) noexcept; + + // other members as required by 27.2.3, input iterators +#ifdef GHC_WITH_EXCEPTIONS + proxy operator++(int) + { + proxy p{**this}; + ++*this; + return p; + } +#endif + bool operator==(const directory_iterator& rhs) const; + bool operator!=(const directory_iterator& rhs) const; + +private: + friend class recursive_directory_iterator; + class impl; + std::shared_ptr _impl; +}; + +// 30.10.13.2 directory_iterator non-member functions +GHC_FS_API directory_iterator begin(directory_iterator iter) noexcept; +GHC_FS_API directory_iterator end(const directory_iterator&) noexcept; + +// 30.10.14 class recursive_directory_iterator +class GHC_FS_API_CLASS recursive_directory_iterator +{ +public: + using iterator_category = std::input_iterator_tag; + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const directory_entry*; + using reference = const directory_entry&; + + // 30.10.14.1 constructors and destructor + recursive_directory_iterator() noexcept; +#ifdef GHC_WITH_EXCEPTIONS + explicit recursive_directory_iterator(const path& p); + recursive_directory_iterator(const path& p, directory_options options); +#endif + recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; + recursive_directory_iterator(const path& p, std::error_code& ec) noexcept; + recursive_directory_iterator(const recursive_directory_iterator& rhs); + recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept; + ~recursive_directory_iterator(); + + // 30.10.14.1 observers + directory_options options() const; + int depth() const; + bool recursion_pending() const; + + const directory_entry& operator*() const; + const directory_entry* operator->() const; + + // 30.10.14.1 modifiers recursive_directory_iterator& + recursive_directory_iterator& operator=(const recursive_directory_iterator& rhs); + recursive_directory_iterator& operator=(recursive_directory_iterator&& rhs) noexcept; +#ifdef GHC_WITH_EXCEPTIONS + recursive_directory_iterator& operator++(); +#endif + recursive_directory_iterator& increment(std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS + void pop(); +#endif + void pop(std::error_code& ec); + void disable_recursion_pending(); + + // other members as required by 27.2.3, input iterators +#ifdef GHC_WITH_EXCEPTIONS + directory_iterator::proxy operator++(int) + { + directory_iterator::proxy proxy{**this}; + ++*this; + return proxy; + } +#endif + bool operator==(const recursive_directory_iterator& rhs) const; + bool operator!=(const recursive_directory_iterator& rhs) const; + +private: + struct recursive_directory_iterator_impl + { + directory_options _options; + bool _recursion_pending; + std::stack _dir_iter_stack; + recursive_directory_iterator_impl(directory_options options, bool recursion_pending) + : _options(options) + , _recursion_pending(recursion_pending) + { + } + }; + std::shared_ptr _impl; +}; + +// 30.10.14.2 directory_iterator non-member functions +GHC_FS_API recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept; +GHC_FS_API recursive_directory_iterator end(const recursive_directory_iterator&) noexcept; + +// 30.10.15 filesystem operations +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API path absolute(const path& p); +#endif +GHC_FS_API path absolute(const path& p, std::error_code& ec); + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API path canonical(const path& p); +#endif +GHC_FS_API path canonical(const path& p, std::error_code& ec); + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void copy(const path& from, const path& to); +#endif +GHC_FS_API void copy(const path& from, const path& to, std::error_code& ec) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void copy(const path& from, const path& to, copy_options options); +#endif +GHC_FS_API void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool copy_file(const path& from, const path& to); +#endif +GHC_FS_API bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option); +#endif +GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink); +#endif +GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool create_directories(const path& p); +#endif +GHC_FS_API bool create_directories(const path& p, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool create_directory(const path& p); +#endif +GHC_FS_API bool create_directory(const path& p, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool create_directory(const path& p, const path& attributes); +#endif +GHC_FS_API bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink); +#endif +GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link); +#endif +GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept; +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void create_symlink(const path& to, const path& new_symlink); +#endif +GHC_FS_API void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API path current_path(); +#endif +GHC_FS_API path current_path(std::error_code& ec); +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void current_path(const path& p); +#endif +GHC_FS_API void current_path(const path& p, std::error_code& ec) noexcept; + +GHC_FS_API bool exists(file_status s) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool exists(const path& p); +#endif +GHC_FS_API bool exists(const path& p, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool equivalent(const path& p1, const path& p2); +#endif +GHC_FS_API bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API uintmax_t file_size(const path& p); +#endif +GHC_FS_API uintmax_t file_size(const path& p, std::error_code& ec) noexcept; + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API uintmax_t hard_link_count(const path& p); +#endif +GHC_FS_API uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept; +#endif + +GHC_FS_API bool is_block_file(file_status s) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool is_block_file(const path& p); +#endif +GHC_FS_API bool is_block_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_character_file(file_status s) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool is_character_file(const path& p); +#endif +GHC_FS_API bool is_character_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_directory(file_status s) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool is_directory(const path& p); +#endif +GHC_FS_API bool is_directory(const path& p, std::error_code& ec) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool is_empty(const path& p); +#endif +GHC_FS_API bool is_empty(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_fifo(file_status s) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool is_fifo(const path& p); +#endif +GHC_FS_API bool is_fifo(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_other(file_status s) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool is_other(const path& p); +#endif +GHC_FS_API bool is_other(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_regular_file(file_status s) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool is_regular_file(const path& p); +#endif +GHC_FS_API bool is_regular_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_socket(file_status s) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool is_socket(const path& p); +#endif +GHC_FS_API bool is_socket(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_symlink(file_status s) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool is_symlink(const path& p); +#endif +GHC_FS_API bool is_symlink(const path& p, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API file_time_type last_write_time(const path& p); +#endif +GHC_FS_API file_time_type last_write_time(const path& p, std::error_code& ec) noexcept; +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void last_write_time(const path& p, file_time_type new_time); +#endif +GHC_FS_API void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void permissions(const path& p, perms prms, perm_options opts = perm_options::replace); +#endif +GHC_FS_API void permissions(const path& p, perms prms, std::error_code& ec) noexcept; +GHC_FS_API void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec); + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API path proximate(const path& p, std::error_code& ec); +GHC_FS_API path proximate(const path& p, const path& base = current_path()); +#endif +GHC_FS_API path proximate(const path& p, const path& base, std::error_code& ec); + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API path read_symlink(const path& p); +#endif +GHC_FS_API path read_symlink(const path& p, std::error_code& ec); + +GHC_FS_API path relative(const path& p, std::error_code& ec); +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API path relative(const path& p, const path& base = current_path()); +#endif +GHC_FS_API path relative(const path& p, const path& base, std::error_code& ec); + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API bool remove(const path& p); +#endif +GHC_FS_API bool remove(const path& p, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API uintmax_t remove_all(const path& p); +#endif +GHC_FS_API uintmax_t remove_all(const path& p, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void rename(const path& from, const path& to); +#endif +GHC_FS_API void rename(const path& from, const path& to, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void resize_file(const path& p, uintmax_t size); +#endif +GHC_FS_API void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API space_info space(const path& p); +#endif +GHC_FS_API space_info space(const path& p, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API file_status status(const path& p); +#endif +GHC_FS_API file_status status(const path& p, std::error_code& ec) noexcept; + +GHC_FS_API bool status_known(file_status s) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API file_status symlink_status(const path& p); +#endif +GHC_FS_API file_status symlink_status(const path& p, std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API path temp_directory_path(); +#endif +GHC_FS_API path temp_directory_path(std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API path weakly_canonical(const path& p); +#endif +GHC_FS_API path weakly_canonical(const path& p, std::error_code& ec) noexcept; + +// Non-C++17 add-on std::fstream wrappers with path +template > +class basic_filebuf : public std::basic_filebuf +{ +public: + basic_filebuf() {} + ~basic_filebuf() override {} + basic_filebuf(const basic_filebuf&) = delete; + const basic_filebuf& operator=(const basic_filebuf&) = delete; + basic_filebuf* open(const path& p, std::ios_base::openmode mode) + { +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + return std::basic_filebuf::open(p.wstring().c_str(), mode) ? this : 0; +#else + return std::basic_filebuf::open(p.string().c_str(), mode) ? this : 0; +#endif + } +}; + +template > +class basic_ifstream : public std::basic_ifstream +{ +public: + basic_ifstream() {} +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) + : std::basic_ifstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) + : std::basic_ifstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.string().c_str(), mode); } +#endif + basic_ifstream(const basic_ifstream&) = delete; + const basic_ifstream& operator=(const basic_ifstream&) = delete; + ~basic_ifstream() override {} +}; + +template > +class basic_ofstream : public std::basic_ofstream +{ +public: + basic_ofstream() {} +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) + : std::basic_ofstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) + : std::basic_ofstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.string().c_str(), mode); } +#endif + basic_ofstream(const basic_ofstream&) = delete; + const basic_ofstream& operator=(const basic_ofstream&) = delete; + ~basic_ofstream() override {} +}; + +template > +class basic_fstream : public std::basic_fstream +{ +public: + basic_fstream() {} +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) + : std::basic_fstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) + : std::basic_fstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.string().c_str(), mode); } +#endif + basic_fstream(const basic_fstream&) = delete; + const basic_fstream& operator=(const basic_fstream&) = delete; + ~basic_fstream() override {} +}; + +typedef basic_filebuf filebuf; +typedef basic_filebuf wfilebuf; +typedef basic_ifstream ifstream; +typedef basic_ifstream wifstream; +typedef basic_ofstream ofstream; +typedef basic_ofstream wofstream; +typedef basic_fstream fstream; +typedef basic_fstream wfstream; + +class GHC_FS_API_CLASS u8arguments +{ +public: + u8arguments(int& argc, char**& argv); + ~u8arguments() + { + _refargc = _argc; + _refargv = _argv; + } + + bool valid() const { return _isvalid; } + +private: + int _argc; + char** _argv; + int& _refargc; + char**& _refargv; + bool _isvalid; +#ifdef GHC_OS_WINDOWS + std::vector _args; + std::vector _argp; +#endif +}; + +//------------------------------------------------------------------------------------------------- +// Implementation +//------------------------------------------------------------------------------------------------- + +namespace detail { +// GHC_FS_API void postprocess_path_with_format(path::impl_string_type& p, path::format fmt); +enum utf8_states_t { S_STRT = 0, S_RJCT = 8 }; +GHC_FS_API void appendUTF8(std::string& str, uint32_t unicode); +GHC_FS_API bool is_surrogate(uint32_t c); +GHC_FS_API bool is_high_surrogate(uint32_t c); +GHC_FS_API bool is_low_surrogate(uint32_t c); +GHC_FS_API unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint); +enum class portable_error { + none = 0, + exists, + not_found, + not_supported, + not_implemented, + invalid_argument, + is_a_directory, +}; +GHC_FS_API std::error_code make_error_code(portable_error err); +#ifdef GHC_OS_WINDOWS +GHC_FS_API std::error_code make_system_error(uint32_t err = 0); +#else +GHC_FS_API std::error_code make_system_error(int err = 0); +#endif +} // namespace detail + +namespace detail { + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::error_code make_error_code(portable_error err) +{ +#ifdef GHC_OS_WINDOWS + switch (err) { + case portable_error::none: + return std::error_code(); + case portable_error::exists: + return std::error_code(ERROR_ALREADY_EXISTS, std::system_category()); + case portable_error::not_found: + return std::error_code(ERROR_PATH_NOT_FOUND, std::system_category()); + case portable_error::not_supported: + return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); + case portable_error::not_implemented: + return std::error_code(ERROR_CALL_NOT_IMPLEMENTED, std::system_category()); + case portable_error::invalid_argument: + return std::error_code(ERROR_INVALID_PARAMETER, std::system_category()); + case portable_error::is_a_directory: +#ifdef ERROR_DIRECTORY_NOT_SUPPORTED + return std::error_code(ERROR_DIRECTORY_NOT_SUPPORTED, std::system_category()); +#else + return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); +#endif + } +#else + switch (err) { + case portable_error::none: + return std::error_code(); + case portable_error::exists: + return std::error_code(EEXIST, std::system_category()); + case portable_error::not_found: + return std::error_code(ENOENT, std::system_category()); + case portable_error::not_supported: + return std::error_code(ENOTSUP, std::system_category()); + case portable_error::not_implemented: + return std::error_code(ENOSYS, std::system_category()); + case portable_error::invalid_argument: + return std::error_code(EINVAL, std::system_category()); + case portable_error::is_a_directory: + return std::error_code(EISDIR, std::system_category()); + } +#endif + return std::error_code(); +} + +#ifdef GHC_OS_WINDOWS +GHC_INLINE std::error_code make_system_error(uint32_t err) +{ + return std::error_code(err ? static_cast(err) : static_cast(::GetLastError()), std::system_category()); +} +#else +GHC_INLINE std::error_code make_system_error(int err) +{ + return std::error_code(err ? err : errno, std::system_category()); +} +#endif + +#endif // GHC_EXPAND_IMPL + +template +using EnableBitmask = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, Enum>::type; +} // namespace detail + +template +detail::EnableBitmask operator&(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) & static_cast(Y)); +} + +template +detail::EnableBitmask operator|(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) | static_cast(Y)); +} + +template +detail::EnableBitmask operator^(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) ^ static_cast(Y)); +} + +template +detail::EnableBitmask operator~(Enum X) +{ + using underlying = typename std::underlying_type::type; + return static_cast(~static_cast(X)); +} + +template +detail::EnableBitmask& operator&=(Enum& X, Enum Y) +{ + X = X & Y; + return X; +} + +template +detail::EnableBitmask& operator|=(Enum& X, Enum Y) +{ + X = X | Y; + return X; +} + +template +detail::EnableBitmask& operator^=(Enum& X, Enum Y) +{ + X = X ^ Y; + return X; +} + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +GHC_INLINE bool in_range(uint32_t c, uint32_t lo, uint32_t hi) +{ + return (static_cast(c - lo) < (hi - lo + 1)); +} + +GHC_INLINE bool is_surrogate(uint32_t c) +{ + return in_range(c, 0xd800, 0xdfff); +} + +GHC_INLINE bool is_high_surrogate(uint32_t c) +{ + return (c & 0xfffffc00) == 0xd800; +} + +GHC_INLINE bool is_low_surrogate(uint32_t c) +{ + return (c & 0xfffffc00) == 0xdc00; +} + +GHC_INLINE void appendUTF8(std::string& str, uint32_t unicode) +{ + if (unicode <= 0x7f) { + str.push_back(static_cast(unicode)); + } + else if (unicode >= 0x80 && unicode <= 0x7ff) { + str.push_back(static_cast((unicode >> 6) + 192)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else if ((unicode >= 0x800 && unicode <= 0xd7ff) || (unicode >= 0xe000 && unicode <= 0xffff)) { + str.push_back(static_cast((unicode >> 12) + 224)); + str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else if (unicode >= 0x10000 && unicode <= 0x10ffff) { + str.push_back(static_cast((unicode >> 18) + 240)); + str.push_back(static_cast(((unicode & 0x3ffff) >> 12) + 128)); + str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal code point for unicode character.", str, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + appendUTF8(str, 0xfffd); +#endif + } +} + +// Thanks to Bjoern Hoehrmann (https://bjoern.hoehrmann.de/utf-8/decoder/dfa/) +// and Taylor R Campbell for the ideas to this DFA approach of UTF-8 decoding; +// Generating debugging and shrinking my own DFA from scratch was a day of fun! +GHC_INLINE unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint) +{ + static const uint32_t utf8_state_info[] = { + // encoded states + 0x11111111u, 0x11111111u, 0x77777777u, 0x77777777u, 0x88888888u, 0x88888888u, 0x88888888u, 0x88888888u, 0x22222299u, 0x22222222u, 0x22222222u, 0x22222222u, 0x3333333au, 0x33433333u, 0x9995666bu, 0x99999999u, + 0x88888880u, 0x22818108u, 0x88888881u, 0x88888882u, 0x88888884u, 0x88888887u, 0x88888886u, 0x82218108u, 0x82281108u, 0x88888888u, 0x88888883u, 0x88888885u, 0u, 0u, 0u, 0u, + }; + uint8_t category = fragment < 128 ? 0 : (utf8_state_info[(fragment >> 3) & 0xf] >> ((fragment & 7) << 2)) & 0xf; + codepoint = (state ? (codepoint << 6) | (fragment & 0x3fu) : (0xffu >> category) & fragment); + return state == S_RJCT ? static_cast(S_RJCT) : static_cast((utf8_state_info[category + 16] >> (state << 2)) & 0xf); +} + +GHC_INLINE bool validUtf8(const std::string& utf8String) +{ + std::string::const_iterator iter = utf8String.begin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.end()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_RJCT) { + return false; + } + } + if (utf8_state) { + return false; + } + return true; +} + +} // namespace detail + +#endif + +namespace detail { + +template ::type* = nullptr> +inline StringType fromUtf8(const std::string& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + return StringType(utf8String.begin(), utf8String.end(), alloc); +} + +template ::type* = nullptr> +inline StringType fromUtf8(const std::string& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + StringType result(alloc); + result.reserve(utf8String.length()); + std::string::const_iterator iter = utf8String.begin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.end()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { + if (codepoint <= 0xffff) { + result += static_cast(codepoint); + } + else { + codepoint -= 0x10000; + result += static_cast((codepoint >> 10) + 0xd800); + result += static_cast((codepoint & 0x3ff) + 0xdc00); + } + codepoint = 0; + } + else if (utf8_state == S_RJCT) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); + utf8_state = S_STRT; + codepoint = 0; +#endif + } + } + if (utf8_state) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); +#endif + } + return result; +} + +template ::type* = nullptr> +inline StringType fromUtf8(const std::string& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + StringType result(alloc); + result.reserve(utf8String.length()); + std::string::const_iterator iter = utf8String.begin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.end()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { + result += static_cast(codepoint); + codepoint = 0; + } + else if (utf8_state == S_RJCT) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); + utf8_state = S_STRT; + codepoint = 0; +#endif + } + } + if (utf8_state) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); +#endif + } + return result; +} + +template ::type size = 1> +inline std::string toUtf8(const std::basic_string& unicodeString) +{ + return std::string(unicodeString.begin(), unicodeString.end()); +} + +template ::type size = 2> +inline std::string toUtf8(const std::basic_string& unicodeString) +{ + std::string result; + for (auto iter = unicodeString.begin(); iter != unicodeString.end(); ++iter) { + char32_t c = *iter; + if (is_surrogate(c)) { + ++iter; + if (iter != unicodeString.end() && is_high_surrogate(c) && is_low_surrogate(*iter)) { + appendUTF8(result, (char32_t(c) << 10) + *iter - 0x35fdc00); + } + else { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal code point for unicode character.", result, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + appendUTF8(result, 0xfffd); + if(iter == unicodeString.end()) { + break; + } +#endif + } + } + else { + appendUTF8(result, c); + } + } + return result; +} + +template ::type size = 4> +inline std::string toUtf8(const std::basic_string& unicodeString) +{ + std::string result; + for (auto c : unicodeString) { + appendUTF8(result, static_cast(c)); + } + return result; +} + +template +inline std::string toUtf8(const charT* unicodeString) +{ + return toUtf8(std::basic_string>(unicodeString)); +} + +} // namespace detail + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +GHC_INLINE bool startsWith(const std::string& what, const std::string& with) +{ + return with.length() <= what.length() && equal(with.begin(), with.end(), what.begin()); +} + +GHC_INLINE bool endsWith(const std::string& what, const std::string& with) +{ + return with.length() <= what.length() && what.compare(what.length() - with.length(), with.size(), with); +} + +} // namespace detail + +GHC_INLINE void path::postprocess_path_with_format(path::impl_string_type& p, path::format fmt) +{ +#ifdef GHC_RAISE_UNICODE_ERRORS + if(!detail::validUtf8(p)) { + path t; + t._path = p; + throw filesystem_error("Illegal byte sequence for unicode character.", t, std::make_error_code(std::errc::illegal_byte_sequence)); + } +#endif + switch (fmt) { +#ifndef GHC_OS_WINDOWS + case path::auto_format: + case path::native_format: +#endif + case path::generic_format: + // nothing to do + break; +#ifdef GHC_OS_WINDOWS + case path::auto_format: + case path::native_format: + if (p.length() > 4 && p[2] == '?') { + if (detail::startsWith(p, std::string("\\\\?\\"))) { + // remove Windows long filename marker + p.erase(0, 4); + if (detail::startsWith(p, std::string("UNC\\"))) { + p.erase(0, 2); + p[0] = '\\'; + } + } + else if (detail::startsWith(p, std::string("\\??\\"))) { + p.erase(0, 4); + } + } + for (auto& c : p) { + if (c == '\\') { + c = '/'; + } + } + break; +#endif + } + if (p.length() > 2 && p[0] == '/' && p[1] == '/' && p[2] != '/') { + std::string::iterator new_end = std::unique(p.begin() + 2, p.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == '/'; }); + p.erase(new_end, p.end()); + } + else { + std::string::iterator new_end = std::unique(p.begin(), p.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == '/'; }); + p.erase(new_end, p.end()); + } +} + +#endif // GHC_EXPAND_IMPL + +template +inline path::path(const Source& source, format fmt) + : _path(detail::toUtf8(source)) +{ + postprocess_path_with_format(_path, fmt); +} +template <> +inline path::path(const std::wstring& source, format fmt) +{ + _path = detail::toUtf8(source); + postprocess_path_with_format(_path, fmt); +} +template <> +inline path::path(const std::u16string& source, format fmt) +{ + _path = detail::toUtf8(source); + postprocess_path_with_format(_path, fmt); +} +template <> +inline path::path(const std::u32string& source, format fmt) +{ + _path = detail::toUtf8(source); + postprocess_path_with_format(_path, fmt); +} + +#ifdef __cpp_lib_string_view +template <> +inline path::path(const std::string_view& source, format fmt) +{ + _path = detail::toUtf8(std::string(source)); + postprocess_path_with_format(_path, fmt); +} +#ifdef GHC_USE_WCHAR_T +template <> +inline path::path(const std::wstring_view& source, format fmt) +{ + _path = detail::toUtf8(std::wstring(source).c_str()); + postprocess_path_with_format(_path, fmt); +} +#endif +#endif + +template +inline path u8path(const Source& source) +{ + return path(source); +} +template +inline path u8path(InputIterator first, InputIterator last) +{ + return path(first, last); +} + +template +inline path::path(InputIterator first, InputIterator last, format fmt) + : path(std::basic_string::value_type>(first, last), fmt) +{ + // delegated +} + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +GHC_INLINE bool equals_simple_insensitive(const char* str1, const char* str2) +{ +#ifdef GHC_OS_WINDOWS +#ifdef __GNUC__ + while (::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2++)) { + if (*str1++ == 0) + return true; + } + return false; +#else + return 0 == ::_stricmp(str1, str2); +#endif +#else + return 0 == ::strcasecmp(str1, str2); +#endif +} + +GHC_INLINE int compare_simple_insensitive(const char* str1, size_t len1, const char* str2, size_t len2) +{ + while(len1 > 0 && len2 > 0 && ::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2)) { + --len1; --len2; + ++str1; ++str2; + } + if (len1 && len2) { + return *str1 < *str2 ? -1 : 1; + } + if(len1 == 0 && len2 == 0) { + return 0; + } + return len1 == 0 ? -1 : 1; +} + +GHC_INLINE const char* strerror_adapter(char* gnu, char*) +{ + return gnu; +} + +GHC_INLINE const char* strerror_adapter(int posix, char* buffer) +{ + if(posix) { + return "Error in strerror_r!"; + } + return buffer; +} + +template +GHC_INLINE std::string systemErrorText(ErrorNumber code = 0) +{ +#if defined(GHC_OS_WINDOWS) + LPVOID msgBuf; + DWORD dw = code ? static_cast(code) : ::GetLastError(); + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msgBuf, 0, NULL); + std::string msg = toUtf8(std::wstring((LPWSTR)msgBuf)); + LocalFree(msgBuf); + return msg; +#else + char buffer[512]; + return strerror_adapter(strerror_r(code ? code : errno, buffer, sizeof(buffer)), buffer); +#endif +} + +#ifdef GHC_OS_WINDOWS +using CreateSymbolicLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, DWORD); +using CreateHardLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); + +GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool to_directory, std::error_code& ec) +{ + std::error_code tec; + auto fs = status(target_name, tec); + if ((fs.type() == file_type::directory && !to_directory) || (fs.type() == file_type::regular && to_directory)) { + ec = detail::make_error_code(detail::portable_error::not_supported); + return; + } +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + static CreateSymbolicLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateSymbolicLinkW")); +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic pop +#endif + if (api_call) { + if (api_call(detail::fromUtf8(new_symlink.u8string()).c_str(), detail::fromUtf8(target_name.u8string()).c_str(), to_directory ? 1 : 0) == 0) { + auto result = ::GetLastError(); + if (result == ERROR_PRIVILEGE_NOT_HELD && api_call(detail::fromUtf8(new_symlink.u8string()).c_str(), detail::fromUtf8(target_name.u8string()).c_str(), to_directory ? 3 : 2) != 0) { + return; + } + ec = detail::make_system_error(result); + } + } + else { + ec = detail::make_system_error(ERROR_NOT_SUPPORTED); + } +} + +GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) +{ +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + static CreateHardLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW")); +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic pop +#endif + if (api_call) { + if (api_call(detail::fromUtf8(new_hardlink.u8string()).c_str(), detail::fromUtf8(target_name.u8string()).c_str(), NULL) == 0) { + ec = detail::make_system_error(); + } + } + else { + ec = detail::make_system_error(ERROR_NOT_SUPPORTED); + } +} +#else +GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool, std::error_code& ec) +{ + if (::symlink(target_name.c_str(), new_symlink.c_str()) != 0) { + ec = detail::make_system_error(); + } +} + +#ifndef GHC_OS_WEB +GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) +{ + if (::link(target_name.c_str(), new_hardlink.c_str()) != 0) { + ec = detail::make_system_error(); + } +} +#endif +#endif + +template +GHC_INLINE file_status file_status_from_st_mode(T mode) +{ +#ifdef GHC_OS_WINDOWS + file_type ft = file_type::unknown; + if ((mode & _S_IFDIR) == _S_IFDIR) { + ft = file_type::directory; + } + else if ((mode & _S_IFREG) == _S_IFREG) { + ft = file_type::regular; + } + else if ((mode & _S_IFCHR) == _S_IFCHR) { + ft = file_type::character; + } + perms prms = static_cast(mode & 0xfff); + return file_status(ft, prms); +#else + file_type ft = file_type::unknown; + if (S_ISDIR(mode)) { + ft = file_type::directory; + } + else if (S_ISREG(mode)) { + ft = file_type::regular; + } + else if (S_ISCHR(mode)) { + ft = file_type::character; + } + else if (S_ISBLK(mode)) { + ft = file_type::block; + } + else if (S_ISFIFO(mode)) { + ft = file_type::fifo; + } + else if (S_ISLNK(mode)) { + ft = file_type::symlink; + } + else if (S_ISSOCK(mode)) { + ft = file_type::socket; + } + perms prms = static_cast(mode & 0xfff); + return file_status(ft, prms); +#endif +} + +GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec) +{ +#ifdef GHC_OS_WINDOWS +#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE + typedef struct _REPARSE_DATA_BUFFER + { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union + { + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct + { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + } DUMMYUNIONNAME; + } REPARSE_DATA_BUFFER; +#ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE +#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE (16 * 1024) +#endif +#endif + + std::shared_ptr file(CreateFileW(p.wstring().c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); + if (file.get() == INVALID_HANDLE_VALUE) { + ec = detail::make_system_error(); + return path(); + } + + std::shared_ptr reparseData((REPARSE_DATA_BUFFER*)std::calloc(1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE), std::free); + ULONG bufferUsed; + path result; + if (DeviceIoControl(file.get(), FSCTL_GET_REPARSE_POINT, 0, 0, reparseData.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bufferUsed, 0)) { + if (IsReparseTagMicrosoft(reparseData->ReparseTag)) { + switch (reparseData->ReparseTag) { + case IO_REPARSE_TAG_SYMLINK: + result = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + if (reparseData->SymbolicLinkReparseBuffer.Flags & 0x1 /*SYMLINK_FLAG_RELATIVE*/) { + result = p.parent_path() / result; + } + break; + case IO_REPARSE_TAG_MOUNT_POINT: + result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + break; + default: + break; + } + } + } + else { + ec = detail::make_system_error(); + } + return result; +#else + size_t bufferSize = 256; + while (true) { + std::vector buffer(bufferSize, static_cast(0)); + auto rc = ::readlink(p.c_str(), buffer.data(), buffer.size()); + if (rc < 0) { + ec = detail::make_system_error(); + return path(); + } + else if (rc < static_cast(bufferSize)) { + return path(std::string(buffer.data(), static_cast(rc))); + } + bufferSize *= 2; + } + return path(); +#endif +} + +#ifdef GHC_OS_WINDOWS +GHC_INLINE time_t timeFromFILETIME(const FILETIME& ft) +{ + ULARGE_INTEGER ull; + ull.LowPart = ft.dwLowDateTime; + ull.HighPart = ft.dwHighDateTime; + return static_cast(ull.QuadPart / 10000000ULL - 11644473600ULL); +} + +GHC_INLINE void timeToFILETIME(time_t t, FILETIME& ft) +{ + LONGLONG ll; + ll = Int32x32To64(t, 10000000) + 116444736000000000; + ft.dwLowDateTime = static_cast(ll); + ft.dwHighDateTime = static_cast(ll >> 32); +} + +template +GHC_INLINE uintmax_t hard_links_from_INFO(const INFO* info) +{ + return static_cast(-1); +} + +template <> +GHC_INLINE uintmax_t hard_links_from_INFO(const BY_HANDLE_FILE_INFORMATION* info) +{ + return info->nNumberOfLinks; +} + +template +GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code&, uintmax_t* sz = nullptr, time_t* lwt = nullptr) +{ + file_type ft = file_type::unknown; + if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { + ft = file_type::symlink; + } + else { + if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + ft = file_type::directory; + } + else { + ft = file_type::regular; + } + } + perms prms = perms::owner_read | perms::group_read | perms::others_read; + if (!(info->dwFileAttributes & FILE_ATTRIBUTE_READONLY)) { + prms = prms | perms::owner_write | perms::group_write | perms::others_write; + } + if (has_executable_extension(p)) { + prms = prms | perms::owner_exec | perms::group_exec | perms::others_exec; + } + if (sz) { + *sz = static_cast(info->nFileSizeHigh) << (sizeof(info->nFileSizeHigh) * 8) | info->nFileSizeLow; + } + if (lwt) { + *lwt = detail::timeFromFILETIME(info->ftLastWriteTime); + } + return file_status(ft, prms); +} + +#endif + +GHC_INLINE bool is_not_found_error(std::error_code& ec) +{ +#ifdef GHC_OS_WINDOWS + return ec.value() == ERROR_FILE_NOT_FOUND || ec.value() == ERROR_PATH_NOT_FOUND || ec.value() == ERROR_INVALID_NAME; +#else + return ec.value() == ENOENT || ec.value() == ENOTDIR; +#endif +} + +GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr) noexcept +{ +#ifdef GHC_OS_WINDOWS + file_status fs; + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!GetFileAttributesExW(detail::fromUtf8(p.u8string()).c_str(), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + } + else { + ec.clear(); + fs = detail::status_from_INFO(p, &attr, ec, sz, lwt); + if (nhl) { + *nhl = 0; + } + if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + fs.type(file_type::symlink); + } + } + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found); + } + return ec ? file_status(file_type::none) : fs; +#else + (void)sz; + (void)nhl; + (void)lwt; + struct ::stat fs; + auto result = ::lstat(p.c_str(), &fs); + if (result == 0) { + ec.clear(); + file_status f_s = detail::file_status_from_st_mode(fs.st_mode); + return f_s; + } + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); +#endif +} + +GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status* sls = nullptr, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr, int recurse_count = 0) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (recurse_count > 16) { + ec = detail::make_system_error(0x2A9 /*ERROR_STOPPED_ON_SYMLINK*/); + return file_status(file_type::unknown); + } + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!::GetFileAttributesExW(p.wstring().c_str(), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + } + else if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + path target = resolveSymlink(p, ec); + file_status result; + if (!ec && !target.empty()) { + if (sls) { + *sls = status_from_INFO(p, &attr, ec); + } + return detail::status_ex(target, ec, nullptr, sz, nhl, lwt, recurse_count + 1); + } + return file_status(file_type::unknown); + } + if (ec) { + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found); + } + return file_status(file_type::none); + } + if (nhl) { + *nhl = 0; + } + return detail::status_from_INFO(p, &attr, ec, sz, lwt); +#else + (void)recurse_count; + struct ::stat st; + auto result = ::lstat(p.c_str(), &st); + if (result == 0) { + ec.clear(); + file_status fs = detail::file_status_from_st_mode(st.st_mode); + if (fs.type() == file_type::symlink) { + result = ::stat(p.c_str(), &st); + if (result == 0) { + if (sls) { + *sls = fs; + } + fs = detail::file_status_from_st_mode(st.st_mode); + } + } + if (sz) { + *sz = static_cast(st.st_size); + } + if (nhl) { + *nhl = st.st_nlink; + } + if (lwt) { + *lwt = st.st_mtime; + } + return fs; + } + else { + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); + } +#endif +} + +} // namespace detail + +GHC_INLINE u8arguments::u8arguments(int& argc, char**& argv) + : _argc(argc) + , _argv(argv) + , _refargc(argc) + , _refargv(argv) + , _isvalid(false) +{ +#ifdef GHC_OS_WINDOWS + LPWSTR* p; + p = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + _args.reserve(static_cast(argc)); + _argp.reserve(static_cast(argc)); + for (size_t i = 0; i < static_cast(argc); ++i) { + _args.push_back(detail::toUtf8(std::wstring(p[i]))); + _argp.push_back((char*)_args[i].data()); + } + argv = _argp.data(); + ::LocalFree(p); + _isvalid = true; +#else + std::setlocale(LC_ALL, ""); +#if defined(__ANDROID__) && __ANDROID_API__ < 26 + _isvalid = true; +#else + if (detail::equals_simple_insensitive(::nl_langinfo(CODESET), "UTF-8")) { + _isvalid = true; + } +#endif +#endif +} + +//----------------------------------------------------------------------------- +// 30.10.8.4.1 constructors and destructor + +GHC_INLINE path::path() noexcept {} + +GHC_INLINE path::path(const path& p) + : _path(p._path) +{ +} + +GHC_INLINE path::path(path&& p) noexcept + : _path(std::move(p._path)) +{ +} + +GHC_INLINE path::path(string_type&& source, format fmt) +#ifdef GHC_USE_WCHAR_T + : _path(detail::toUtf8(source)) +#else + : _path(std::move(source)) +#endif +{ + postprocess_path_with_format(_path, fmt); +} + +#endif // GHC_EXPAND_IMPL + +#ifdef GHC_WITH_EXCEPTIONS +template +inline path::path(const Source& source, const std::locale& loc, format fmt) + : path(source, fmt) +{ + std::string locName = loc.name(); + if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { + throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); + } +} + +template +inline path::path(InputIterator first, InputIterator last, const std::locale& loc, format fmt) + : path(std::basic_string::value_type>(first, last), fmt) +{ + std::string locName = loc.name(); + if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { + throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); + } +} +#endif + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE path::~path() {} + +//----------------------------------------------------------------------------- +// 30.10.8.4.2 assignments + +GHC_INLINE path& path::operator=(const path& p) +{ + _path = p._path; + return *this; +} + +GHC_INLINE path& path::operator=(path&& p) noexcept +{ + _path = std::move(p._path); + return *this; +} + +GHC_INLINE path& path::operator=(path::string_type&& source) +{ + return assign(source); +} + +GHC_INLINE path& path::assign(path::string_type&& source) +{ +#ifdef GHC_USE_WCHAR_T + _path = detail::toUtf8(source); +#else + _path = std::move(source); +#endif + postprocess_path_with_format(_path, native_format); + return *this; +} + +#endif // GHC_EXPAND_IMPL + +template +inline path& path::operator=(const Source& source) +{ + return assign(source); +} + +template +inline path& path::assign(const Source& source) +{ + _path.assign(detail::toUtf8(source)); + postprocess_path_with_format(_path, native_format); + return *this; +} + +template <> +inline path& path::assign(const path& source) +{ + _path = source._path; + return *this; +} + +template +inline path& path::assign(InputIterator first, InputIterator last) +{ + _path.assign(first, last); + postprocess_path_with_format(_path, native_format); + return *this; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// 30.10.8.4.3 appends + +GHC_INLINE path& path::operator/=(const path& p) +{ + if (p.empty()) { + // was: if ((!has_root_directory() && is_absolute()) || has_filename()) + if (!_path.empty() && _path[_path.length() - 1] != '/' && _path[_path.length() - 1] != ':') { + _path += '/'; + } + return *this; + } + if ((p.is_absolute() && (_path != root_name() || p._path != "/")) || (p.has_root_name() && p.root_name() != root_name())) { + assign(p); + return *this; + } + if (p.has_root_directory()) { + assign(root_name()); + } + else if ((!has_root_directory() && is_absolute()) || has_filename()) { + _path += '/'; + } + auto iter = p.begin(); + bool first = true; + if (p.has_root_name()) { + ++iter; + } + while (iter != p.end()) { + if (!first && !(!_path.empty() && _path[_path.length() - 1] == '/')) { + _path += '/'; + } + first = false; + _path += (*iter++).generic_string(); + } + return *this; +} + +GHC_INLINE void path::append_name(const char* name) +{ + if (_path.empty()) { + this->operator/=(path(name)); + } + else { + if (_path.back() != path::generic_separator) { + _path.push_back(path::generic_separator); + } + _path += name; + } +} + +#endif // GHC_EXPAND_IMPL + +template +inline path& path::operator/=(const Source& source) +{ + return append(source); +} + +template +inline path& path::append(const Source& source) +{ + return this->operator/=(path(detail::toUtf8(source))); +} + +template <> +inline path& path::append(const path& p) +{ + return this->operator/=(p); +} + +template +inline path& path::append(InputIterator first, InputIterator last) +{ + std::basic_string::value_type> part(first, last); + return append(part); +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// 30.10.8.4.4 concatenation + +GHC_INLINE path& path::operator+=(const path& x) +{ + return concat(x._path); +} + +GHC_INLINE path& path::operator+=(const string_type& x) +{ + return concat(x); +} + +#ifdef __cpp_lib_string_view +GHC_INLINE path& path::operator+=(std::basic_string_view x) +{ + return concat(x); +} +#endif + +GHC_INLINE path& path::operator+=(const value_type* x) +{ + return concat(string_type(x)); +} + +GHC_INLINE path& path::operator+=(value_type x) +{ +#ifdef GHC_OS_WINDOWS + if (x == '\\') { + x = generic_separator; + } +#endif + if (_path.empty() || _path.back() != generic_separator) { +#ifdef GHC_USE_WCHAR_T + _path += detail::toUtf8(string_type(1, x)); +#else + _path += x; +#endif + } + return *this; +} + +#endif // GHC_EXPAND_IMPL + +template +inline path::path_from_string& path::operator+=(const Source& x) +{ + return concat(x); +} + +template +inline path::path_type_EcharT& path::operator+=(EcharT x) +{ + std::basic_string part(1, x); + concat(detail::toUtf8(part)); + return *this; +} + +template +inline path& path::concat(const Source& x) +{ + path p(x); + postprocess_path_with_format(p._path, native_format); + _path += p._path; + return *this; +} +template +inline path& path::concat(InputIterator first, InputIterator last) +{ + _path.append(first, last); + postprocess_path_with_format(_path, native_format); + return *this; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// 30.10.8.4.5 modifiers +GHC_INLINE void path::clear() noexcept +{ + _path.clear(); +} + +GHC_INLINE path& path::make_preferred() +{ + // as this filesystem implementation only uses generic_format + // internally, this must be a no-op + return *this; +} + +GHC_INLINE path& path::remove_filename() +{ + if (has_filename()) { + _path.erase(_path.size() - filename()._path.size()); + } + return *this; +} + +GHC_INLINE path& path::replace_filename(const path& replacement) +{ + remove_filename(); + return append(replacement); +} + +GHC_INLINE path& path::replace_extension(const path& replacement) +{ + if (has_extension()) { + _path.erase(_path.size() - extension()._path.size()); + } + if (!replacement.empty() && replacement._path[0] != '.') { + _path += '.'; + } + return concat(replacement); +} + +GHC_INLINE void path::swap(path& rhs) noexcept +{ + _path.swap(rhs._path); +} + +//----------------------------------------------------------------------------- +// 30.10.8.4.6, native format observers +#ifdef GHC_OS_WINDOWS +GHC_INLINE path::impl_string_type path::native_impl() const +{ + impl_string_type result; + if (is_absolute() && _path.length() > MAX_PATH - 10) { + // expand long Windows filenames with marker + if (has_root_name() && _path[0] == '/') { + result = "\\\\?\\UNC" + _path.substr(1); + } + else { + result = "\\\\?\\" + _path; + } + } + else { + result = _path; + } + /*if (has_root_name() && root_name()._path[0] == '/') { + return _path; + }*/ + for (auto& c : result) { + if (c == '/') { + c = '\\'; + } + } + return result; +} +#else +GHC_INLINE const path::impl_string_type& path::native_impl() const +{ + return _path; +} +#endif + +GHC_INLINE const path::string_type& path::native() const +{ +#ifdef GHC_OS_WINDOWS +#ifdef GHC_USE_WCHAR_T + _native_cache = detail::fromUtf8(native_impl()); +#else + _native_cache = native_impl(); +#endif + return _native_cache; +#else + return _path; +#endif +} + +GHC_INLINE const path::value_type* path::c_str() const +{ + return native().c_str(); +} + +GHC_INLINE path::operator path::string_type() const +{ + return native(); +} + +#endif // GHC_EXPAND_IMPL + +template +inline std::basic_string path::string(const Allocator& a) const +{ + return detail::fromUtf8>(native_impl(), a); +} + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::string path::string() const +{ + return native_impl(); +} + +GHC_INLINE std::wstring path::wstring() const +{ +#ifdef GHC_USE_WCHAR_T + return native(); +#else + return detail::fromUtf8(native()); +#endif +} + +GHC_INLINE std::string path::u8string() const +{ + return native_impl(); +} + +GHC_INLINE std::u16string path::u16string() const +{ + return detail::fromUtf8(native_impl()); +} + +GHC_INLINE std::u32string path::u32string() const +{ + return detail::fromUtf8(native_impl()); +} + +#endif // GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// 30.10.8.4.7, generic format observers +template +inline std::basic_string path::generic_string(const Allocator& a) const +{ + return detail::fromUtf8>(_path, a); +} + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE const std::string& path::generic_string() const +{ + return _path; +} + +GHC_INLINE std::wstring path::generic_wstring() const +{ + return detail::fromUtf8(_path); +} + +GHC_INLINE std::string path::generic_u8string() const +{ + return _path; +} + +GHC_INLINE std::u16string path::generic_u16string() const +{ + return detail::fromUtf8(_path); +} + +GHC_INLINE std::u32string path::generic_u32string() const +{ + return detail::fromUtf8(_path); +} + +//----------------------------------------------------------------------------- +// 30.10.8.4.8, compare +GHC_INLINE int path::compare(const path& p) const noexcept +{ +#ifdef LWG_2936_BEHAVIOUR + auto rnl1 = root_name_length(); + auto rnl2 = p.root_name_length(); +#ifdef GHC_OS_WINDOWS + auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); +#else + auto rnc = _path.compare(0, rnl1, p._path, 0, (std::min(rnl1, rnl2))); +#endif + if (rnc) { + return rnc; + } + bool hrd1 = has_root_directory(), hrd2 = p.has_root_directory(); + if (hrd1 != hrd2) { + return hrd1 ? 1 : -1; + } + if (hrd1) { + ++rnl1; + ++rnl2; + } + auto iter1 = _path.begin() + static_cast(rnl1); + auto iter2 = p._path.begin() + static_cast(rnl2); + while (iter1 != _path.end() && iter2 != p._path.end() && *iter1 == *iter2) { + ++iter1; + ++iter2; + } + if (iter1 == _path.end()) { + return iter2 == p._path.end() ? 0 : -1; + } + if (iter2 == p._path.end()) { + return 1; + } + if (*iter1 == '/') { + return -1; + } + if (*iter2 == '/') { + return 1; + } + return *iter1 < *iter2 ? -1 : 1; +#else // LWG_2936_BEHAVIOUR +#ifdef GHC_OS_WINDOWS + auto rnl1 = root_name_length(); + auto rnl2 = p.root_name_length(); + auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); + if(rnc) { + return rnc; + } + auto p1 = _path; + std::replace(p1.begin()+static_cast(rnl1), p1.end(), '/', '\\'); + auto p2 = p._path; + std::replace(p2.begin()+static_cast(rnl2), p2.end(), '/', '\\'); + return p1.compare(rnl1, std::string::npos, p2, rnl2, std::string::npos); +#else + return _path.compare(p._path); +#endif +#endif +} + +GHC_INLINE int path::compare(const string_type& s) const +{ + return compare(path(s)); +} + +#ifdef __cpp_lib_string_view +GHC_INLINE int path::compare(std::basic_string_view s) const +{ + return compare(path(s)); +} +#endif + +GHC_INLINE int path::compare(const value_type* s) const +{ + return compare(path(s)); +} + +//----------------------------------------------------------------------------- +// 30.10.8.4.9, decomposition +GHC_INLINE path::string_type::size_type path::root_name_length() const noexcept +{ +#ifdef GHC_OS_WINDOWS + if (_path.length() >= 2 && std::toupper(static_cast(_path[0])) >= 'A' && std::toupper(static_cast(_path[0])) <= 'Z' && _path[1] == ':') { + return 2; + } +#endif + if (_path.length() > 2 && _path[0] == '/' && _path[1] == '/' && _path[2] != '/' && std::isprint(_path[2])) { + impl_string_type::size_type pos = _path.find_first_of("/\\", 3); + if (pos == impl_string_type::npos) { + return _path.length(); + } + else { + return pos; + } + } + return 0; +} + +GHC_INLINE path path::root_name() const +{ + return path(_path.substr(0, root_name_length()), generic_format); +} + +GHC_INLINE path path::root_directory() const +{ + if(has_root_directory()) { + return path("/", generic_format); + } + return path(); +} + +GHC_INLINE path path::root_path() const +{ + return path(root_name().generic_string() + root_directory().generic_string(), generic_format); +} + +GHC_INLINE path path::relative_path() const +{ + auto rootPathLen = root_name_length() + (has_root_directory() ? 1 : 0); + return path(_path.substr((std::min)(rootPathLen, _path.length())), generic_format); +} + +GHC_INLINE path path::parent_path() const +{ + if (has_relative_path()) { + if (empty() || begin() == --end()) { + return path(); + } + else { + path pp; + for (string_type s : input_iterator_range(begin(), --end())) { + if (s == "/") { + // don't use append to join a path- + pp += s; + } + else { + pp /= s; + } + } + return pp; + } + } + else { + return *this; + } +} + +GHC_INLINE path path::filename() const +{ + return relative_path().empty() ? path() : path(*--end()); +} + +GHC_INLINE path path::stem() const +{ + impl_string_type fn = filename().string(); + if (fn != "." && fn != "..") { + impl_string_type::size_type pos = fn.rfind('.'); + if (pos != impl_string_type::npos && pos > 0) { + return path{fn.substr(0, pos), generic_format}; + } + } + return path{fn, generic_format}; +} + +GHC_INLINE path path::extension() const +{ + if (has_relative_path()) { + auto iter = end(); + const auto& fn = *--iter; + impl_string_type::size_type pos = fn._path.rfind('.'); + if (pos != std::string::npos && pos > 0) { + return path(fn._path.substr(pos), generic_format); + } + } + return path(); +} + +#ifdef GHC_OS_WINDOWS +namespace detail { +GHC_INLINE bool has_executable_extension(const path& p) +{ + if (p.has_relative_path()) { + auto iter = p.end(); + const auto& fn = *--iter; + auto pos = fn._path.find_last_of('.'); + if (pos == std::string::npos || pos == 0 || fn._path.length() - pos != 3) { + return false; + } + const char* ext = fn._path.c_str() + pos + 1; + if (detail::equals_simple_insensitive(ext, "exe") || detail::equals_simple_insensitive(ext, "cmd") || detail::equals_simple_insensitive(ext, "bat") || detail::equals_simple_insensitive(ext, "com")) { + return true; + } + } + return false; +} +} // namespace detail +#endif + +//----------------------------------------------------------------------------- +// 30.10.8.4.10, query +GHC_INLINE bool path::empty() const noexcept +{ + return _path.empty(); +} + +GHC_INLINE bool path::has_root_name() const +{ + return root_name_length() > 0; +} + +GHC_INLINE bool path::has_root_directory() const +{ + auto rootLen = root_name_length(); + return (_path.length() > rootLen && _path[rootLen] == '/'); +} + +GHC_INLINE bool path::has_root_path() const +{ + return has_root_name() || has_root_directory(); +} + +GHC_INLINE bool path::has_relative_path() const +{ + auto rootPathLen = root_name_length() + (has_root_directory() ? 1 : 0); + return rootPathLen < _path.length(); +} + +GHC_INLINE bool path::has_parent_path() const +{ + return !parent_path().empty(); +} + +GHC_INLINE bool path::has_filename() const +{ + return has_relative_path() && !filename().empty(); +} + +GHC_INLINE bool path::has_stem() const +{ + return !stem().empty(); +} + +GHC_INLINE bool path::has_extension() const +{ + return !extension().empty(); +} + +GHC_INLINE bool path::is_absolute() const +{ +#ifdef GHC_OS_WINDOWS + return has_root_name() && has_root_directory(); +#else + return has_root_directory(); +#endif +} + +GHC_INLINE bool path::is_relative() const +{ + return !is_absolute(); +} + +//----------------------------------------------------------------------------- +// 30.10.8.4.11, generation +GHC_INLINE path path::lexically_normal() const +{ + path dest; + bool lastDotDot = false; + for (string_type s : *this) { + if (s == ".") { + dest /= ""; + continue; + } + else if (s == ".." && !dest.empty()) { + auto root = root_path(); + if (dest == root) { + continue; + } + else if (*(--dest.end()) != "..") { + if (dest._path.back() == generic_separator) { + dest._path.pop_back(); + } + dest.remove_filename(); + continue; + } + } + if (!(s.empty() && lastDotDot)) { + dest /= s; + } + lastDotDot = s == ".."; + } + if (dest.empty()) { + dest = "."; + } + return dest; +} + +GHC_INLINE path path::lexically_relative(const path& base) const +{ + if (root_name() != base.root_name() || is_absolute() != base.is_absolute() || (!has_root_directory() && base.has_root_directory())) { + return path(); + } + const_iterator a = begin(), b = base.begin(); + while (a != end() && b != base.end() && *a == *b) { + ++a; + ++b; + } + if (a == end() && b == base.end()) { + return path("."); + } + int count = 0; + for (const auto& element : input_iterator_range(b, base.end())) { + if (element != "." && element != "" && element != "..") { + ++count; + } + else if (element == "..") { + --count; + } + } + if (count < 0) { + return path(); + } + path result; + for (int i = 0; i < count; ++i) { + result /= ".."; + } + for (const auto& element : input_iterator_range(a, end())) { + result /= element; + } + return result; +} + +GHC_INLINE path path::lexically_proximate(const path& base) const +{ + path result = lexically_relative(base); + return result.empty() ? *this : result; +} + +//----------------------------------------------------------------------------- +// 30.10.8.5, iterators +GHC_INLINE path::iterator::iterator() {} + +GHC_INLINE path::iterator::iterator(const path::impl_string_type::const_iterator& first, const path::impl_string_type::const_iterator& last, const path::impl_string_type::const_iterator& pos) + : _first(first) + , _last(last) + , _iter(pos) +{ + updateCurrent(); + // find the position of a potential root directory slash +#ifdef GHC_OS_WINDOWS + if (_last - _first >= 3 && std::toupper(static_cast(*first)) >= 'A' && std::toupper(static_cast(*first)) <= 'Z' && *(first + 1) == ':' && *(first + 2) == '/') { + _root = _first + 2; + } + else +#endif + { + if (_first != _last && *_first == '/') { + if (_last - _first >= 2 && *(_first + 1) == '/' && !(_last - _first >= 3 && *(_first + 2) == '/')) { + _root = increment(_first); + } + else { + _root = _first; + } + } + else { + _root = _last; + } + } +} + +GHC_INLINE path::impl_string_type::const_iterator path::iterator::increment(const path::impl_string_type::const_iterator& pos) const +{ + path::impl_string_type::const_iterator i = pos; + bool fromStart = i == _first; + if (i != _last) { + // we can only sit on a slash if it is a network name or a root + if (*i++ == '/') { + if (i != _last && *i == '/') { + if (fromStart && !(i + 1 != _last && *(i + 1) == '/')) { + // leadind double slashes detected, treat this and the + // following until a slash as one unit + i = std::find(++i, _last, '/'); + } + else { + // skip redundant slashes + while (i != _last && *i == '/') { + ++i; + } + } + } + } + else { + if (fromStart && i != _last && *i == ':') { + ++i; + } + else { + i = std::find(i, _last, '/'); + } + } + } + return i; +} + +GHC_INLINE path::impl_string_type::const_iterator path::iterator::decrement(const path::impl_string_type::const_iterator& pos) const +{ + path::impl_string_type::const_iterator i = pos; + if (i != _first) { + --i; + // if this is now the root slash or the trailing slash, we are done, + // else check for network name + if (i != _root && (pos != _last || *i != '/')) { +#ifdef GHC_OS_WINDOWS + static const std::string seps = "/:"; + i = std::find_first_of(std::reverse_iterator(i), std::reverse_iterator(_first), seps.begin(), seps.end()).base(); + if (i > _first && *i == ':') { + i++; + } +#else + i = std::find(std::reverse_iterator(i), std::reverse_iterator(_first), '/').base(); +#endif + // Now we have to check if this is a network name + if (i - _first == 2 && *_first == '/' && *(_first + 1) == '/') { + i -= 2; + } + } + } + return i; +} + +GHC_INLINE void path::iterator::updateCurrent() +{ + if (_iter != _first && _iter != _last && (*_iter == '/' && _iter != _root) && (_iter + 1 == _last)) { + _current = ""; + } + else { + _current.assign(_iter, increment(_iter)); + if (_current.generic_string().size() > 1 && _current.generic_string()[0] == '/' && _current.generic_string()[_current.generic_string().size() - 1] == '/') { + // shrink successive slashes to one + _current = "/"; + } + } +} + +GHC_INLINE path::iterator& path::iterator::operator++() +{ + _iter = increment(_iter); + while (_iter != _last && // we didn't reach the end + _iter != _root && // this is not a root position + *_iter == '/' && // we are on a slash + (_iter + 1) != _last // the slash is not the last char + ) { + ++_iter; + } + updateCurrent(); + return *this; +} + +GHC_INLINE path::iterator path::iterator::operator++(int) +{ + path::iterator i{*this}; + ++(*this); + return i; +} + +GHC_INLINE path::iterator& path::iterator::operator--() +{ + _iter = decrement(_iter); + updateCurrent(); + return *this; +} + +GHC_INLINE path::iterator path::iterator::operator--(int) +{ + auto i = *this; + --(*this); + return i; +} + +GHC_INLINE bool path::iterator::operator==(const path::iterator& other) const +{ + return _iter == other._iter; +} + +GHC_INLINE bool path::iterator::operator!=(const path::iterator& other) const +{ + return _iter != other._iter; +} + +GHC_INLINE path::iterator::reference path::iterator::operator*() const +{ + return _current; +} + +GHC_INLINE path::iterator::pointer path::iterator::operator->() const +{ + return &_current; +} + +GHC_INLINE path::iterator path::begin() const +{ + return iterator(_path.begin(), _path.end(), _path.begin()); +} + +GHC_INLINE path::iterator path::end() const +{ + return iterator(_path.begin(), _path.end(), _path.end()); +} + +//----------------------------------------------------------------------------- +// 30.10.8.6, path non-member functions +GHC_INLINE void swap(path& lhs, path& rhs) noexcept +{ + swap(lhs._path, rhs._path); +} + +GHC_INLINE size_t hash_value(const path& p) noexcept +{ + return std::hash()(p.generic_string()); +} + +GHC_INLINE bool operator==(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) == 0; +} + +GHC_INLINE bool operator!=(const path& lhs, const path& rhs) noexcept +{ + return !(lhs == rhs); +} + +GHC_INLINE bool operator<(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) < 0; +} + +GHC_INLINE bool operator<=(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) <= 0; +} + +GHC_INLINE bool operator>(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) > 0; +} + +GHC_INLINE bool operator>=(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) >= 0; +} + +GHC_INLINE path operator/(const path& lhs, const path& rhs) +{ + path result(lhs); + result /= rhs; + return result; +} + +#endif // GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// 30.10.8.6.1 path inserter and extractor +template +inline std::basic_ostream& operator<<(std::basic_ostream& os, const path& p) +{ + os << "\""; + auto ps = p.string(); + for (auto c : ps) { + if (c == '"' || c == '\\') { + os << '\\'; + } + os << c; + } + os << "\""; + return os; +} + +template +inline std::basic_istream& operator>>(std::basic_istream& is, path& p) +{ + std::basic_string tmp; + charT c; + is >> c; + if (c == '"') { + auto sf = is.flags(); + is >> std::noskipws; + while (is) { + auto c2 = is.get(); + if (is) { + if (c2 == '\\') { + c2 = is.get(); + if (is) { + tmp += static_cast(c2); + } + } + else if (c2 == '"') { + break; + } + else { + tmp += static_cast(c2); + } + } + } + if ((sf & std::ios_base::skipws) == std::ios_base::skipws) { + is >> std::skipws; + } + p = path(tmp); + } + else { + is >> tmp; + p = path(static_cast(c) + tmp); + } + return is; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// 30.10.9 Class filesystem_error +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) +{ +} + +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) + , _p1(p1) +{ + if (!_p1.empty()) { + _what_arg += ": '" + _p1.u8string() + "'"; + } +} + +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) + , _p1(p1) + , _p2(p2) +{ + if (!_p1.empty()) { + _what_arg += ": '" + _p1.u8string() + "'"; + } + if (!_p2.empty()) { + _what_arg += ", '" + _p2.u8string() + "'"; + } +} + +GHC_INLINE const path& filesystem_error::path1() const noexcept +{ + return _p1; +} + +GHC_INLINE const path& filesystem_error::path2() const noexcept +{ + return _p2; +} + +GHC_INLINE const char* filesystem_error::what() const noexcept +{ + return _what_arg.c_str(); +} + +//----------------------------------------------------------------------------- +// 30.10.15, filesystem operations +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path absolute(const path& p) +{ + std::error_code ec; + path result = absolute(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path absolute(const path& p, std::error_code& ec) +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (p.empty()) { + return absolute(current_path(ec), ec) / ""; + } + ULONG size = ::GetFullPathNameW(p.wstring().c_str(), 0, 0, 0); + if (size) { + std::vector buf(size, 0); + ULONG s2 = GetFullPathNameW(p.wstring().c_str(), size, buf.data(), nullptr); + if (s2 && s2 < size) { + path result = path(std::wstring(buf.data(), s2)); + if (p.filename() == ".") { + result /= "."; + } + return result; + } + } + ec = detail::make_system_error(); + return path(); +#else + path base = current_path(ec); + if (!ec) { + if (p.empty()) { + return base / p; + } + if (p.has_root_name()) { + if (p.has_root_directory()) { + return p; + } + else { + return p.root_name() / base.root_directory() / base.relative_path() / p.relative_path(); + } + } + else { + if (p.has_root_directory()) { + return base.root_name() / p; + } + else { + return base / p; + } + } + } + ec = detail::make_system_error(); + return path(); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path canonical(const path& p) +{ + std::error_code ec; + auto result = canonical(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path canonical(const path& p, std::error_code& ec) +{ + if (p.empty()) { + ec = detail::make_error_code(detail::portable_error::not_found); + return path(); + } + path work = p.is_absolute() ? p : absolute(p, ec); + path root = work.root_path(); + path result; + + auto fs = status(work, ec); + if (ec) { + return path(); + } + if (fs.type() == file_type::not_found) { + ec = detail::make_error_code(detail::portable_error::not_found); + return path(); + } + bool redo; + do { + redo = false; + result.clear(); + for (auto pe : work) { + if (pe.empty() || pe == ".") { + continue; + } + else if (pe == "..") { + result = result.parent_path(); + continue; + } + else if ((result / pe).string().length() <= root.string().length()) { + result /= pe; + continue; + } + auto sls = symlink_status(result / pe, ec); + if (ec) { + return path(); + } + if (is_symlink(sls)) { + redo = true; + auto target = read_symlink(result / pe, ec); + if (ec) { + return path(); + } + if (target.is_absolute()) { + result = target; + continue; + } + else { + result /= target; + continue; + } + } + else { + result /= pe; + } + } + work = result; + } while (redo); + ec.clear(); + return result; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void copy(const path& from, const path& to) +{ + copy(from, to, copy_options::none); +} +#endif + +GHC_INLINE void copy(const path& from, const path& to, std::error_code& ec) noexcept +{ + copy(from, to, copy_options::none, ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void copy(const path& from, const path& to, copy_options options) +{ + std::error_code ec; + copy(from, to, options, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } +} +#endif + +GHC_INLINE void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept +{ + std::error_code tec; + file_status fs_from, fs_to; + ec.clear(); + if ((options & (copy_options::skip_symlinks | copy_options::copy_symlinks | copy_options::create_symlinks)) != copy_options::none) { + fs_from = symlink_status(from, ec); + } + else { + fs_from = status(from, ec); + } + if (!exists(fs_from)) { + if (!ec) { + ec = detail::make_error_code(detail::portable_error::not_found); + } + return; + } + if ((options & (copy_options::skip_symlinks | copy_options::create_symlinks)) != copy_options::none) { + fs_to = symlink_status(to, tec); + } + else { + fs_to = status(to, tec); + } + if (is_other(fs_from) || is_other(fs_to) || (is_directory(fs_from) && is_regular_file(fs_to)) || (exists(fs_to) && equivalent(from, to, ec))) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + } + else if (is_symlink(fs_from)) { + if ((options & copy_options::skip_symlinks) == copy_options::none) { + if (!exists(fs_to) && (options & copy_options::copy_symlinks) != copy_options::none) { + copy_symlink(from, to, ec); + } + else { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + } + } + } + else if (is_regular_file(fs_from)) { + if ((options & copy_options::directories_only) == copy_options::none) { + if ((options & copy_options::create_symlinks) != copy_options::none) { + create_symlink(from.is_absolute() ? from : canonical(from, ec), to, ec); + } +#ifndef GHC_OS_WEB + else if ((options & copy_options::create_hard_links) != copy_options::none) { + create_hard_link(from, to, ec); + } +#endif + else if (is_directory(fs_to)) { + copy_file(from, to / from.filename(), options, ec); + } + else { + copy_file(from, to, options, ec); + } + } + } +#ifdef LWG_2682_BEHAVIOUR + else if (is_directory(fs_from) && (options & copy_options::create_symlinks) != copy_options::none) { + ec = detail::make_error_code(detail::portable_error::is_a_directory); + } +#endif + else if (is_directory(fs_from) && (options == copy_options::none || (options & copy_options::recursive) != copy_options::none)) { + if (!exists(fs_to)) { + create_directory(to, from, ec); + if (ec) { + return; + } + } + for (auto iter = directory_iterator(from, ec); iter != directory_iterator(); iter.increment(ec)) { + if (!ec) { + copy(iter->path(), to / iter->path().filename(), options | static_cast(0x8000), ec); + } + if (ec) { + return; + } + } + } + return; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool copy_file(const path& from, const path& to) +{ + return copy_file(from, to, copy_options::none); +} +#endif + +GHC_INLINE bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept +{ + return copy_file(from, to, copy_options::none, ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool copy_file(const path& from, const path& to, copy_options option) +{ + std::error_code ec; + auto result = copy_file(from, to, option, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } + return result; +} +#endif + +GHC_INLINE bool copy_file(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept +{ + std::error_code tecf, tect; + auto sf = status(from, tecf); + auto st = status(to, tect); + bool overwrite = false; + ec.clear(); + if (!is_regular_file(sf)) { + ec = tecf; + return false; + } + if (exists(st) && (!is_regular_file(st) || equivalent(from, to, ec) || (options & (copy_options::skip_existing | copy_options::overwrite_existing | copy_options::update_existing)) == copy_options::none)) { + ec = tect ? tect : detail::make_error_code(detail::portable_error::exists); + return false; + } + if (exists(st)) { + if ((options & copy_options::update_existing) == copy_options::update_existing) { + auto from_time = last_write_time(from, ec); + if (ec) { + ec = detail::make_system_error(); + return false; + } + auto to_time = last_write_time(to, ec); + if (ec) { + ec = detail::make_system_error(); + return false; + } + if (from_time <= to_time) { + return false; + } + } + overwrite = true; + } +#ifdef GHC_OS_WINDOWS + if (!::CopyFileW(detail::fromUtf8(from.u8string()).c_str(), detail::fromUtf8(to.u8string()).c_str(), !overwrite)) { + ec = detail::make_system_error(); + return false; + } + return true; +#else + std::vector buffer(16384, '\0'); + int in = -1, out = -1; + if ((in = ::open(from.c_str(), O_RDONLY)) < 0) { + ec = detail::make_system_error(); + return false; + } + int mode = O_CREAT | O_WRONLY | O_TRUNC; + if (!overwrite) { + mode |= O_EXCL; + } + if ((out = ::open(to.c_str(), mode, static_cast(sf.permissions() & perms::all))) < 0) { + ec = detail::make_system_error(); + ::close(in); + return false; + } + ssize_t br, bw; + while ((br = ::read(in, buffer.data(), buffer.size())) > 0) { + ssize_t offset = 0; + do { + if ((bw = ::write(out, buffer.data() + offset, static_cast(br))) > 0) { + br -= bw; + offset += bw; + } + else if (bw < 0) { + ec = detail::make_system_error(); + ::close(in); + ::close(out); + return false; + } + } while (br); + } + ::close(in); + ::close(out); + return true; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink) +{ + std::error_code ec; + copy_symlink(existing_symlink, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), existing_symlink, new_symlink, ec); + } +} +#endif + +GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept +{ + ec.clear(); + auto to = read_symlink(existing_symlink, ec); + if (!ec) { + if (exists(to, ec) && is_directory(to, ec)) { + create_directory_symlink(to, new_symlink, ec); + } + else { + create_symlink(to, new_symlink, ec); + } + } +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directories(const path& p) +{ + std::error_code ec; + auto result = create_directories(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directories(const path& p, std::error_code& ec) noexcept +{ + path current; + ec.clear(); + bool didCreate = false; + for (path::string_type part : p) { + current /= part; + if (current != p.root_name() && current != p.root_path()) { + std::error_code tec; + auto fs = status(current, tec); + if (tec && fs.type() != file_type::not_found) { + ec = tec; + return false; + } + if (!exists(fs)) { + create_directory(current, ec); + if (ec) { + std::error_code tmp_ec; + if (is_directory(current, tmp_ec)) { + ec.clear(); + } else { + return false; + } + } + didCreate = true; + } +#ifndef LWG_2935_BEHAVIOUR + else if (!is_directory(fs)) { + ec = detail::make_error_code(detail::portable_error::exists); + return false; + } +#endif + } + } + return didCreate; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directory(const path& p) +{ + std::error_code ec; + auto result = create_directory(p, path(), ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directory(const path& p, std::error_code& ec) noexcept +{ + return create_directory(p, path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directory(const path& p, const path& attributes) +{ + std::error_code ec; + auto result = create_directory(p, attributes, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept +{ + std::error_code tec; + ec.clear(); + auto fs = status(p, tec); +#ifdef LWG_2935_BEHAVIOUR + if (status_known(fs) && exists(fs)) { + return false; + } +#else + if (status_known(fs) && exists(fs) && is_directory(fs)) { + return false; + } +#endif +#ifdef GHC_OS_WINDOWS + if (!attributes.empty()) { + if (!::CreateDirectoryExW(detail::fromUtf8(attributes.u8string()).c_str(), detail::fromUtf8(p.u8string()).c_str(), NULL)) { + ec = detail::make_system_error(); + return false; + } + } + else if (!::CreateDirectoryW(detail::fromUtf8(p.u8string()).c_str(), NULL)) { + ec = detail::make_system_error(); + return false; + } +#else + ::mode_t attribs = static_cast(perms::all); + if (!attributes.empty()) { + struct ::stat fileStat; + if (::stat(attributes.c_str(), &fileStat) != 0) { + ec = detail::make_system_error(); + return false; + } + attribs = fileStat.st_mode; + } + if (::mkdir(p.c_str(), attribs) != 0) { + ec = detail::make_system_error(); + return false; + } +#endif + return true; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink) +{ + std::error_code ec; + create_directory_symlink(to, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); + } +} +#endif + +GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept +{ + detail::create_symlink(to, new_symlink, true, ec); +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link) +{ + std::error_code ec; + create_hard_link(to, new_hard_link, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_hard_link, ec); + } +} +#endif + +GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept +{ + detail::create_hardlink(to, new_hard_link, ec); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_symlink(const path& to, const path& new_symlink) +{ + std::error_code ec; + create_symlink(to, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); + } +} +#endif + +GHC_INLINE void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept +{ + detail::create_symlink(to, new_symlink, false, ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path current_path() +{ + std::error_code ec; + auto result = current_path(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE path current_path(std::error_code& ec) +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + DWORD pathlen = ::GetCurrentDirectoryW(0, 0); + std::unique_ptr buffer(new wchar_t[size_t(pathlen) + 1]); + if (::GetCurrentDirectoryW(pathlen, buffer.get()) == 0) { + ec = detail::make_system_error(); + return path(); + } + return path(std::wstring(buffer.get()), path::native_format); +#else + size_t pathlen = static_cast(std::max(int(::pathconf(".", _PC_PATH_MAX)), int(PATH_MAX))); + std::unique_ptr buffer(new char[pathlen + 1]); + if (::getcwd(buffer.get(), pathlen) == nullptr) { + ec = detail::make_system_error(); + return path(); + } + return path(buffer.get()); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void current_path(const path& p) +{ + std::error_code ec; + current_path(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void current_path(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (!::SetCurrentDirectoryW(detail::fromUtf8(p.u8string()).c_str())) { + ec = detail::make_system_error(); + } +#else + if (::chdir(p.string().c_str()) == -1) { + ec = detail::make_system_error(); + } +#endif +} + +GHC_INLINE bool exists(file_status s) noexcept +{ + return status_known(s) && s.type() != file_type::not_found; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool exists(const path& p) +{ + return exists(status(p)); +} +#endif + +GHC_INLINE bool exists(const path& p, std::error_code& ec) noexcept +{ + file_status s = status(p, ec); + if (status_known(s)) { + ec.clear(); + } + return exists(s); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool equivalent(const path& p1, const path& p2) +{ + std::error_code ec; + bool result = equivalent(p1, p2, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p1, p2, ec); + } + return result; +} +#endif + +GHC_INLINE bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + std::shared_ptr file1(::CreateFileW(p1.wstring().c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); + auto e1 = ::GetLastError(); + std::shared_ptr file2(::CreateFileW(p2.wstring().c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); + if (file1.get() == INVALID_HANDLE_VALUE || file2.get() == INVALID_HANDLE_VALUE) { +#ifdef LWG_2937_BEHAVIOUR + ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); +#else + if (file1 == file2) { + ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); + } +#endif + return false; + } + BY_HANDLE_FILE_INFORMATION inf1, inf2; + if (!::GetFileInformationByHandle(file1.get(), &inf1)) { + ec = detail::make_system_error(); + return false; + } + if (!::GetFileInformationByHandle(file2.get(), &inf2)) { + ec = detail::make_system_error(); + return false; + } + return inf1.ftLastWriteTime.dwLowDateTime == inf2.ftLastWriteTime.dwLowDateTime && inf1.ftLastWriteTime.dwHighDateTime == inf2.ftLastWriteTime.dwHighDateTime && inf1.nFileIndexHigh == inf2.nFileIndexHigh && inf1.nFileIndexLow == inf2.nFileIndexLow && + inf1.nFileSizeHigh == inf2.nFileSizeHigh && inf1.nFileSizeLow == inf2.nFileSizeLow && inf1.dwVolumeSerialNumber == inf2.dwVolumeSerialNumber; +#else + struct ::stat s1, s2; + auto rc1 = ::stat(p1.c_str(), &s1); + auto e1 = errno; + auto rc2 = ::stat(p2.c_str(), &s2); + if (rc1 || rc2) { +#ifdef LWG_2937_BEHAVIOUR + ec = detail::make_system_error(e1 ? e1 : errno); +#else + if (rc1 && rc2) { + ec = detail::make_system_error(e1 ? e1 : errno); + } +#endif + return false; + } + return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t file_size(const path& p) +{ + std::error_code ec; + auto result = file_size(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t file_size(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!GetFileAttributesExW(detail::fromUtf8(p.u8string()).c_str(), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + return static_cast(-1); + } + return static_cast(attr.nFileSizeHigh) << (sizeof(attr.nFileSizeHigh) * 8) | attr.nFileSizeLow; +#else + struct ::stat fileStat; + if (::stat(p.c_str(), &fileStat) == -1) { + ec = detail::make_system_error(); + return static_cast(-1); + } + return static_cast(fileStat.st_size); +#endif +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t hard_link_count(const path& p) +{ + std::error_code ec; + auto result = hard_link_count(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + uintmax_t result = static_cast(-1); + std::shared_ptr file(::CreateFileW(p.wstring().c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); + BY_HANDLE_FILE_INFORMATION inf; + if (file.get() == INVALID_HANDLE_VALUE) { + ec = detail::make_system_error(); + } + else { + if (!::GetFileInformationByHandle(file.get(), &inf)) { + ec = detail::make_system_error(); + } + else { + result = inf.nNumberOfLinks; + } + } + return result; +#else + uintmax_t result = 0; + file_status fs = detail::status_ex(p, ec, nullptr, nullptr, &result, nullptr); + if (fs.type() == file_type::not_found) { + ec = detail::make_error_code(detail::portable_error::not_found); + } + return ec ? static_cast(-1) : result; +#endif +} +#endif + +GHC_INLINE bool is_block_file(file_status s) noexcept +{ + return s.type() == file_type::block; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_block_file(const path& p) +{ + return is_block_file(status(p)); +} +#endif + +GHC_INLINE bool is_block_file(const path& p, std::error_code& ec) noexcept +{ + return is_block_file(status(p, ec)); +} + +GHC_INLINE bool is_character_file(file_status s) noexcept +{ + return s.type() == file_type::character; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_character_file(const path& p) +{ + return is_character_file(status(p)); +} +#endif + +GHC_INLINE bool is_character_file(const path& p, std::error_code& ec) noexcept +{ + return is_character_file(status(p, ec)); +} + +GHC_INLINE bool is_directory(file_status s) noexcept +{ + return s.type() == file_type::directory; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_directory(const path& p) +{ + return is_directory(status(p)); +} +#endif + +GHC_INLINE bool is_directory(const path& p, std::error_code& ec) noexcept +{ + return is_directory(status(p, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_empty(const path& p) +{ + if (is_directory(p)) { + return directory_iterator(p) == directory_iterator(); + } + else { + return file_size(p) == 0; + } +} +#endif + +GHC_INLINE bool is_empty(const path& p, std::error_code& ec) noexcept +{ + auto fs = status(p, ec); + if (ec) { + return false; + } + if (is_directory(fs)) { + directory_iterator iter(p, ec); + if (ec) { + return false; + } + return iter == directory_iterator(); + } + else { + auto sz = file_size(p, ec); + if (ec) { + return false; + } + return sz == 0; + } +} + +GHC_INLINE bool is_fifo(file_status s) noexcept +{ + return s.type() == file_type::fifo; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_fifo(const path& p) +{ + return is_fifo(status(p)); +} +#endif + +GHC_INLINE bool is_fifo(const path& p, std::error_code& ec) noexcept +{ + return is_fifo(status(p, ec)); +} + +GHC_INLINE bool is_other(file_status s) noexcept +{ + return exists(s) && !is_regular_file(s) && !is_directory(s) && !is_symlink(s); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_other(const path& p) +{ + return is_other(status(p)); +} +#endif + +GHC_INLINE bool is_other(const path& p, std::error_code& ec) noexcept +{ + return is_other(status(p, ec)); +} + +GHC_INLINE bool is_regular_file(file_status s) noexcept +{ + return s.type() == file_type::regular; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_regular_file(const path& p) +{ + return is_regular_file(status(p)); +} +#endif + +GHC_INLINE bool is_regular_file(const path& p, std::error_code& ec) noexcept +{ + return is_regular_file(status(p, ec)); +} + +GHC_INLINE bool is_socket(file_status s) noexcept +{ + return s.type() == file_type::socket; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_socket(const path& p) +{ + return is_socket(status(p)); +} +#endif + +GHC_INLINE bool is_socket(const path& p, std::error_code& ec) noexcept +{ + return is_socket(status(p, ec)); +} + +GHC_INLINE bool is_symlink(file_status s) noexcept +{ + return s.type() == file_type::symlink; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_symlink(const path& p) +{ + return is_symlink(symlink_status(p)); +} +#endif + +GHC_INLINE bool is_symlink(const path& p, std::error_code& ec) noexcept +{ + return is_symlink(symlink_status(p, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_time_type last_write_time(const path& p) +{ + std::error_code ec; + auto result = last_write_time(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE file_time_type last_write_time(const path& p, std::error_code& ec) noexcept +{ + time_t result = 0; + ec.clear(); + file_status fs = detail::status_ex(p, ec, nullptr, nullptr, nullptr, &result); + return ec ? (file_time_type::min)() : std::chrono::system_clock::from_time_t(result); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void last_write_time(const path& p, file_time_type new_time) +{ + std::error_code ec; + last_write_time(p, new_time, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept +{ + ec.clear(); + auto d = new_time.time_since_epoch(); +#ifdef GHC_OS_WINDOWS + std::shared_ptr file(::CreateFileW(p.wstring().c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL), ::CloseHandle); + FILETIME ft; + auto tt = std::chrono::duration_cast(d).count() * 10 + 116444736000000000; + ft.dwLowDateTime = static_cast(tt); + ft.dwHighDateTime = static_cast(tt >> 32); + if (!::SetFileTime(file.get(), 0, 0, &ft)) { + ec = detail::make_system_error(); + } +#elif defined(GHC_OS_MACOS) +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED +#if __MAC_OS_X_VERSION_MIN_REQUIRED < 101300 + struct ::stat fs; + if (::stat(p.c_str(), &fs) == 0) { + struct ::timeval tv[2]; + tv[0].tv_sec = fs.st_atimespec.tv_sec; + tv[0].tv_usec = static_cast(fs.st_atimespec.tv_nsec / 1000); + tv[1].tv_sec = std::chrono::duration_cast(d).count(); + tv[1].tv_usec = static_cast(std::chrono::duration_cast(d).count() % 1000000); + if (::utimes(p.c_str(), tv) == 0) { + return; + } + } + ec = detail::make_system_error(); + return; +#else + struct ::timespec times[2]; + times[0].tv_sec = 0; + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_sec = std::chrono::duration_cast(d).count(); + times[1].tv_nsec = 0; //std::chrono::duration_cast(d).count() % 1000000000; + if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { + ec = detail::make_system_error(); + } + return; +#endif +#endif +#else +#ifndef UTIME_OMIT +#define UTIME_OMIT ((1l << 30) - 2l) +#endif + struct ::timespec times[2]; + times[0].tv_sec = 0; + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_sec = static_cast(std::chrono::duration_cast(d).count()); + times[1].tv_nsec = static_cast(std::chrono::duration_cast(d).count() % 1000000000); +#if defined(__ANDROID_API__) && __ANDROID_API__ < 12 + if (syscall(__NR_utimensat, AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { +#else + if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { +#endif + ec = detail::make_system_error(); + } + return; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void permissions(const path& p, perms prms, perm_options opts) +{ + std::error_code ec; + permissions(p, prms, opts, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void permissions(const path& p, perms prms, std::error_code& ec) noexcept +{ + permissions(p, prms, perm_options::replace, ec); +} + +GHC_INLINE void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) +{ + if (static_cast(opts & (perm_options::replace | perm_options::add | perm_options::remove)) == 0) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + return; + } + auto fs = symlink_status(p, ec); + if ((opts & perm_options::replace) != perm_options::replace) { + if ((opts & perm_options::add) == perm_options::add) { + prms = fs.permissions() | prms; + } + else { + prms = fs.permissions() & ~prms; + } + } +#ifdef GHC_OS_WINDOWS +#ifdef __GNUC__ + auto oldAttr = GetFileAttributesW(p.wstring().c_str()); + if (oldAttr != INVALID_FILE_ATTRIBUTES) { + DWORD newAttr = ((prms & perms::owner_write) == perms::owner_write) ? oldAttr & ~(static_cast(FILE_ATTRIBUTE_READONLY)) : oldAttr | FILE_ATTRIBUTE_READONLY; + if (oldAttr == newAttr || SetFileAttributesW(p.wstring().c_str(), newAttr)) { + return; + } + } + ec = detail::make_system_error(); +#else + int mode = 0; + if ((prms & perms::owner_read) == perms::owner_read) { + mode |= _S_IREAD; + } + if ((prms & perms::owner_write) == perms::owner_write) { + mode |= _S_IWRITE; + } + if (::_wchmod(p.wstring().c_str(), mode) != 0) { + ec = detail::make_system_error(); + } +#endif +#else + if ((opts & perm_options::nofollow) != perm_options::nofollow) { + if (::chmod(p.c_str(), static_cast(prms)) != 0) { + ec = detail::make_system_error(); + } + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path proximate(const path& p, std::error_code& ec) +{ + return proximate(p, current_path(), ec); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path proximate(const path& p, const path& base) +{ + return weakly_canonical(p).lexically_proximate(weakly_canonical(base)); +} +#endif + +GHC_INLINE path proximate(const path& p, const path& base, std::error_code& ec) +{ + return weakly_canonical(p, ec).lexically_proximate(weakly_canonical(base, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path read_symlink(const path& p) +{ + std::error_code ec; + auto result = read_symlink(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path read_symlink(const path& p, std::error_code& ec) +{ + file_status fs = symlink_status(p, ec); + if (fs.type() != file_type::symlink) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + return path(); + } + auto result = detail::resolveSymlink(p, ec); + return ec ? path() : result; +} + +GHC_INLINE path relative(const path& p, std::error_code& ec) +{ + return relative(p, current_path(ec), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path relative(const path& p, const path& base) +{ + return weakly_canonical(p).lexically_relative(weakly_canonical(base)); +} +#endif + +GHC_INLINE path relative(const path& p, const path& base, std::error_code& ec) +{ + return weakly_canonical(p, ec).lexically_relative(weakly_canonical(base, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool remove(const path& p) +{ + std::error_code ec; + auto result = remove(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool remove(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + std::wstring np = detail::fromUtf8(p.u8string()); + DWORD attr = GetFileAttributesW(np.c_str()); + if (attr == INVALID_FILE_ATTRIBUTES) { + auto error = ::GetLastError(); + if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) { + return false; + } + ec = detail::make_system_error(error); + } + if (!ec) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + if (!RemoveDirectoryW(np.c_str())) { + ec = detail::make_system_error(); + } + } + else { + if (!DeleteFileW(np.c_str())) { + ec = detail::make_system_error(); + } + } + } +#else + if (::remove(p.c_str()) == -1) { + auto error = errno; + if (error == ENOENT) { + return false; + } + ec = detail::make_system_error(); + } +#endif + return ec ? false : true; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t remove_all(const path& p) +{ + std::error_code ec; + auto result = remove_all(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t remove_all(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); + uintmax_t count = 0; + if (p == "/") { + ec = detail::make_error_code(detail::portable_error::not_supported); + return static_cast(-1); + } + std::error_code tec; + auto fs = status(p, tec); + if (exists(fs) && is_directory(fs)) { + for (auto iter = directory_iterator(p, ec); iter != directory_iterator(); iter.increment(ec)) { + if (ec) { + break; + } + bool is_symlink_result = iter->is_symlink(ec); + if (ec) return static_cast(-1); + bool is_directory_result = iter->is_directory(ec); + if (ec) return static_cast(-1); + if (!is_symlink_result && is_directory_result) { + count += remove_all(iter->path(), ec); + if (ec) { + return static_cast(-1); + } + } + else { + remove(iter->path(), ec); + if (ec) { + return static_cast(-1); + } + ++count; + } + } + } + if (!ec) { + if (remove(p, ec)) { + ++count; + } + } + if (ec) { + return static_cast(-1); + } + return count; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void rename(const path& from, const path& to) +{ + std::error_code ec; + rename(from, to, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } +} +#endif + +GHC_INLINE void rename(const path& from, const path& to, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (from != to) { + if (!MoveFileExW(detail::fromUtf8(from.u8string()).c_str(), detail::fromUtf8(to.u8string()).c_str(), (DWORD)MOVEFILE_REPLACE_EXISTING)) { + ec = detail::make_system_error(); + } + } +#else + if (from != to) { + if (::rename(from.c_str(), to.c_str()) != 0) { + ec = detail::make_system_error(); + } + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void resize_file(const path& p, uintmax_t size) +{ + std::error_code ec; + resize_file(p, size, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + LARGE_INTEGER lisize; + lisize.QuadPart = static_cast(size); + if(lisize.QuadPart < 0) { +#ifdef ERROR_FILE_TOO_LARGE + ec = detail::make_system_error(ERROR_FILE_TOO_LARGE); +#else + ec = detail::make_system_error(223); +#endif + return; + } + std::shared_ptr file(CreateFileW(detail::fromUtf8(p.u8string()).c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL), CloseHandle); + if (file.get() == INVALID_HANDLE_VALUE) { + ec = detail::make_system_error(); + } + else if (SetFilePointerEx(file.get(), lisize, NULL, FILE_BEGIN) == 0 || SetEndOfFile(file.get()) == 0) { + ec = detail::make_system_error(); + } +#else + if (::truncate(p.c_str(), static_cast(size)) != 0) { + ec = detail::make_system_error(); + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE space_info space(const path& p) +{ + std::error_code ec; + auto result = space(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE space_info space(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + ULARGE_INTEGER freeBytesAvailableToCaller = {{0, 0}}; + ULARGE_INTEGER totalNumberOfBytes = {{0, 0}}; + ULARGE_INTEGER totalNumberOfFreeBytes = {{0, 0}}; + if (!GetDiskFreeSpaceExW(detail::fromUtf8(p.u8string()).c_str(), &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes)) { + ec = detail::make_system_error(); + return {static_cast(-1), static_cast(-1), static_cast(-1)}; + } + return {static_cast(totalNumberOfBytes.QuadPart), static_cast(totalNumberOfFreeBytes.QuadPart), static_cast(freeBytesAvailableToCaller.QuadPart)}; +#else + struct ::statvfs sfs; + if (::statvfs(p.c_str(), &sfs) != 0) { + ec = detail::make_system_error(); + return {static_cast(-1), static_cast(-1), static_cast(-1)}; + } + return {static_cast(sfs.f_blocks * sfs.f_frsize), static_cast(sfs.f_bfree * sfs.f_frsize), static_cast(sfs.f_bavail * sfs.f_frsize)}; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status status(const path& p) +{ + std::error_code ec; + auto result = status(p, ec); + if (result.type() == file_type::none) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE file_status status(const path& p, std::error_code& ec) noexcept +{ + return detail::status_ex(p, ec); +} + +GHC_INLINE bool status_known(file_status s) noexcept +{ + return s.type() != file_type::none; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status symlink_status(const path& p) +{ + std::error_code ec; + auto result = symlink_status(p, ec); + if (result.type() == file_type::none) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE file_status symlink_status(const path& p, std::error_code& ec) noexcept +{ + return detail::symlink_status_ex(p, ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path temp_directory_path() +{ + std::error_code ec; + path result = temp_directory_path(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE path temp_directory_path(std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + wchar_t buffer[512]; + auto rc = GetTempPathW(511, buffer); + if (!rc || rc > 511) { + ec = detail::make_system_error(); + return path(); + } + return path(std::wstring(buffer)); +#else + static const char* temp_vars[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR", nullptr}; + const char* temp_path = nullptr; + for (auto temp_name = temp_vars; *temp_name != nullptr; ++temp_name) { + temp_path = std::getenv(*temp_name); + if (temp_path) { + return path(temp_path); + } + } + return path("/tmp"); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path weakly_canonical(const path& p) +{ + std::error_code ec; + auto result = weakly_canonical(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path weakly_canonical(const path& p, std::error_code& ec) noexcept +{ + path result; + ec.clear(); + bool scan = true; + for (auto pe : p) { + if (scan) { + std::error_code tec; + if (exists(result / pe, tec)) { + result /= pe; + } + else { + if (ec) { + return path(); + } + scan = false; + if (!result.empty()) { + result = canonical(result, ec) / pe; + if (ec) { + break; + } + } + else { + result /= pe; + } + } + } + else { + result /= pe; + } + } + if (scan) { + if (!result.empty()) { + result = canonical(result, ec); + } + } + return ec ? path() : result.lexically_normal(); +} + +//----------------------------------------------------------------------------- +// 30.10.11 class file_status +// 30.10.11.1 constructors and destructor +GHC_INLINE file_status::file_status() noexcept + : file_status(file_type::none) +{ +} + +GHC_INLINE file_status::file_status(file_type ft, perms prms) noexcept + : _type(ft) + , _perms(prms) +{ +} + +GHC_INLINE file_status::file_status(const file_status& other) noexcept + : _type(other._type) + , _perms(other._perms) +{ +} + +GHC_INLINE file_status::file_status(file_status&& other) noexcept + : _type(other._type) + , _perms(other._perms) +{ +} + +GHC_INLINE file_status::~file_status() {} + +// assignments: +GHC_INLINE file_status& file_status::operator=(const file_status& rhs) noexcept +{ + _type = rhs._type; + _perms = rhs._perms; + return *this; +} + +GHC_INLINE file_status& file_status::operator=(file_status&& rhs) noexcept +{ + _type = rhs._type; + _perms = rhs._perms; + return *this; +} + +// 30.10.11.3 modifiers +GHC_INLINE void file_status::type(file_type ft) noexcept +{ + _type = ft; +} + +GHC_INLINE void file_status::permissions(perms prms) noexcept +{ + _perms = prms; +} + +// 30.10.11.2 observers +GHC_INLINE file_type file_status::type() const noexcept +{ + return _type; +} + +GHC_INLINE perms file_status::permissions() const noexcept +{ + return _perms; +} + +//----------------------------------------------------------------------------- +// 30.10.12 class directory_entry +// 30.10.12.1 constructors and destructor +// directory_entry::directory_entry() noexcept = default; +// directory_entry::directory_entry(const directory_entry&) = default; +// directory_entry::directory_entry(directory_entry&&) noexcept = default; +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_entry::directory_entry(const filesystem::path& p) + : _path(p) + , _file_size(0) +#ifndef GHC_OS_WINDOWS + , _hard_link_count(0) +#endif + , _last_write_time(0) +{ + refresh(); +} +#endif + +GHC_INLINE directory_entry::directory_entry(const filesystem::path& p, std::error_code& ec) + : _path(p) + , _file_size(0) +#ifndef GHC_OS_WINDOWS + , _hard_link_count(0) +#endif + , _last_write_time(0) +{ + refresh(ec); +} + +GHC_INLINE directory_entry::~directory_entry() {} + +// assignments: +// directory_entry& directory_entry::operator=(const directory_entry&) = default; +// directory_entry& directory_entry::operator=(directory_entry&&) noexcept = default; + +// 30.10.12.2 directory_entry modifiers +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::assign(const filesystem::path& p) +{ + _path = p; + refresh(); +} +#endif + +GHC_INLINE void directory_entry::assign(const filesystem::path& p, std::error_code& ec) +{ + _path = p; + refresh(ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p) +{ + _path.replace_filename(p); + refresh(); +} +#endif + +GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p, std::error_code& ec) +{ + _path.replace_filename(p); + refresh(ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::refresh() +{ + std::error_code ec; + refresh(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _path, ec); + } +} +#endif + +GHC_INLINE void directory_entry::refresh(std::error_code& ec) noexcept +{ +#ifdef GHC_OS_WINDOWS + _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, nullptr, &_last_write_time); +#else + _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, &_hard_link_count, &_last_write_time); +#endif +} + +// 30.10.12.3 directory_entry observers +GHC_INLINE const filesystem::path& directory_entry::path() const noexcept +{ + return _path; +} + +GHC_INLINE directory_entry::operator const filesystem::path&() const noexcept +{ + return _path; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::exists() const +{ + return filesystem::exists(status()); +} +#endif + +GHC_INLINE bool directory_entry::exists(std::error_code& ec) const noexcept +{ + return filesystem::exists(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_block_file() const +{ + return filesystem::is_block_file(status()); +} +#endif +GHC_INLINE bool directory_entry::is_block_file(std::error_code& ec) const noexcept +{ + return filesystem::is_block_file(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_character_file() const +{ + return filesystem::is_character_file(status()); +} +#endif + +GHC_INLINE bool directory_entry::is_character_file(std::error_code& ec) const noexcept +{ + return filesystem::is_character_file(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_directory() const +{ + return filesystem::is_directory(status()); +} +#endif + +GHC_INLINE bool directory_entry::is_directory(std::error_code& ec) const noexcept +{ + return filesystem::is_directory(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_fifo() const +{ + return filesystem::is_fifo(status()); +} +#endif + +GHC_INLINE bool directory_entry::is_fifo(std::error_code& ec) const noexcept +{ + return filesystem::is_fifo(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_other() const +{ + return filesystem::is_other(status()); +} +#endif + +GHC_INLINE bool directory_entry::is_other(std::error_code& ec) const noexcept +{ + return filesystem::is_other(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_regular_file() const +{ + return filesystem::is_regular_file(status()); +} +#endif + +GHC_INLINE bool directory_entry::is_regular_file(std::error_code& ec) const noexcept +{ + return filesystem::is_regular_file(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_socket() const +{ + return filesystem::is_socket(status()); +} +#endif + +GHC_INLINE bool directory_entry::is_socket(std::error_code& ec) const noexcept +{ + return filesystem::is_socket(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_symlink() const +{ + return filesystem::is_symlink(symlink_status()); +} +#endif + +GHC_INLINE bool directory_entry::is_symlink(std::error_code& ec) const noexcept +{ + return filesystem::is_symlink(symlink_status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t directory_entry::file_size() const +{ + if (_status.type() != file_type::none) { + return _file_size; + } + return filesystem::file_size(path()); +} +#endif + +GHC_INLINE uintmax_t directory_entry::file_size(std::error_code& ec) const noexcept +{ + if (_status.type() != file_type::none) { + ec.clear(); + return _file_size; + } + return filesystem::file_size(path(), ec); +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t directory_entry::hard_link_count() const +{ +#ifndef GHC_OS_WINDOWS + if (_status.type() != file_type::none) { + return _hard_link_count; + } +#endif + return filesystem::hard_link_count(path()); +} +#endif + +GHC_INLINE uintmax_t directory_entry::hard_link_count(std::error_code& ec) const noexcept +{ +#ifndef GHC_OS_WINDOWS + if (_status.type() != file_type::none) { + ec.clear(); + return _hard_link_count; + } +#endif + return filesystem::hard_link_count(path(), ec); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_time_type directory_entry::last_write_time() const +{ + if (_status.type() != file_type::none) { + return std::chrono::system_clock::from_time_t(_last_write_time); + } + return filesystem::last_write_time(path()); +} +#endif + +GHC_INLINE file_time_type directory_entry::last_write_time(std::error_code& ec) const noexcept +{ + if (_status.type() != file_type::none) { + ec.clear(); + return std::chrono::system_clock::from_time_t(_last_write_time); + } + return filesystem::last_write_time(path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status directory_entry::status() const +{ + if (_status.type() != file_type::none) { + return _status; + } + return filesystem::status(path()); +} +#endif + +GHC_INLINE file_status directory_entry::status(std::error_code& ec) const noexcept +{ + if (_status.type() != file_type::none) { + ec.clear(); + return _status; + } + return filesystem::status(path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status directory_entry::symlink_status() const +{ + if (_symlink_status.type() != file_type::none) { + return _symlink_status; + } + return filesystem::symlink_status(path()); +} +#endif + +GHC_INLINE file_status directory_entry::symlink_status(std::error_code& ec) const noexcept +{ + if (_symlink_status.type() != file_type::none) { + ec.clear(); + return _symlink_status; + } + return filesystem::symlink_status(path(), ec); +} + +GHC_INLINE bool directory_entry::operator<(const directory_entry& rhs) const noexcept +{ + return _path < rhs._path; +} + +GHC_INLINE bool directory_entry::operator==(const directory_entry& rhs) const noexcept +{ + return _path == rhs._path; +} + +GHC_INLINE bool directory_entry::operator!=(const directory_entry& rhs) const noexcept +{ + return _path != rhs._path; +} + +GHC_INLINE bool directory_entry::operator<=(const directory_entry& rhs) const noexcept +{ + return _path <= rhs._path; +} + +GHC_INLINE bool directory_entry::operator>(const directory_entry& rhs) const noexcept +{ + return _path > rhs._path; +} + +GHC_INLINE bool directory_entry::operator>=(const directory_entry& rhs) const noexcept +{ + return _path >= rhs._path; +} + +//----------------------------------------------------------------------------- +// 30.10.13 class directory_iterator + +#ifdef GHC_OS_WINDOWS +class directory_iterator::impl +{ +public: + impl(const path& p, directory_options options) + : _base(p) + , _options(options) + , _dirHandle(INVALID_HANDLE_VALUE) + { + if (!_base.empty()) { + ZeroMemory(&_findData, sizeof(WIN32_FIND_DATAW)); + if ((_dirHandle = FindFirstFileW(detail::fromUtf8((_base / "*").u8string()).c_str(), &_findData)) != INVALID_HANDLE_VALUE) { + if (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L"..") { + increment(_ec); + } + else { + _current = _base / std::wstring(_findData.cFileName); + copyToDirEntry(_ec); + } + } + else { + auto error = ::GetLastError(); + _base = filesystem::path(); + if (error != ERROR_ACCESS_DENIED || (options & directory_options::skip_permission_denied) == directory_options::none) { + _ec = detail::make_system_error(); + } + } + } + } + impl(const impl& other) = delete; + ~impl() + { + if (_dirHandle != INVALID_HANDLE_VALUE) { + FindClose(_dirHandle); + _dirHandle = INVALID_HANDLE_VALUE; + } + } + void increment(std::error_code& ec) + { + if (_dirHandle != INVALID_HANDLE_VALUE) { + do { + if (FindNextFileW(_dirHandle, &_findData)) { + _current = _base; +#ifdef GHC_RAISE_UNICODE_ERRORS + try { + _current.append_name(detail::toUtf8(_findData.cFileName).c_str()); + } + catch(filesystem_error& fe) { + ec = fe.code(); + return; + } +#else + _current.append_name(detail::toUtf8(_findData.cFileName).c_str()); +#endif + copyToDirEntry(ec); + } + else { + auto err = ::GetLastError(); + if(err != ERROR_NO_MORE_FILES) { + _ec = ec = detail::make_system_error(err); + } + FindClose(_dirHandle); + _dirHandle = INVALID_HANDLE_VALUE; + _current = filesystem::path(); + break; + } + } while (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L".."); + } + else { + ec = _ec; + } + } + void copyToDirEntry(std::error_code& ec) + { + _dir_entry._path = _current; + if (_findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + _dir_entry._status = detail::status_ex(_current, ec, &_dir_entry._symlink_status, &_dir_entry._file_size, nullptr, &_dir_entry._last_write_time); + } + else { + _dir_entry._status = detail::status_from_INFO(_current, &_findData, ec, &_dir_entry._file_size, &_dir_entry._last_write_time); + _dir_entry._symlink_status = _dir_entry._status; + } + if (ec) { + if (_dir_entry._status.type() != file_type::none && _dir_entry._symlink_status.type() != file_type::none) { + ec.clear(); + } + else { + _dir_entry._file_size = static_cast(-1); + _dir_entry._last_write_time = 0; + } + } + } + path _base; + directory_options _options; + WIN32_FIND_DATAW _findData; + HANDLE _dirHandle; + path _current; + directory_entry _dir_entry; + std::error_code _ec; +}; +#else +// POSIX implementation +class directory_iterator::impl +{ +public: + impl(const path& path, directory_options options) + : _base(path) + , _options(options) + , _dir(nullptr) + , _entry(nullptr) + { + if (!path.empty()) { + _dir = ::opendir(path.native().c_str()); + } + if (!path.empty()) { + if (!_dir) { + auto error = errno; + _base = filesystem::path(); + if (error != EACCES || (options & directory_options::skip_permission_denied) == directory_options::none) { + _ec = detail::make_system_error(); + } + } + else { + increment(_ec); + } + } + } + impl(const impl& other) = delete; + ~impl() + { + if (_dir) { + ::closedir(_dir); + } + } + void increment(std::error_code& ec) + { + if (_dir) { + bool skip; + do { + skip = false; + errno = 0; + _entry = ::readdir(_dir); + if (_entry) { + _current = _base; + _current.append_name(_entry->d_name); + _dir_entry = directory_entry(_current, ec); + if(ec && (ec.value() == EACCES || ec.value() == EPERM) && (_options & directory_options::skip_permission_denied) == directory_options::skip_permission_denied) { + ec.clear(); + skip = true; + } + } + else { + ::closedir(_dir); + _dir = nullptr; + _current = path(); + if (errno) { + ec = detail::make_system_error(); + } + break; + } + } while (skip || std::strcmp(_entry->d_name, ".") == 0 || std::strcmp(_entry->d_name, "..") == 0); + } + } + path _base; + directory_options _options; + path _current; + DIR* _dir; + struct ::dirent* _entry; + directory_entry _dir_entry; + std::error_code _ec; +}; +#endif + +// 30.10.13.1 member functions +GHC_INLINE directory_iterator::directory_iterator() noexcept + : _impl(new impl(path(), directory_options::none)) +{ +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_iterator::directory_iterator(const path& p) + : _impl(new impl(p, directory_options::none)) +{ + if (_impl->_ec) { + throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); + } + _impl->_ec.clear(); +} + +GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options) + : _impl(new impl(p, options)) +{ + if (_impl->_ec) { + throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); + } +} +#endif + +GHC_INLINE directory_iterator::directory_iterator(const path& p, std::error_code& ec) noexcept + : _impl(new impl(p, directory_options::none)) +{ + if (_impl->_ec) { + ec = _impl->_ec; + } +} + +GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept + : _impl(new impl(p, options)) +{ + if (_impl->_ec) { + ec = _impl->_ec; + } +} + +GHC_INLINE directory_iterator::directory_iterator(const directory_iterator& rhs) + : _impl(rhs._impl) +{ +} + +GHC_INLINE directory_iterator::directory_iterator(directory_iterator&& rhs) noexcept + : _impl(std::move(rhs._impl)) +{ +} + +GHC_INLINE directory_iterator::~directory_iterator() {} + +GHC_INLINE directory_iterator& directory_iterator::operator=(const directory_iterator& rhs) +{ + _impl = rhs._impl; + return *this; +} + +GHC_INLINE directory_iterator& directory_iterator::operator=(directory_iterator&& rhs) noexcept +{ + _impl = std::move(rhs._impl); + return *this; +} + +GHC_INLINE const directory_entry& directory_iterator::operator*() const +{ + return _impl->_dir_entry; +} + +GHC_INLINE const directory_entry* directory_iterator::operator->() const +{ + return &_impl->_dir_entry; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_iterator& directory_iterator::operator++() +{ + std::error_code ec; + _impl->increment(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_current, ec); + } + return *this; +} +#endif + +GHC_INLINE directory_iterator& directory_iterator::increment(std::error_code& ec) noexcept +{ + _impl->increment(ec); + return *this; +} + +GHC_INLINE bool directory_iterator::operator==(const directory_iterator& rhs) const +{ + return _impl->_current == rhs._impl->_current; +} + +GHC_INLINE bool directory_iterator::operator!=(const directory_iterator& rhs) const +{ + return _impl->_current != rhs._impl->_current; +} + +// 30.10.13.2 directory_iterator non-member functions + +GHC_INLINE directory_iterator begin(directory_iterator iter) noexcept +{ + return iter; +} + +GHC_INLINE directory_iterator end(const directory_iterator&) noexcept +{ + return directory_iterator(); +} + +//----------------------------------------------------------------------------- +// 30.10.14 class recursive_directory_iterator + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator() noexcept + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator()); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p) + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options) + : _impl(new recursive_directory_iterator_impl(options, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, options)); +} +#endif + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept + : _impl(new recursive_directory_iterator_impl(options, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, options, ec)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, std::error_code& ec) noexcept + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, ec)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const recursive_directory_iterator& rhs) + : _impl(rhs._impl) +{ +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept + : _impl(std::move(rhs._impl)) +{ +} + +GHC_INLINE recursive_directory_iterator::~recursive_directory_iterator() {} + +// 30.10.14.1 observers +GHC_INLINE directory_options recursive_directory_iterator::options() const +{ + return _impl->_options; +} + +GHC_INLINE int recursive_directory_iterator::depth() const +{ + return static_cast(_impl->_dir_iter_stack.size() - 1); +} + +GHC_INLINE bool recursive_directory_iterator::recursion_pending() const +{ + return _impl->_recursion_pending; +} + +GHC_INLINE const directory_entry& recursive_directory_iterator::operator*() const +{ + return *(_impl->_dir_iter_stack.top()); +} + +GHC_INLINE const directory_entry* recursive_directory_iterator::operator->() const +{ + return &(*(_impl->_dir_iter_stack.top())); +} + +// 30.10.14.1 modifiers recursive_directory_iterator& +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(const recursive_directory_iterator& rhs) +{ + _impl = rhs._impl; + return *this; +} + +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(recursive_directory_iterator&& rhs) noexcept +{ + _impl = std::move(rhs._impl); + return *this; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator++() +{ + std::error_code ec; + increment(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); + } + return *this; +} +#endif + +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::increment(std::error_code& ec) noexcept +{ + auto status = (*this)->status(ec); + if (ec) return *this; + auto symlink_status = (*this)->symlink_status(ec); + if (ec) return *this; + if (recursion_pending() && is_directory(status) && (!is_symlink(symlink_status) || (options() & directory_options::follow_directory_symlink) != directory_options::none)) { + _impl->_dir_iter_stack.push(directory_iterator((*this)->path(), _impl->_options, ec)); + } + else { + _impl->_dir_iter_stack.top().increment(ec); + } + if (!ec) { + while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()) { + _impl->_dir_iter_stack.pop(); + _impl->_dir_iter_stack.top().increment(ec); + } + } + else if (!_impl->_dir_iter_stack.empty()) { + _impl->_dir_iter_stack.pop(); + } + _impl->_recursion_pending = true; + return *this; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void recursive_directory_iterator::pop() +{ + std::error_code ec; + pop(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); + } +} +#endif + +GHC_INLINE void recursive_directory_iterator::pop(std::error_code& ec) +{ + if (depth() == 0) { + *this = recursive_directory_iterator(); + } + else { + do { + _impl->_dir_iter_stack.pop(); + _impl->_dir_iter_stack.top().increment(ec); + } while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()); + } +} + +GHC_INLINE void recursive_directory_iterator::disable_recursion_pending() +{ + _impl->_recursion_pending = false; +} + +// other members as required by 27.2.3, input iterators +GHC_INLINE bool recursive_directory_iterator::operator==(const recursive_directory_iterator& rhs) const +{ + return _impl->_dir_iter_stack.top() == rhs._impl->_dir_iter_stack.top(); +} + +GHC_INLINE bool recursive_directory_iterator::operator!=(const recursive_directory_iterator& rhs) const +{ + return _impl->_dir_iter_stack.top() != rhs._impl->_dir_iter_stack.top(); +} + +// 30.10.14.2 directory_iterator non-member functions +GHC_INLINE recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept +{ + return iter; +} + +GHC_INLINE recursive_directory_iterator end(const recursive_directory_iterator&) noexcept +{ + return recursive_directory_iterator(); +} + +#endif // GHC_EXPAND_IMPL + +} // namespace filesystem +} // namespace ghc + +// cleanup some macros +#undef GHC_INLINE +#undef GHC_EXPAND_IMPL + +#endif // GHC_FILESYSTEM_H diff --git a/src/CompilerSupport/span.h b/src/CompilerSupport/span.h new file mode 100644 index 0000000..ca6f3de --- /dev/null +++ b/src/CompilerSupport/span.h @@ -0,0 +1,8 @@ +#pragma once + +#if defined(__cplusplus) && __cplusplus >= 201703L && defined(__has_include) && __has_include() + #include +#else + #include "CompilerSupport/span_implementation.hpp" +#endif + diff --git a/src/CompilerSupport/span_implementation.hpp b/src/CompilerSupport/span_implementation.hpp new file mode 100644 index 0000000..606a92a --- /dev/null +++ b/src/CompilerSupport/span_implementation.hpp @@ -0,0 +1,611 @@ +/* +This is an implementation of C++20's std::span +http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/n4820.pdf +*/ + +// Copyright Tristan Brindle 2018. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file ../../LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +#ifndef TCB_SPAN_HPP_INCLUDED +#define TCB_SPAN_HPP_INCLUDED + +#include +#include +#include +#include + +#ifndef TCB_SPAN_NO_EXCEPTIONS +// Attempt to discover whether we're being compiled with exception support +#if !(defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) +#define TCB_SPAN_NO_EXCEPTIONS +#endif +#endif + +#ifndef TCB_SPAN_NO_EXCEPTIONS +#include +#include +#endif + +// Various feature test macros + +#ifndef TCB_SPAN_NAMESPACE_NAME +#define TCB_SPAN_NAMESPACE_NAME std +#endif + +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define TCB_SPAN_HAVE_CPP17 +#endif + +#if __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +#define TCB_SPAN_HAVE_CPP14 +#endif + +namespace TCB_SPAN_NAMESPACE_NAME { + +// Establish default contract checking behavior +#if !defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION) && \ + !defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) && \ + !defined(TCB_SPAN_NO_CONTRACT_CHECKING) +#if defined(NDEBUG) || !defined(TCB_SPAN_HAVE_CPP14) +#define TCB_SPAN_NO_CONTRACT_CHECKING +#else +#define TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION +#endif +#endif + +#if defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION) +struct contract_violation_error : std::logic_error { + explicit contract_violation_error(const char* msg) : std::logic_error(msg) + {} +}; + +inline void contract_violation(const char* msg) +{ + throw contract_violation_error(msg); +} + +#elif defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) +[[noreturn]] inline void contract_violation(const char* /*unused*/) +{ + std::terminate(); +} +#endif + +#if !defined(TCB_SPAN_NO_CONTRACT_CHECKING) +#define TCB_SPAN_STRINGIFY(cond) #cond +#define TCB_SPAN_EXPECT(cond) \ + cond ? (void) 0 : contract_violation("Expected " TCB_SPAN_STRINGIFY(cond)) +#else +#define TCB_SPAN_EXPECT(cond) +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_inline_variables) +#define TCB_SPAN_INLINE_VAR inline +#else +#define TCB_SPAN_INLINE_VAR +#endif + +#if defined(TCB_SPAN_HAVE_CPP14) || \ + (defined(__cpp_constexpr) && __cpp_constexpr >= 201304) +#define TCB_SPAN_HAVE_CPP14_CONSTEXPR +#endif + +#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR) +#define TCB_SPAN_CONSTEXPR14 constexpr +#else +#define TCB_SPAN_CONSTEXPR14 +#endif + +#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR) && \ + (!defined(_MSC_VER) || _MSC_VER > 1900) +#define TCB_SPAN_CONSTEXPR_ASSIGN constexpr +#else +#define TCB_SPAN_CONSTEXPR_ASSIGN +#endif + +#if defined(TCB_SPAN_NO_CONTRACT_CHECKING) +#define TCB_SPAN_CONSTEXPR11 constexpr +#else +#define TCB_SPAN_CONSTEXPR11 TCB_SPAN_CONSTEXPR14 +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_deduction_guides) +#define TCB_SPAN_HAVE_DEDUCTION_GUIDES +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_byte) +#define TCB_SPAN_HAVE_STD_BYTE +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_array_constexpr) +#define TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC +#endif + +#if defined(TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC) +#define TCB_SPAN_ARRAY_CONSTEXPR constexpr +#else +#define TCB_SPAN_ARRAY_CONSTEXPR +#endif + +#ifdef TCB_SPAN_HAVE_STD_BYTE +using byte = std::byte; +#else +using byte = unsigned char; +#endif + +#if defined(TCB_SPAN_HAVE_CPP17) +#define TCB_SPAN_NODISCARD [[nodiscard]] +#else +#define TCB_SPAN_NODISCARD +#endif + +TCB_SPAN_INLINE_VAR constexpr std::size_t dynamic_extent = SIZE_MAX; + +template +class span; + +namespace detail { + +template +struct span_storage { + constexpr span_storage() noexcept = default; + + constexpr span_storage(E* p_ptr, std::size_t /*unused*/) noexcept + : ptr(p_ptr) + {} + + E* ptr = nullptr; + static constexpr std::size_t size = S; +}; + +template +struct span_storage { + constexpr span_storage() noexcept = default; + + constexpr span_storage(E* p_ptr, std::size_t p_size) noexcept + : ptr(p_ptr), size(p_size) + {} + + E* ptr = nullptr; + std::size_t size = 0; +}; + +// Reimplementation of C++17 std::size() and std::data() +#if defined(TCB_SPAN_HAVE_CPP17) || \ + defined(__cpp_lib_nonmember_container_access) +using std::data; +using std::size; +#else +template +constexpr auto size(const C& c) -> decltype(c.size()) +{ + return c.size(); +} + +template +constexpr std::size_t size(const T (&)[N]) noexcept +{ + return N; +} + +template +constexpr auto data(C& c) -> decltype(c.data()) +{ + return c.data(); +} + +template +constexpr auto data(const C& c) -> decltype(c.data()) +{ + return c.data(); +} + +template +constexpr T* data(T (&array)[N]) noexcept +{ + return array; +} + +template +constexpr const E* data(std::initializer_list il) noexcept +{ + return il.begin(); +} +#endif // TCB_SPAN_HAVE_CPP17 + +#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_void_t) +using std::void_t; +#else +template +using void_t = void; +#endif + +template +using uncvref_t = + typename std::remove_cv::type>::type; + +template +struct is_span : std::false_type {}; + +template +struct is_span> : std::true_type {}; + +template +struct is_std_array : std::false_type {}; + +template +struct is_std_array> : std::true_type {}; + +template +struct has_size_and_data : std::false_type {}; + +template +struct has_size_and_data())), + decltype(detail::data(std::declval()))>> + : std::true_type {}; + +template > +struct is_container { + static constexpr bool value = + !is_span::value && !is_std_array::value && + !std::is_array::value && has_size_and_data::value; +}; + +template +using remove_pointer_t = typename std::remove_pointer::type; + +template +struct is_container_element_type_compatible : std::false_type {}; + +template +struct is_container_element_type_compatible< + T, E, + typename std::enable_if< + !std::is_same()))>::type, + void>::value>::type> + : std::is_convertible< + remove_pointer_t()))> (*)[], + E (*)[]> {}; + +template +struct is_complete : std::false_type {}; + +template +struct is_complete : std::true_type {}; + +} // namespace detail + +template +class span { + static_assert(std::is_object::value, + "A span's ElementType must be an object type (not a " + "reference type or void)"); + static_assert(detail::is_complete::value, + "A span's ElementType must be a complete type (not a forward " + "declaration)"); + static_assert(!std::is_abstract::value, + "A span's ElementType cannot be an abstract class type"); + + using storage_type = detail::span_storage; + +public: + // constants and types + using element_type = ElementType; + using value_type = typename std::remove_cv::type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = element_type*; + using const_pointer = const element_type*; + using reference = element_type&; + using const_reference = const element_type&; + using iterator = pointer; + using reverse_iterator = std::reverse_iterator; + + static constexpr size_type extent = Extent; + + // [span.cons], span constructors, copy, assignment, and destructor + template < + std::size_t E = Extent, + typename std::enable_if<(E == dynamic_extent || E <= 0), int>::type = 0> + constexpr span() noexcept + {} + + TCB_SPAN_CONSTEXPR11 span(pointer ptr, size_type count) + : storage_(ptr, count) + { + TCB_SPAN_EXPECT(extent == dynamic_extent || count == extent); + } + + TCB_SPAN_CONSTEXPR11 span(pointer first_elem, pointer last_elem) + : storage_(first_elem, last_elem - first_elem) + { + TCB_SPAN_EXPECT(extent == dynamic_extent || + last_elem - first_elem == + static_cast(extent)); + } + + template ::value, + int>::type = 0> + constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N) + {} + + template &, ElementType>::value, + int>::type = 0> + TCB_SPAN_ARRAY_CONSTEXPR span(std::array& arr) noexcept + : storage_(arr.data(), N) + {} + + template &, ElementType>::value, + int>::type = 0> + TCB_SPAN_ARRAY_CONSTEXPR span(const std::array& arr) noexcept + : storage_(arr.data(), N) + {} + + template < + typename Container, std::size_t E = Extent, + typename std::enable_if< + E == dynamic_extent && detail::is_container::value && + detail::is_container_element_type_compatible< + Container&, ElementType>::value, + int>::type = 0> + constexpr span(Container& cont) + : storage_(detail::data(cont), detail::size(cont)) + {} + + template < + typename Container, std::size_t E = Extent, + typename std::enable_if< + E == dynamic_extent && detail::is_container::value && + detail::is_container_element_type_compatible< + const Container&, ElementType>::value, + int>::type = 0> + constexpr span(const Container& cont) + : storage_(detail::data(cont), detail::size(cont)) + {} + + constexpr span(const span& other) noexcept = default; + + template ::value, + int>::type = 0> + constexpr span(const span& other) noexcept + : storage_(other.data(), other.size()) + {} + + ~span() noexcept = default; + + TCB_SPAN_CONSTEXPR_ASSIGN span& + operator=(const span& other) noexcept = default; + + // [span.sub], span subviews + template + TCB_SPAN_CONSTEXPR11 span first() const + { + TCB_SPAN_EXPECT(Count <= size()); + return {data(), Count}; + } + + template + TCB_SPAN_CONSTEXPR11 span last() const + { + TCB_SPAN_EXPECT(Count <= size()); + return {data() + (size() - Count), Count}; + } + + template + using subspan_return_t = + span; + + template + TCB_SPAN_CONSTEXPR11 subspan_return_t subspan() const + { + TCB_SPAN_EXPECT(Offset <= size() && + (Count == dynamic_extent || Offset + Count <= size())); + return {data() + Offset, + Count != dynamic_extent ? Count : size() - Offset}; + } + + TCB_SPAN_CONSTEXPR11 span + first(size_type count) const + { + TCB_SPAN_EXPECT(count <= size()); + return {data(), count}; + } + + TCB_SPAN_CONSTEXPR11 span + last(size_type count) const + { + TCB_SPAN_EXPECT(count <= size()); + return {data() + (size() - count), count}; + } + + TCB_SPAN_CONSTEXPR11 span + subspan(size_type offset, size_type count = dynamic_extent) const + { + TCB_SPAN_EXPECT(offset <= size() && + (count == dynamic_extent || offset + count <= size())); + return {data() + offset, + count == dynamic_extent ? size() - offset : count}; + } + + // [span.obs], span observers + constexpr size_type size() const noexcept { return storage_.size; } + + constexpr size_type size_bytes() const noexcept + { + return size() * sizeof(element_type); + } + + TCB_SPAN_NODISCARD constexpr bool empty() const noexcept + { + return size() == 0; + } + + // [span.elem], span element access + TCB_SPAN_CONSTEXPR11 reference operator[](size_type idx) const + { + TCB_SPAN_EXPECT(idx < size()); + return *(data() + idx); + } + + TCB_SPAN_CONSTEXPR11 reference front() const + { + TCB_SPAN_EXPECT(!empty()); + return *data(); + } + + TCB_SPAN_CONSTEXPR11 reference back() const + { + TCB_SPAN_EXPECT(!empty()); + return *(data() + (size() - 1)); + } + + constexpr pointer data() const noexcept { return storage_.ptr; } + + // [span.iterators], span iterator support + constexpr iterator begin() const noexcept { return data(); } + + constexpr iterator end() const noexcept { return data() + size(); } + + TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rbegin() const noexcept + { + return reverse_iterator(end()); + } + + TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rend() const noexcept + { + return reverse_iterator(begin()); + } + +private: + storage_type storage_{}; +}; + +#ifdef TCB_SPAN_HAVE_DEDUCTION_GUIDES + +/* Deduction Guides */ +template +span(T (&)[N])->span; + +template +span(std::array&)->span; + +template +span(const std::array&)->span; + +template +span(Container&)->span; + +template +span(const Container&)->span; + +#endif // TCB_HAVE_DEDUCTION_GUIDES + +template +constexpr span +make_span(span s) noexcept +{ + return s; +} + +template +constexpr span make_span(T (&arr)[N]) noexcept +{ + return {arr}; +} + +template +TCB_SPAN_ARRAY_CONSTEXPR span make_span(std::array& arr) noexcept +{ + return {arr}; +} + +template +TCB_SPAN_ARRAY_CONSTEXPR span +make_span(const std::array& arr) noexcept +{ + return {arr}; +} + +template +constexpr span make_span(Container& cont) +{ + return {cont}; +} + +template +constexpr span +make_span(const Container& cont) +{ + return {cont}; +} + +template +span +as_bytes(span s) noexcept +{ + return {reinterpret_cast(s.data()), s.size_bytes()}; +} + +template < + class ElementType, size_t Extent, + typename std::enable_if::value, int>::type = 0> +span +as_writable_bytes(span s) noexcept +{ + return {reinterpret_cast(s.data()), s.size_bytes()}; +} + +template +constexpr auto get(span s) -> decltype(s[N]) +{ + return s[N]; +} + +} // namespace TCB_SPAN_NAMESPACE_NAME + +namespace std { + +template +class tuple_size> + : public integral_constant {}; + +template +class tuple_size>; // not defined + +template +class tuple_element> { +public: + static_assert(Extent != TCB_SPAN_NAMESPACE_NAME::dynamic_extent && + I < Extent, + ""); + using type = ElementType; +}; + +} // end namespace std + +#endif // TCB_SPAN_HPP_INCLUDED diff --git a/src/Files/Files.cpp b/src/Files/Files.cpp new file mode 100644 index 0000000..65226c3 --- /dev/null +++ b/src/Files/Files.cpp @@ -0,0 +1,337 @@ +#include "Pomme.h" +#include "Utilities/BigEndianIStream.h" +#include "Utilities/GrowablePool.h" +#include "Utilities/memstream.h" +#include "PommeFiles.h" +#include "Files/Volume.h" +#include "Files/HostVolume.h" + +#include +#include +#include "CompilerSupport/filesystem.h" + +#if _WIN32 + #include "Platform/Windows/PommeWindows.h" +#endif + +using namespace Pomme; +using namespace Pomme::Files; + +#define LOG POMME_GENLOG(POMME_DEBUG_FILES, "FILE") + +constexpr int MAX_VOLUMES = 32767; // vRefNum is a signed short + +//----------------------------------------------------------------------------- +// State + +static Pomme::GrowablePool, SInt16, 0x7FFF> openFiles; + +static std::vector> volumes; + +//----------------------------------------------------------------------------- +// Utilities + +bool Pomme::Files::IsRefNumLegal(short refNum) +{ + return openFiles.IsAllocated(refNum); +} + +std::iostream& Pomme::Files::GetStream(short refNum) +{ + if (!IsRefNumLegal(refNum)) + { + throw std::runtime_error("illegal refNum"); + } + return openFiles[refNum]->GetStream(); +} + +void Pomme::Files::CloseStream(short refNum) +{ + if (!IsRefNumLegal(refNum)) + { + throw std::runtime_error("illegal refNum"); + } + openFiles[refNum].reset(nullptr); + openFiles.Dispose(refNum); + LOG << "Stream #" << refNum << " closed\n"; +} + +bool Pomme::Files::IsStreamOpen(short refNum) +{ + if (!IsRefNumLegal(refNum)) + { + throw std::runtime_error("illegal refNum"); + } + return openFiles[refNum].get() != nullptr; +// return openFiles[refNum].stream.is_open(); +} + +bool Pomme::Files::IsStreamPermissionAllowed(short refNum, char perm) +{ + if (!IsRefNumLegal(refNum)) + { + throw std::runtime_error("illegal refNum"); + } + return (perm & openFiles[refNum]->permission) == perm; +} + +//----------------------------------------------------------------------------- +// Init + +void Pomme::Files::Init() +{ + auto hostVolume = std::make_unique(0); + volumes.push_back(std::move(hostVolume)); + + short systemRefNum = openFiles.Alloc(); + if (systemRefNum != 0) + { + throw std::logic_error("expecting 0 for system refnum"); + } +} + +//----------------------------------------------------------------------------- +// Implementation + +bool IsVolumeLegal(short vRefNum) +{ + return vRefNum >= 0 && (unsigned short) vRefNum < volumes.size(); +} + +OSErr FSMakeFSSpec(short vRefNum, long dirID, const char* cstrFileName, FSSpec* spec) +{ + if (!IsVolumeLegal(vRefNum)) + return nsvErr; + + return volumes.at(vRefNum)->FSMakeFSSpec(dirID, cstrFileName, spec); +} + +static OSErr OpenFork(const FSSpec* spec, ForkType forkType, char permission, short* refNum) +{ + if (openFiles.IsFull()) + return tmfoErr; + if (!IsVolumeLegal(spec->vRefNum)) + return nsvErr; + short newRefNum = openFiles.Alloc(); + auto& handlePtr = openFiles[newRefNum]; + OSErr rc = volumes.at(spec->vRefNum)->OpenFork(spec, forkType, permission, handlePtr); + if (rc != noErr) + { + openFiles.Dispose(newRefNum); + newRefNum = -1; + LOG << "Failed to open " << spec->cName << "\n"; + } + else + { + LOG << "Stream #" << newRefNum << " opened: " << spec->cName << ", " << (forkType == DataFork ? "data" : "rsrc") << "\n"; + } + if (refNum) + { + *refNum = newRefNum; + } + return rc; +} + +OSErr FSpOpenDF(const FSSpec* spec, char permission, short* refNum) +{ + return OpenFork(spec, ForkType::DataFork, permission, refNum); +} + +OSErr FSpOpenRF(const FSSpec* spec, char permission, short* refNum) +{ + return OpenFork(spec, ForkType::ResourceFork, permission, refNum); +} + +OSErr FindFolder(short vRefNum, OSType folderType, Boolean createFolder, short* foundVRefNum, long* foundDirID) +{ + if (vRefNum != kOnSystemDisk) + { + throw std::runtime_error("FindFolder only supports kOnSystemDisk"); + } + + fs::path path; + + switch (folderType) + { + case kPreferencesFolderType: + { +#ifdef _WIN32 + path = Pomme::Platform::Windows::GetPreferencesFolder(); +#elif defined(__APPLE__) + const char *home = getenv("HOME"); + if (!home) { + return fnfErr; + } + path = fs::path(home) / "Library" / "Preferences"; +#else + const char* home = getenv("XDG_CONFIG_HOME"); + if (home) + { + path = std::filesystem::path(home); + } + else + { + home = getenv("HOME"); + if (!home) + { + return fnfErr; + } + path = std::filesystem::path(home) / ".config"; + } +#endif + break; + } + + default: + TODO2("folder type '" << Pomme::FourCCString(folderType) << "' isn't supported yet"); + return fnfErr; + } + + path = path.lexically_normal(); + + bool exists = fs::exists(path); + + if (exists && !fs::is_directory(path)) + { + return dupFNErr; + } + if (!exists && createFolder) + { + fs::create_directories(path); + } + + *foundVRefNum = 0;//GetVolumeID(path); + *foundDirID = dynamic_cast(volumes.at(0).get())->GetDirectoryID(path); + return noErr; +} + +OSErr DirCreate(short vRefNum, long parentDirID, const char* cstrDirectoryName, long* createdDirID) +{ + return IsVolumeLegal(vRefNum) + ? volumes.at(vRefNum)->DirCreate(parentDirID, cstrDirectoryName, createdDirID) + : (OSErr)nsvErr; +} + +OSErr FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag) +{ + return IsVolumeLegal(spec->vRefNum) + ? volumes.at(spec->vRefNum)->FSpCreate(spec, creator, fileType, scriptTag) + : (OSErr)nsvErr; +} + +OSErr FSpDelete(const FSSpec* spec) +{ + return IsVolumeLegal(spec->vRefNum) + ? volumes.at(spec->vRefNum)->FSpDelete(spec) + : (OSErr)nsvErr; +} + +OSErr ResolveAlias(const FSSpec* spec, AliasHandle alias, FSSpec* target, Boolean* wasChanged) +{ + *wasChanged = false; + + int aliasSize = GetHandleSize(alias); + + // the target FN is at offset 50, and the target FN is a Str63, so 50+64 + if (aliasSize < 50 + 64) + { + std::cerr << "unexpected size of alias: " << aliasSize << "\n"; + return unimpErr; + } + + memstream istr(*alias, GetHandleSize(alias)); + Pomme::BigEndianIStream f(istr); + + f.Skip(4); // application signature + + if (f.Read() != aliasSize) + { + std::cerr << "unexpected size field in alias\n"; + return unimpErr; + } + + if (f.Read() != 2) + { + std::cerr << "unexpected alias version number\n"; + return unimpErr; + } + + auto kind = f.Read(); + if (kind > 2) + { + std::cerr << "unsupported alias kind " << kind << "\n"; + return unimpErr; + } + + f.Skip(28); // volume name pascal string + f.Skip(4); // volume creation date + f.Skip(2); // volume signature (RW, BD, H+) + f.Skip(2); // drive type (0=HD, 1=network, 2=400K FD, 3=800K FD, 4=1.4M FD, 5=misc.ejectable) + f.Skip(4); // parent directory ID + + auto targetFilename = f.ReadPascalString_FixedLengthRecord(63); + + return FSMakeFSSpec(spec->vRefNum, spec->parID, targetFilename.c_str(), target); +} + +OSErr FSRead(short refNum, long* count, Ptr buffPtr) +{ + if (*count < 0) return paramErr; + if (!IsRefNumLegal(refNum)) return rfNumErr; + if (!IsStreamOpen(refNum)) return fnOpnErr; + if (!IsStreamPermissionAllowed(refNum, fsRdPerm)) return ioErr; + + auto& f = GetStream(refNum); + f.read(buffPtr, *count); + *count = (long) f.gcount(); + if (f.eof()) return eofErr; + + return noErr; +} + +OSErr FSWrite(short refNum, long* count, Ptr buffPtr) +{ + if (*count < 0) return paramErr; + if (!IsRefNumLegal(refNum)) return rfNumErr; + if (!IsStreamOpen(refNum)) return fnOpnErr; + if (!IsStreamPermissionAllowed(refNum, fsWrPerm)) return wrPermErr; + + auto& f = GetStream(refNum); + f.write(buffPtr, *count); + + return noErr; +} + +OSErr FSClose(short refNum) +{ + if (!IsRefNumLegal(refNum)) + return rfNumErr; + if (!IsStreamOpen(refNum)) + return fnOpnErr; + CloseStream(refNum); + return noErr; +} + +OSErr GetEOF(short refNum, long* logEOF) +{ + if (!IsRefNumLegal(refNum)) return rfNumErr; + if (!IsStreamOpen(refNum)) return fnOpnErr; + + auto& f = GetStream(refNum); + StreamPosGuard guard(f); + f.seekg(0, std::ios::end); + *logEOF = (long) f.tellg(); + + return noErr; +} + +OSErr SetEOF(short refNum, long logEOF) +{ + TODO(); + return unimpErr; +} + +FSSpec Pomme::Files::HostPathToFSSpec(const fs::path& fullPath) +{ + return dynamic_cast(volumes[0].get())->ToFSSpec(fullPath); +} \ No newline at end of file diff --git a/src/Files/HostVolume.cpp b/src/Files/HostVolume.cpp new file mode 100644 index 0000000..82caab5 --- /dev/null +++ b/src/Files/HostVolume.cpp @@ -0,0 +1,343 @@ +#include "PommeEnums.h" +#include "PommeDebug.h" +#include "PommeFiles.h" +#include "Files/HostVolume.h" +#include "Utilities/BigEndianIStream.h" +#include "Utilities/StringUtils.h" + +#include +#include + +#define LOG POMME_GENLOG(POMME_DEBUG_FILES, "HOST") + +using namespace Pomme; +using namespace Pomme::Files; + +struct HostForkHandle : public ForkHandle +{ + std::fstream backingStream; + +public: + HostForkHandle(ForkType theForkType, char perm, fs::path& path) + : ForkHandle(theForkType, perm) + { + std::ios::openmode openmode = std::ios::binary; + if (permission & fsWrPerm) openmode |= std::ios::out; + if (permission & fsRdPerm) openmode |= std::ios::in; + + backingStream = std::fstream(path, openmode); + } + + virtual ~HostForkHandle() = default; + + virtual std::iostream& GetStream() override + { + return backingStream; + } +}; + + +HostVolume::HostVolume(short vRefNum) + : Volume(vRefNum) +{ + // default directory (ID 0) + directories.push_back(fs::current_path()); +} + +//----------------------------------------------------------------------------- +// Public utilities + +long HostVolume::GetDirectoryID(const fs::path& dirPath) +{ + if (fs::exists(dirPath) && !fs::is_directory(dirPath)) + { + std::cerr << "Warning: GetDirectoryID should only be used on directories! " << dirPath << "\n"; + } + + auto it = std::find(directories.begin(), directories.end(), dirPath); + if (it != directories.end()) + { + return std::distance(directories.begin(), it); + } + + directories.emplace_back(dirPath); + LOG << "directory " << directories.size() - 1 << ": " << dirPath << "\n"; + return (long) directories.size() - 1; +} + +//----------------------------------------------------------------------------- +// Internal utilities + +fs::path HostVolume::ToPath(long parID, const std::string& name) +{ + fs::path path = directories[parID] / AsU8(name); + return path.lexically_normal(); +} + +FSSpec HostVolume::ToFSSpec(const fs::path& fullPath) +{ + auto parentPath = fullPath; + parentPath.remove_filename(); + + FSSpec spec; + spec.vRefNum = volumeID; + spec.parID = GetDirectoryID(parentPath); + snprintf(spec.cName, 256, "%s", (const char*) fullPath.filename().u8string().c_str()); + return spec; +} + +static void ADFJumpToResourceFork(std::istream& stream) +{ + auto f = Pomme::BigEndianIStream(stream); + + if (0x0005160700020000ULL != f.Read()) + { + throw std::runtime_error("No ADF magic"); + } + f.Skip(16); + auto numOfEntries = f.Read(); + + for (int i = 0; i < numOfEntries; i++) + { + auto entryID = f.Read(); + auto offset = f.Read(); + f.Skip(4); // length + if (entryID == 2) + { + // Found entry ID 2 (resource fork) + f.Goto(offset); + return; + } + } + + throw std::runtime_error("Didn't find entry ID=2 in ADF"); +} + +OSErr HostVolume::OpenFork(const FSSpec* spec, ForkType forkType, char permission, std::unique_ptr& handle) +{ + if (permission == fsCurPerm) + { + TODO2("fsCurPerm not implemented yet"); + return unimpErr; + } + + if ((permission & fsWrPerm) && forkType != ForkType::DataFork) + { + TODO2("opening resource fork for writing isn't implemented yet"); + return unimpErr; + } + + auto path = ToPath(spec->parID, spec->cName); + + if (forkType == DataFork) + { + if (!fs::is_regular_file(path)) + { + return fnfErr; + } + handle = std::make_unique(DataFork, permission, path); + return noErr; + } + else + { + // We want to open a resource fork on the host volume. + // It is likely stored under one of the following names: ._NAME, NAME.rsrc, or NAME/..namedfork/rsrc + + auto specName = path.filename().u8string(); + + struct + { + u8string filename; + bool isAppleDoubleFile; + } candidates[] = + { + // "._NAME": ADF contained in zips created by macOS's built-in archiver + { u8"._" + specName, true }, + + // "NAME.rsrc": ADF extracted from StuffIt/CompactPro archives by unar + { specName + u8".rsrc", true }, + +#if __APPLE__ + // "NAME/..namedfork/rsrc": macOS-specific way to access true resource forks (not ADF) + { specName + u8"/..namedfork/rsrc", false }, +#endif + }; + + path.remove_filename(); + + for (auto c : candidates) + { + auto candidatePath = path / c.filename; + if (!fs::is_regular_file(candidatePath)) + { + continue; + } + path = candidatePath; + handle = std::make_unique(ResourceFork, permission, path); + if (c.isAppleDoubleFile) + { + ADFJumpToResourceFork(handle->GetStream()); + } + return noErr; + } + } + + return fnfErr; +} + +static bool CaseInsensitiveAppendToPath( + fs::path& path, + const std::string& element, + bool skipFiles = false) +{ + fs::path naiveConcat = path / AsU8(element); + + if (!fs::exists(path)) + { + path = naiveConcat; + return false; + } + + if (fs::exists(naiveConcat)) + { + path = naiveConcat; + return true; + } + + const auto ELEMENT = UppercaseCopy(element); + + for (const auto& candidate : fs::directory_iterator(path)) + { + if (skipFiles && !candidate.is_directory()) + { + continue; + } + + auto f = FromU8(candidate.path().filename().u8string()); + + // It might be an AppleDouble resource fork ("._file" or "file.rsrc") + if (candidate.is_regular_file()) + { + if (f.starts_with("._")) + { + f = f.substr(2); + } + else if (f.ends_with(".rsrc")) + { + f = f.substr(0, f.length() - 5); + } + } + + if (ELEMENT == UppercaseCopy(f)) + { + path /= AsU8(f); + return true; + } + } + + path = naiveConcat; + return false; +} + +//----------------------------------------------------------------------------- +// Implementation + +OSErr HostVolume::FSMakeFSSpec(long dirID, const std::string& fileName, FSSpec* spec) +{ + if (dirID < 0 || (unsigned long) dirID >= directories.size()) + { + throw std::runtime_error("HostVolume::FSMakeFSSpec: directory ID not registered."); + } + + auto path = directories[dirID]; + auto suffix = fileName; + + // Case-insensitive sanitization + bool exists = fs::exists(path); + std::string::size_type begin = (suffix.at(0) == ':') ? 1 : 0; + + // Iterate on path elements between colons + while (begin < suffix.length()) + { + auto end = suffix.find(":", begin); + + bool isLeaf = end == std::string::npos; // no ':' found => end of path + if (isLeaf) end = suffix.length(); + + if (end == begin) // "::" => parent directory + { + path = path.parent_path(); + } + else + { + auto element = suffix.substr(begin, end - begin); + exists = CaseInsensitiveAppendToPath(path, element, !isLeaf); + } + + // +1: jump over current colon + begin = end + 1; + } + + path = path.lexically_normal(); + + LOG << path << "\n"; + + *spec = ToFSSpec(path); + + return exists ? noErr : fnfErr; +} + +OSErr HostVolume::DirCreate(long parentDirID, const std::string& directoryName, long* createdDirID) +{ + const auto path = ToPath(parentDirID, directoryName); + + if (fs::exists(path)) + { + if (fs::is_directory(path)) + { + LOG << __func__ << ": directory already exists: " << path << "\n"; + return noErr; + } + else + { + std::cerr << __func__ << ": a file with the same name already exists: " << path << "\n"; + return bdNamErr; + } + } + + try + { + fs::create_directory(path); + } + catch (const fs::filesystem_error& e) + { + std::cerr << __func__ << " threw " << e.what() << "\n"; + return ioErr; + } + + if (createdDirID) + { + *createdDirID = GetDirectoryID(path); + } + + LOG << __func__ << ": created " << path << "\n"; + return noErr; +} + +OSErr HostVolume::FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag) +{ + std::ofstream df(ToPath(spec->parID, spec->cName)); + df.close(); + // TODO: we could write an AppleDouble file to save the creator/filetype. + return noErr; +} + +OSErr HostVolume::FSpDelete(const FSSpec* spec) +{ + auto path = ToPath(spec->parID, spec->cName); + + if (fs::remove(path)) + return noErr; + else + return fnfErr; +} diff --git a/src/Files/HostVolume.h b/src/Files/HostVolume.h new file mode 100644 index 0000000..74cf83c --- /dev/null +++ b/src/Files/HostVolume.h @@ -0,0 +1,44 @@ +#pragma once + +#include "Files/Volume.h" +#include "CompilerSupport/filesystem.h" +#include + +namespace Pomme::Files +{ + /** + * Volume implementation that lets the Mac app access files + * on the host system's filesystem. + */ + class HostVolume : public Volume + { + std::vector directories; + + fs::path ToPath(long parID, const std::string& name); + + public: + explicit HostVolume(short vRefNum); + + virtual ~HostVolume() = default; + + //----------------------------------------------------------------------------- + // Utilities + + long GetDirectoryID(const fs::path& dirPath); + + FSSpec ToFSSpec(const fs::path& fullPath); + + //----------------------------------------------------------------------------- + // Toolbox API Implementation + + OSErr FSMakeFSSpec(long dirID, const std::string& fileName, FSSpec* spec) override; + + OSErr OpenFork(const FSSpec* spec, ForkType forkType, char permission, std::unique_ptr& stream) override; + + OSErr FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag) override; + + OSErr FSpDelete(const FSSpec* spec) override; + + OSErr DirCreate(long parentDirID, const std::string& directoryName, long* createdDirID) override; + }; +} diff --git a/src/Files/Resources.cpp b/src/Files/Resources.cpp new file mode 100644 index 0000000..7b7fa1c --- /dev/null +++ b/src/Files/Resources.cpp @@ -0,0 +1,304 @@ +#include "Pomme.h" +#include "PommeFiles.h" +#include "Utilities/BigEndianIStream.h" + +#include +#include +#include + +#define LOG POMME_GENLOG(POMME_DEBUG_RESOURCES, "RSRC") + +using namespace Pomme; +using namespace Pomme::Files; + +//----------------------------------------------------------------------------- +// State + +static OSErr lastResError = noErr; + +static std::vector rezSearchStack; + +static int rezSearchStackIndex = 0; + +//----------------------------------------------------------------------------- +// Internal + +static void ResourceAssert(bool condition, const char* message) +{ + if (!condition) + { + throw std::runtime_error(message); + } +} + +static ResourceFork& GetCurRF() +{ + return rezSearchStack[rezSearchStackIndex]; +} + +#if 0 +static void PrintStack(const char* msg) { + LOG << "------ RESOURCE SEARCH STACK " << msg << " -------\n"; + for (int i = int(rezSearchStack.size() - 1); i >= 0; i--) { + LOG << (rezSearchStackIndex == i? " =====> " : " ") + << " StackPos=" << i << " " + << " RefNum=" << rezSearchStack[i].fileRefNum << " " +// << Pomme::Files::GetHostFilename(rezSearchStack[i].fileRefNum) + << "\n"; + } + LOG << "------------------------------------\n"; +} + +static void DumpResource(ResourceMetadata& meta) +{ + Handle handle = NewHandle(meta.size); + auto& fork = Pomme::Files::GetStream(meta.forkRefNum); + fork.seekg(meta.dataOffset, std::ios::beg); + fork.read(*handle, meta.size); + + std::stringstream fn; + fn << "rezdump/" << meta.id << "_" << meta.name << "." << Pomme::FourCCString(meta.type, '_'); + std::ofstream dump(fn.str(), std::ofstream::binary); + dump.write(*handle, meta.size); + dump.close(); + std::cout << "wrote " << fn.str() << "\n"; + + DisposeHandle(handle); +} +#endif + +//----------------------------------------------------------------------------- +// Resource file management + +OSErr ResError(void) +{ + return lastResError; +} + +short FSpOpenResFile(const FSSpec* spec, char permission) +{ + short slot; + + lastResError = FSpOpenRF(spec, permission, &slot); + + if (noErr != lastResError) + { + return -1; + } + + auto f = Pomme::BigEndianIStream(Pomme::Files::GetStream(slot)); + auto resForkOff = f.Tell(); + + // ---------------- + // Load resource fork + + rezSearchStack.emplace_back(); + rezSearchStackIndex = int(rezSearchStack.size() - 1); + GetCurRF().fileRefNum = slot; + GetCurRF().resourceMap.clear(); + + // ------------------- + // Resource Header + UInt32 dataSectionOff = f.Read() + resForkOff; + UInt32 mapSectionOff = f.Read() + resForkOff; + f.Skip(4); // UInt32 dataSectionLen + f.Skip(4); // UInt32 mapSectionLen + f.Skip(112 + 128); // system- (112) and app- (128) reserved data + + ResourceAssert(f.Tell() == dataSectionOff, "FSpOpenResFile: Unexpected data offset"); + + f.Goto(mapSectionOff); + + // map header + f.Skip(16 + 4 + 2); // junk + f.Skip(2); // UInt16 fileAttr + UInt32 typeListOff = f.Read() + mapSectionOff; + UInt32 resNameListOff = f.Read() + mapSectionOff; + + // all resource types + int nResTypes = 1 + f.Read(); + for (int i = 0; i < nResTypes; i++) + { + OSType resType = f.Read(); + int resCount = f.Read() + 1; + UInt32 resRefListOff = f.Read() + typeListOff; + + // The guard will rewind the file cursor to the pos in the next iteration + auto guard1 = f.GuardPos(); + + f.Goto(resRefListOff); + + for (int j = 0; j < resCount; j++) + { + SInt16 resID = f.Read(); + UInt16 resNameRelativeOff = f.Read(); + UInt32 resPackedAttr = f.Read(); + f.Skip(4); // junk + + // The guard will rewind the file cursor to the pos in the next iteration + auto guard2 = f.GuardPos(); + + // unpack attributes + Byte resFlags = (resPackedAttr & 0xFF000000) >> 24; + UInt32 resDataOff = (resPackedAttr & 0x00FFFFFF) + dataSectionOff; + + // Check compressed flag + ResourceAssert(!(resFlags & 1), "FSpOpenResFile: Compressed resources not supported yet"); + + // Fetch name + std::string name; + if (resNameRelativeOff != 0xFFFF) + { + f.Goto(resNameListOff + resNameRelativeOff); + name = f.ReadPascalString(); + } + + // Fetch size + f.Goto(resDataOff); + SInt32 size = f.Read(); + + ResourceMetadata resMetadata; + resMetadata.forkRefNum = slot; + resMetadata.type = resType; + resMetadata.id = resID; + resMetadata.flags = resFlags; + resMetadata.dataOffset = resDataOff + 4; + resMetadata.size = size; + resMetadata.name = name; + GetCurRF().resourceMap[resType][resID] = resMetadata; + } + } + + //PrintStack(__func__); + + return slot; +} + +void UseResFile(short refNum) +{ + // See MoreMacintoshToolbox:1-69 + + lastResError = unimpErr; + + ResourceAssert(refNum != 0, "UseResFile: Using the System file's resource fork is not implemented."); + ResourceAssert(refNum >= 0, "UseResFile: Illegal refNum"); + ResourceAssert(IsStreamOpen(refNum), "UseResFile: Resource stream not open"); + + for (size_t i = 0; i < rezSearchStack.size(); i++) + { + if (rezSearchStack[i].fileRefNum == refNum) + { + lastResError = noErr; + rezSearchStackIndex = i; + return; + } + } + + std::cerr << "no RF open with refNum " << rfNumErr << "\n"; + lastResError = rfNumErr; +} + +short CurResFile() +{ + return GetCurRF().fileRefNum; +} + +void CloseResFile(short refNum) +{ + ResourceAssert(refNum != 0, "CloseResFile: Closing the System file's resource fork is not implemented."); + ResourceAssert(refNum >= 0, "CloseResFile: Illegal refNum"); + ResourceAssert(IsStreamOpen(refNum), "CloseResFile: Resource stream not open"); + + //UpdateResFile(refNum); // MMT:1-110 + Pomme::Files::CloseStream(refNum); + + auto it = rezSearchStack.begin(); + while (it != rezSearchStack.end()) + { + if (it->fileRefNum == refNum) + it = rezSearchStack.erase(it); + else + it++; + } + + rezSearchStackIndex = std::min(rezSearchStackIndex, (int) rezSearchStack.size() - 1); +} + +short Count1Resources(ResType theType) +{ + lastResError = noErr; + + try + { + return (short) GetCurRF().resourceMap.at(theType).size(); + } + catch (std::out_of_range&) + { + return 0; + } +} + +Handle GetResource(ResType theType, short theID) +{ + lastResError = noErr; + + for (int i = rezSearchStackIndex; i >= 0; i--) + { + const auto& fork = rezSearchStack[i]; + + if (fork.resourceMap.end() == fork.resourceMap.find(theType)) + continue; + + auto& resourcesOfType = fork.resourceMap.at(theType); + if (resourcesOfType.end() == resourcesOfType.find(theID)) + continue; + + const auto& meta = fork.resourceMap.at(theType).at(theID); + auto& forkStream = Pomme::Files::GetStream(rezSearchStack[i].fileRefNum); + Handle handle = NewHandle(meta.size); + forkStream.seekg(meta.dataOffset, std::ios::beg); + forkStream.read(*handle, meta.size); + return handle; + } + + lastResError = resNotFound; + return nil; +} + +void ReleaseResource(Handle theResource) +{ + DisposeHandle(theResource); +} + +void RemoveResource(Handle theResource) +{ + DisposeHandle(theResource); + TODO(); +} + +void AddResource(Handle theData, ResType theType, short theID, const char* name) +{ + TODO(); +} + +void WriteResource(Handle theResource) +{ + TODO(); +} + +void DetachResource(Handle theResource) +{ + lastResError = noErr; + ONCE(TODOMINOR()); +} + +long GetResourceSizeOnDisk(Handle theResource) +{ + TODO(); + return -1; +} + +long SizeResource(Handle theResource) +{ + return GetResourceSizeOnDisk(theResource); +} diff --git a/src/Files/Volume.h b/src/Files/Volume.h new file mode 100644 index 0000000..b4a6885 --- /dev/null +++ b/src/Files/Volume.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +namespace Pomme::Files +{ + enum ForkType + { + DataFork, + ResourceFork + }; + + struct ForkHandle + { + public: + ForkType forkType; + char permission; + + protected: + ForkHandle(ForkType _forkType, char _permission) + : forkType(_forkType) + , permission(_permission) + {} + + public: + virtual std::iostream& GetStream() = 0; + + virtual ~ForkHandle() = default; + }; + + /** + * Base class for volumes through which the Mac app is given access to files. + */ + class Volume + { + protected: + short volumeID; + + public: + Volume(short vid) + : volumeID(vid) + {} + + virtual ~Volume() = default; + + //----------------------------------------------------------------------------- + // Toolbox API Implementation + + virtual OSErr FSMakeFSSpec(long dirID, const std::string& suffix, FSSpec* spec) = 0; + + virtual OSErr OpenFork(const FSSpec* spec, ForkType forkType, char permission, std::unique_ptr& handle) = 0; + + virtual OSErr FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag) = 0; + + virtual OSErr FSpDelete(const FSSpec* spec) = 0; + + virtual OSErr DirCreate(long parentDirID, const std::string& directoryName, long* createdDirID) = 0; + }; +} \ No newline at end of file diff --git a/src/Graphics/ARGBPixmap.cpp b/src/Graphics/ARGBPixmap.cpp new file mode 100644 index 0000000..fc43313 --- /dev/null +++ b/src/Graphics/ARGBPixmap.cpp @@ -0,0 +1,73 @@ +#include "PommeGraphics.h" +#include + +using namespace Pomme::Graphics; + +//----------------------------------------------------------------------------- +// Pixmap + +ARGBPixmap::ARGBPixmap() + : width(0) + , height(0) + , data(0) +{ +} + +ARGBPixmap::ARGBPixmap(int w, int h) + : width(w) + , height(h) + , data(w * h * 4, 0xAA) +{ + Fill(255, 0, 255); +} + +ARGBPixmap::ARGBPixmap(ARGBPixmap&& other) noexcept + : width(other.width) + , height(other.height) + , data(std::move(other.data)) +{ + other.width = -1; + other.height = -1; +} + +ARGBPixmap& ARGBPixmap::operator=(ARGBPixmap&& other) noexcept +{ + if (this != &other) + { + width = other.width; + height = other.height; + data = std::move(other.data); + other.width = -1; + other.height = -1; + } + return *this; +} + +void ARGBPixmap::Fill(UInt8 red, UInt8 green, UInt8 blue, UInt8 alpha) +{ + for (int i = 0; i < width * height * 4; i += 4) + { + data[i + 0] = alpha; + data[i + 1] = red; + data[i + 2] = green; + data[i + 3] = blue; + } +} + +void ARGBPixmap::Plot(int x, int y, UInt32 color) +{ + if (x < 0 || y < 0 || x >= width || y >= height) + { + throw std::out_of_range("ARGBPixmap::Plot: out of bounds"); + } + else + { + *(UInt32*) &data[4 * (y * width + x)] = color; + } +} + +void ARGBPixmap::WriteTGA(const char* path) const +{ + DumpTGA(path, width, height, (const char*) data.data()); +} + diff --git a/src/Graphics/Color.cpp b/src/Graphics/Color.cpp new file mode 100644 index 0000000..cfb220d --- /dev/null +++ b/src/Graphics/Color.cpp @@ -0,0 +1,27 @@ +#include "PommeGraphics.h" + +using namespace Pomme::Graphics; + +//----------------------------------------------------------------------------- +// Color + +Color::Color(uint8_t r_, uint8_t g_, uint8_t b_) + : a(0xFF) + , r(r_) + , g(g_) + , b(b_) +{} + +Color::Color(uint8_t r_, uint8_t g_, uint8_t b_, uint8_t a_) + : a(a_) + , r(r_) + , g(g_) + , b(b_) +{} + +Color::Color() + : a(0xFF) + , r(0xFF) + , g(0x00) + , b(0xFF) +{} diff --git a/src/Graphics/Graphics.cpp b/src/Graphics/Graphics.cpp new file mode 100644 index 0000000..b043c36 --- /dev/null +++ b/src/Graphics/Graphics.cpp @@ -0,0 +1,641 @@ +#include "Pomme.h" +#include "PommeGraphics.h" +#include "SysFont.h" +#include "Utilities/memstream.h" + +#include +#include +#include + +using namespace Pomme; +using namespace Pomme::Graphics; + +static bool IntersectRects(const Rect* r1, Rect* r2) +{ + r2->left = std::max(r1->left, r2->left); + r2->right = std::min(r1->right, r2->right); + r2->top = std::max(r1->top, r2->top); + r2->bottom = std::min(r1->bottom, r2->bottom); + return r2->left < r2->right && r2->top < r2->bottom; +} + +// ---------------------------------------------------------------------------- - +// Types + +struct GrafPortImpl +{ + GrafPort port; + ARGBPixmap pixels; + bool dirty; + Rect dirtyRect; + PixMap macpm; + PixMap* macpmPtr; + + GrafPortImpl(const Rect boundsRect) + : port({boundsRect, this}) + , pixels(boundsRect.right - boundsRect.left, boundsRect.bottom - boundsRect.top) + , dirty(false) + { + macpm = {}; + macpm.bounds = boundsRect; + macpm.pixelSize = 32; + macpm._impl = (Ptr) &pixels; + macpmPtr = &macpm; + } + + void DamageRegion(const Rect& r) + { + if (!dirty) + { + dirtyRect = r; + } + else + { + // Already dirty, expand existing dirty rect + dirtyRect.top = std::min(dirtyRect.top, r.top); + dirtyRect.left = std::min(dirtyRect.left, r.left); + dirtyRect.bottom = std::max(dirtyRect.bottom, r.bottom); + dirtyRect.right = std::max(dirtyRect.right, r.right); + } + dirty = true; + } + + void DamageRegion(SInt16 x, SInt16 y, SInt16 w, SInt16 h) + { + Rect r = {y, x, static_cast(y + h), static_cast(x + w)}; + DamageRegion(r); + } + + ~GrafPortImpl() + { + macpm._impl = nullptr; + } +}; + +// ---------------------------------------------------------------------------- - +// Internal State + +static std::unique_ptr screenPort = nullptr; +static GrafPortImpl* curPort = nullptr; +static UInt32 penFG = 0xFF'FF'00'FF; +static UInt32 penBG = 0xFF'00'00'FF; +static int penX = 0; +static int penY = 0; + +// ---------------------------------------------------------------------------- - +// Globals + +extern "C" { +SDL_Window* gSDLWindow = nullptr; +} + +// ---------------------------------------------------------------------------- - +// Initialization + +CGrafPtr Pomme::Graphics::GetScreenPort(void) +{ + return &screenPort->port; +} + +void Pomme::Graphics::Init(const char* windowTitle, int windowWidth, int windowHeight) +{ + if (0 != SDL_Init(SDL_INIT_VIDEO)) + { + throw std::runtime_error("Couldn't initialize SDL video subsystem."); + } + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); +#ifndef _WIN32 + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); +#endif + + gSDLWindow = SDL_CreateWindow( + windowTitle, + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + windowWidth, + windowHeight, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN); + + if (!gSDLWindow) + { + throw std::runtime_error("Couldn't create SDL window."); + } + + Rect boundsRect = {0, 0, 480, 640}; + screenPort = std::make_unique(boundsRect); + curPort = screenPort.get(); +} + +void Pomme::Graphics::Shutdown() +{ + SDL_DestroyWindow(gSDLWindow); + gSDLWindow = nullptr; +} + +// ---------------------------------------------------------------------------- - +// Internal utils + +static UInt32 GetEightColorPaletteValue(long color) +{ + switch (color) { + case whiteColor: return clut4[0]; + case yellowColor: return clut4[1]; + case redColor: return clut4[3]; + case magentaColor: return clut4[4]; + case blackColor: return clut4[15]; + case cyanColor: return clut4[7]; + case greenColor: return clut4[8]; // I'm assuming this is light green rather than dark + case blueColor: return clut4[6]; + default: return 0xFF'FF'00'FF; + } +} + +// ---------------------------------------------------------------------------- - +// PICT resources + +PicHandle GetPicture(short PICTresourceID) +{ + Handle rawResource = GetResource('PICT', PICTresourceID); + if (rawResource == nil) + return nil; + memstream substream(*rawResource, GetHandleSize(rawResource)); + ARGBPixmap pm = ReadPICT(substream, false); + ReleaseResource(rawResource); + + // Tack the data onto the end of the Picture struct, + // so that DisposeHandle frees both the Picture and the data. + PicHandle ph = (PicHandle) NewHandle(int(sizeof(Picture) + pm.data.size())); + + Picture& pic = **ph; + Ptr pixels = (Ptr) *ph + sizeof(Picture); + + pic.picFrame = Rect{0, 0, (SInt16) pm.height, (SInt16) pm.width}; + pic.picSize = -1; + pic.__pomme_pixelsARGB32 = pixels; + + memcpy(pic.__pomme_pixelsARGB32, pm.data.data(), pm.data.size()); + + return ph; +} + +// ---------------------------------------------------------------------------- - +// Rect + +void SetRect(Rect* r, short left, short top, short right, short bottom) +{ + r->left = left; + r->top = top; + r->right = right; + r->bottom = bottom; +} + +// ---------------------------------------------------------------------------- - +// GWorld + +static inline GrafPortImpl& GetImpl(GWorldPtr offscreenGWorld) +{ + return *(GrafPortImpl*) offscreenGWorld->_impl; +} + +static inline ARGBPixmap& GetImpl(PixMapPtr pixMap) +{ + return *(ARGBPixmap*) pixMap->_impl; +} + +OSErr NewGWorld(GWorldPtr* offscreenGWorld, short pixelDepth, const Rect* boundsRect, void* junk1, void* junk2, long junk3) +{ + GrafPortImpl* impl = new GrafPortImpl(*boundsRect); + *offscreenGWorld = &impl->port; + return noErr; +} + +void DisposeGWorld(GWorldPtr offscreenGWorld) +{ + delete &GetImpl(offscreenGWorld); +} + +void GetGWorld(CGrafPtr* port, GDHandle* gdh) +{ + *port = &curPort->port; + *gdh = nil; +} + +void SetGWorld(CGrafPtr port, GDHandle gdh) +{ + SetPort(port); +} + +PixMapHandle GetGWorldPixMap(GWorldPtr offscreenGWorld) +{ + return &GetImpl(offscreenGWorld).macpmPtr; +} + +Ptr GetPixBaseAddr(PixMapHandle pm) +{ + return (Ptr) GetImpl(*pm).data.data(); +} + +Boolean LockPixels(PixMapHandle pm) +{ + return true; +} + +void UnlockPixels(PixMapHandle pm) +{ + // no-op +} + +// ---------------------------------------------------------------------------- - +// Port + +void SetPort(GrafPtr port) +{ + curPort = &GetImpl(port); +} + +void GetPort(GrafPtr* outPort) +{ + *outPort = &curPort->port; +} + +Boolean IsPortDamaged(void) +{ + return curPort->dirty; +} + +void GetPortDamageRegion(Rect* r) +{ + *r = curPort->dirtyRect; +} + +void ClearPortDamage(void) +{ + curPort->dirty = false; +} + +void DamagePortRegion(const Rect* r) +{ + curPort->DamageRegion(*r); +} + +void DumpPortTGA(const char* outPath) +{ + curPort->pixels.WriteTGA(outPath); +} + +// ---------------------------------------------------------------------------- - +// Pen state manipulation + +void MoveTo(short h, short v) +{ + penX = h; + penY = v; +} + +void ForeColor(long color) +{ + penFG = GetEightColorPaletteValue(color); +} + +void BackColor(long color) +{ + penBG = GetEightColorPaletteValue(color); +} + +void GetForeColor(RGBColor* rgb) +{ + rgb->red = (penFG >> 16 & 0xFF) << 8; + rgb->green = (penFG >> 8 & 0xFF) << 8; + rgb->blue = (penFG & 0xFF) << 8; +} + +void RGBBackColor(const RGBColor* color) +{ + penBG + = 0xFF'00'00'00 + | ((color->red >> 8) << 16) + | ((color->green >> 8) << 8) + | (color->blue >> 8) + ; +} + +void RGBForeColor(const RGBColor* color) +{ + penFG + = 0xFF'00'00'00 + | ((color->red >> 8) << 16) + | ((color->green >> 8) << 8) + | (color->blue >> 8) + ; +} + +void RGBBackColor2(const UInt32 color) +{ + penBG = 0xFF000000 | (color & 0x00FFFFFF); +} + +void RGBForeColor2(const UInt32 color) +{ + penFG = 0xFF000000 | (color & 0x00FFFFFF); +} + +// ---------------------------------------------------------------------------- - +// Paint + +static void _FillRect(const int left, const int top, const int right, const int bottom, UInt32 fillColor) +{ + if (!curPort) + { + throw std::runtime_error("_FillRect: no port set"); + } + + Rect dstRect; + dstRect.left = left; + dstRect.top = top; + dstRect.right = right; + dstRect.bottom = bottom; + Rect clippedDstRect = dstRect; + if (!IntersectRects(&curPort->port.portRect, &clippedDstRect)) + { + return; + } + curPort->DamageRegion(clippedDstRect); + + fillColor = ByteswapScalar(fillColor); // convert to big-endian + + UInt32* dst = curPort->pixels.GetPtr(clippedDstRect.left, clippedDstRect.top); + + for (int y = clippedDstRect.top; y < clippedDstRect.bottom; y++) + { + for (int x = 0; x < clippedDstRect.right - clippedDstRect.left; x++) + { + dst[x] = fillColor; + } + dst += curPort->pixels.width; + } +} + +void PaintRect(const struct Rect* r) +{ + _FillRect(r->left, r->top, r->right, r->bottom, penFG); +} + +void EraseRect(const struct Rect* r) +{ + _FillRect(r->left, r->top, r->right, r->bottom, penBG); +} + +void LineTo(short x1, short y1) +{ + auto color = ByteswapScalar(penFG); + + auto offx = curPort->port.portRect.left; + auto offy = curPort->port.portRect.top; + + int x0 = penX; + int y0 = penY; + int dx = std::abs(x1 - x0); + int sx = x0 < x1 ? 1 : -1; + int dy = -std::abs(y1 - y0); + int sy = y0 < y1 ? 1 : -1; + int err = dx + dy; + curPort->DamageRegion(penX, penY, dx, dy); + while (1) + { + curPort->pixels.Plot(x0 - offx, y0 - offy, color); + if (x0 == x1 && y0 == y1) break; + int e2 = 2 * err; + if (e2 >= dy) + { + err += dy; + x0 += sx; + } + if (e2 <= dx) + { + err += dx; + y0 += sy; + } + } + penX = x0; + penY = y0; +} + +void FrameRect(const Rect* r) +{ + auto color = ByteswapScalar(penFG); + auto& pm = curPort->pixels; + auto offx = curPort->port.portRect.left; + auto offy = curPort->port.portRect.top; + + for (int x = r->left; x < r->right; x++) pm.Plot(x - offx, r->top - offy, color); + for (int x = r->left; x < r->right; x++) pm.Plot(x - offx, r->bottom - 1 - offy, color); + for (int y = r->top; y < r->bottom; y++) pm.Plot(r->left - offx, y - offy, color); + for (int y = r->top; y < r->bottom; y++) pm.Plot(r->right - 1 - offx, y - offy, color); + + curPort->DamageRegion(*r); +} + +void Pomme::Graphics::DrawARGBPixmap(int left, int top, ARGBPixmap& pixmap) +{ + if (!curPort) + { + throw std::runtime_error("DrawARGBPixmap: no port set"); + } + + Rect dstRect; + dstRect.left = left; + dstRect.top = top; + dstRect.right = left + pixmap.width; + dstRect.bottom = top + pixmap.height; + Rect clippedDstRect = dstRect; + if (!IntersectRects(&curPort->port.portRect, &clippedDstRect)) + { + return; // wholly outside bounds + } + curPort->DamageRegion(clippedDstRect); + + UInt32* src = pixmap.GetPtr(clippedDstRect.left - dstRect.left, clippedDstRect.top - dstRect.top); + UInt32* dst = curPort->pixels.GetPtr(clippedDstRect.left, clippedDstRect.top); + + for (int y = clippedDstRect.top; y < clippedDstRect.bottom; y++) + { + memcpy(dst, src, Width(clippedDstRect) * sizeof(UInt32)); + dst += curPort->pixels.width; + src += pixmap.width; + } +} + +void DrawPicture(PicHandle myPicture, const Rect* dstRect) +{ + auto& pic = **myPicture; + + UInt32* srcPixels = (UInt32*) pic.__pomme_pixelsARGB32; + + int dstWidth = Width(*dstRect); + int dstHeight = Height(*dstRect); + int srcWidth = Width(pic.picFrame); + int srcHeight = Height(pic.picFrame); + + if (srcWidth != dstWidth || srcHeight != dstHeight) + TODOFATAL2("we only support dstRect with the same width/height as the source picture"); + + for (int y = 0; y < dstHeight; y++) + { + memcpy( + curPort->pixels.GetPtr(dstRect->left, dstRect->top + y), + srcPixels + y * srcWidth, + 4 * dstWidth); + } + + curPort->DamageRegion(*dstRect); +} + +void CopyBits( + const PixMap* srcBits, + PixMap* dstBits, + const Rect* srcRect, + const Rect* dstRect, + short mode, + void* maskRgn +) +{ + auto& srcPM = GetImpl((PixMapPtr) srcBits); + auto& dstPM = GetImpl(dstBits); + + const auto& srcBounds = srcBits->bounds; + const auto& dstBounds = dstBits->bounds; + + int srcRectWidth = Width(*srcRect); + int srcRectHeight = Height(*srcRect); + int dstRectWidth = Width(*dstRect); + int dstRectHeight = Height(*dstRect); + + if (srcRectWidth != dstRectWidth || srcRectHeight != dstRectHeight) + TODOFATAL2("can only copy between rects of same dimensions"); + + for (int y = 0; y < srcRectHeight; y++) + { + memcpy( + dstPM.GetPtr(dstRect->left - dstBounds.left, dstRect->top - dstBounds.top + y), + srcPM.GetPtr(srcRect->left - srcBounds.left, srcRect->top - srcBounds.top + y), + 4 * srcRectWidth + ); + } +} + +// ---------------------------------------------------------------------------- - +// Text rendering + +short TextWidthC(const char* cstr) +{ + if (!cstr) return 0; + + int totalWidth = -SysFont::charSpacing; + for (; *cstr; cstr++) + { + totalWidth += SysFont::charSpacing; + totalWidth += SysFont::GetGlyph(*cstr).width; + } + return totalWidth; +} + +void DrawStringC(const char* cstr) +{ + if (!cstr) return; + + _FillRect( + penX, + penY - SysFont::ascend, + penX + TextWidthC(cstr), + penY + SysFont::descend, + penBG + ); + + penX -= SysFont::charSpacing; + for (; *cstr; cstr++) + { + penX += SysFont::charSpacing; + DrawChar(*cstr); + } +} + +void DrawChar(char c) +{ + UInt32 fg = ByteswapScalar(penFG); + + auto& glyph = SysFont::GetGlyph(c); + + // Theoretical coordinates of top-left corner of glyph (may be outside port bounds!) + Rect dstRect; + dstRect.left = penX - SysFont::leftMargin; + dstRect.top = penY - SysFont::ascend; + dstRect.right = dstRect.left + SysFont::widthBits; + dstRect.bottom = dstRect.top + SysFont::rows; + + Rect clippedDstRect = dstRect; + if (!IntersectRects(&curPort->port.portRect, &clippedDstRect)) + { + return; // wholly outside bounds + } + curPort->DamageRegion(clippedDstRect); + + // Glyph boundaries + int minCol = clippedDstRect.left - dstRect.left; + int minRow = clippedDstRect.top - dstRect.top; + + auto* dst2 = curPort->pixels.GetPtr(clippedDstRect.left, clippedDstRect.top); + + for (int glyphY = minRow; glyphY < minRow + Height(clippedDstRect); glyphY++) + { + auto rowBits = glyph.bits[glyphY]; + + rowBits >>= minCol; + + auto* dstRow = dst2; + + for (int glyphX = minCol; glyphX < minCol + Width(clippedDstRect); glyphX++) + { + if (rowBits & 1) + { + *dstRow = fg; + } + rowBits >>= 1; + dstRow++; + } + + dst2 += curPort->pixels.width; + } + + penX += glyph.width; +} + +// ---------------------------------------------------------------------------- +// Icons + +void Pomme::Graphics::SetWindowIconFromIcl8Resource(short icl8ID) +{ + Handle macIcon = GetResource('icl8', icl8ID); + if (1024 != GetHandleSize(macIcon)) + { + throw std::invalid_argument("icl8 resource has incorrect size"); + } + + const int width = 32; + const int height = 32; + + SDL_Surface* icon = SDL_CreateRGBSurface(0, width, height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); + for (int y = 0; y < height; y++) + { + uint32_t* out = (uint32_t*) ((char*) icon->pixels + icon->pitch * y); + for (int x = 0; x < width; x++) + { + unsigned char pixel = (*macIcon)[y * width + x]; + *out++ = pixel == 0 ? 0 : Pomme::Graphics::clut8[pixel]; + } + } + SDL_SetWindowIcon(gSDLWindow, icon); + SDL_FreeSurface(icon); + DisposeHandle(macIcon); +} diff --git a/src/Graphics/PICT.cpp b/src/Graphics/PICT.cpp new file mode 100644 index 0000000..edcbccb --- /dev/null +++ b/src/Graphics/PICT.cpp @@ -0,0 +1,466 @@ +#include "Pomme.h" +#include "PommeGraphics.h" +#include "Utilities/BigEndianIStream.h" + +#include +#include +#include +#include + +using namespace Pomme; +using namespace Pomme::Graphics; + +#define LOG POMME_GENLOG(POMME_DEBUG_PICT, "PICT") +#define LOG_NOPREFIX POMME_GENLOG_NOPREFIX(POMME_DEBUG_PICT) + +class PICTException : public std::runtime_error +{ +public: + PICTException(const char* m) : std::runtime_error(m) + {} +}; + +//----------------------------------------------------------------------------- +// Rect helpers + +Rect ReadRect(BigEndianIStream& f) +{ + Rect r; + r.top = f.Read(); + r.left = f.Read(); + r.bottom = f.Read(); + r.right = f.Read(); + return r; +} + +bool operator==(const Rect& r1, const Rect& r2) +{ + return + r1.top == r2.top && + r1.left == r2.left && + r1.bottom == r2.bottom && + r1.right == r2.right; +} + +bool operator!=(const Rect& r1, const Rect& r2) +{ + return + r1.top != r2.top || + r1.left != r2.left || + r1.bottom != r2.bottom || + r1.right != r2.right; +} + +std::ostream& operator<<(std::ostream& s, const Rect& r) +{ + return s << "T" << r.top << "L" << r.left << "B" << r.bottom << "R" << r.right; +} + +//----------------------------------------------------------------------------- +// Dump Targa + +void Pomme::Graphics::DumpTGA(const char* path, short width, short height, const char* argbData) +{ + std::ofstream tga(path); + short tgaHdr[] = {0, 2, 0, 0, 0, 0, (short) width, (short) height, 0x2020}; + tga.write((const char*) tgaHdr, sizeof(tgaHdr)); + for (int i = 0; i < 4 * width * height; i += 4) + { + tga.put(argbData[i + 3]); //b + tga.put(argbData[i + 2]); //g + tga.put(argbData[i + 1]); //r + tga.put(argbData[i + 0]); //a + } + tga.close(); + std::cout << "wrote " << path << "\n"; +} + +//----------------------------------------------------------------------------- +// PackBits + +template +static std::vector UnpackBits(BigEndianIStream& f, UInt16 rowbytes, int packedLength) +{ + //LOG << "UnpackBits rowbytes=" << rowbytes << " packedlength=" << packedLength << "\n"; + + std::vector unpacked; + + if (rowbytes < 8) + { + // Bits aren't compressed + LOG << "Bits aren't compressed\n"; + for (int j = 0; j < packedLength; j += sizeof(T)) + { + unpacked.push_back(f.Read()); + } + return unpacked; + } + + for (int j = 0; j < packedLength;) + { + Byte FlagCounter = f.Read(); + + if (FlagCounter == 0x80) + { + // special case: repeat value of 0. Apple says ignore. + j++; + } + else if (FlagCounter & 0x80) + { + // Packed data. + int len = ((FlagCounter ^ 0xFF) & 0xFF) + 2; + auto item = f.Read(); + for (int k = 0; k < len; k++) + { + unpacked.push_back(item); + } + j += 1 + sizeof(T); + } + else + { + // Unpacked data + int len = (FlagCounter & 0xFF) + 1; + for (int k = 0; k < len; k++) + { + unpacked.push_back(f.Read()); + } + j += 1 + len * sizeof(T); + } + } + + return unpacked; +} + +//----------------------------------------------------------------------------- +// Unpack PICT pixmap formats + +template +static std::vector UnpackAllRows(BigEndianIStream& f, int w, int h, UInt16 rowbytes, std::size_t expectedItemCount) +{ + LOG << "UnpackBits<" << typeid(T).name() << ">"; + + std::vector data; + data.reserve(expectedItemCount); + for (int y = 0; y < h; y++) + { + int packedRowBytes = rowbytes > 250 ? f.Read() : f.Read(); + std::vector rowPixels = UnpackBits(f, rowbytes, packedRowBytes); + data.insert(data.end(), rowPixels.begin(), rowPixels.end()); + } + + if (expectedItemCount != data.size()) + { + throw PICTException("UnpackAllRows: unexpected item count"); + } + + LOG_NOPREFIX << "\n"; + return data; +} + +// Unpack pixel type 0 (8-bit indexed) +static ARGBPixmap Unpack0(BigEndianIStream& f, int w, int h, const std::vector& palette) +{ + auto unpacked = UnpackAllRows(f, w, h, w, w * h); + ARGBPixmap dst(w, h); + dst.data.clear(); + LOG << "indexed to RGBA"; + for (uint8_t px : unpacked) + { + if (px >= palette.size()) + { + throw PICTException("Unpack0: illegal color index in pixmap"); + } + Color c = palette[px]; + dst.data.push_back(c.a); + dst.data.push_back(c.r); + dst.data.push_back(c.g); + dst.data.push_back(c.b); + } + LOG_NOPREFIX << "\n"; + return dst; +} + +// Unpack pixel type 4 (16 bits, chunky) +static ARGBPixmap Unpack3(BigEndianIStream& f, int w, int h, UInt16 rowbytes) +{ + auto unpacked = UnpackAllRows(f, w, h, rowbytes, w * h); + ARGBPixmap dst(w, h); + dst.data.clear(); + dst.data.reserve(unpacked.size() * 4); + LOG << "Chunky16 to RGBA"; + for (UInt16 px : unpacked) + { + dst.data.push_back(0xFF); // a + dst.data.push_back(((px >> 10) & 0x1F) << 3); // r + dst.data.push_back(((px >> 5) & 0x1F) << 3); // g + dst.data.push_back(((px >> 0) & 0x1F) << 3); // b + } + LOG_NOPREFIX << "\n"; + return dst; +} + +// Unpack pixel type 4 (24 or 32 bits, planar) +static ARGBPixmap Unpack4(BigEndianIStream& f, int w, int h, UInt16 rowbytes, int numPlanes) +{ + auto unpacked = UnpackAllRows(f, w, h, rowbytes, numPlanes * w * h); + ARGBPixmap dst(w, h); + dst.data.clear(); + LOG << "Planar" << numPlanes*8 << " to RGBA"; + for (int y = 0; y < h; y++) + { + if (numPlanes == 3) + { + for (int x = 0; x < w; x++) + { + dst.data.push_back(0xFF); + dst.data.push_back(unpacked[y*w*3 + x + w*0]); // red + dst.data.push_back(unpacked[y*w*3 + x + w*1]); // grn + dst.data.push_back(unpacked[y*w*3 + x + w*2]); // blu + } + } + else + { + for (int x = 0; x < w; x++) + { + dst.data.push_back(unpacked[y*w*3 + x + w*0]); // alpha + dst.data.push_back(unpacked[y*w*3 + x + w*1]); // red + dst.data.push_back(unpacked[y*w*3 + x + w*2]); // grn + dst.data.push_back(unpacked[y*w*3 + x + w*3]); // blu + } + } + } + LOG_NOPREFIX << "\n"; + return dst; +} + +//----------------------------------------------------------------------------- +// PICT header + +static ARGBPixmap ReadPICTBits(BigEndianIStream& f, int opcode, const Rect& canvasRect) +{ + bool directBitsOpcode = opcode == 0x009A || opcode == 0x009B; + + //printf("@@@ ReadPictBits %04x ", opcode); LOG << "canvasRect: " << canvasRect << "\n"; + + if (directBitsOpcode) f.Skip(4); //skip junk + int rowbytes = f.Read(); + //LOG << "**** rowbytes = " << rowbytes << "\n"; + + Rect frameRect = ReadRect(f); + LOG << "frameRect " << frameRect << "\n"; + if (frameRect != canvasRect) + throw PICTException("frame dims != canvas dims 1"); + + int packType = -1; + int pixelSize = -1; + int componentCount = -1; + + SInt16 pixmapVersion = f.Read(); + packType = f.Read(); + SInt32 packSize = f.Read(); + Fixed hResolution = f.Read(); + Fixed vResolution = f.Read(); + SInt16 pixelType = f.Read(); + pixelSize = f.Read(); + componentCount = f.Read(); + SInt16 componentSize = f.Read(); + SInt32 planeBytes = f.Read(); + SInt32 table = f.Read(); + if (directBitsOpcode || (rowbytes & 0x8000) != 0) + { + f.Skip(4); + + LOG << "----PICT PIXMAP----" + << "\n\tpixmap version " << pixmapVersion + << "\n\tpack type " << packType + << "\n\tpack size " << packSize + << "\n\tresolution " << (hResolution>>16) << "x" << (vResolution>>16) + << "\n\tpixel type " << pixelType + << "\n\tbpp " << pixelSize + << "\n\tcomponent count " << componentCount + << "\n\tcomponent size " << componentSize + << "\n\tplane bytes " << planeBytes + << "\n\ttable " << table + << "\n"; + + if (pixelSize > 32) throw PICTException("pixmap invalid bpp"); + if (componentCount > 4) throw PICTException("pixmap invalid component count"); + if (componentSize <= 0) throw PICTException("pixmap invalid component size"); + } + + auto palette = std::vector(); + if (!directBitsOpcode) + { + f.Skip(4); + UInt16 flags = f.Read(); + int nColors = 1 + f.Read(); + + LOG << "Colormap: " << nColors << " colors\n"; + if (nColors <= 0 || nColors > 256) throw PICTException("unsupported palette size"); + + palette.resize(nColors); + + for (int i = 0; i < nColors; i++) + { + int index; + if (flags & 0x8000) + { + // ignore junk index (usually just set to 0) + f.Skip(2); + index = i; + } + else + { + index = f.Read(); + if (index >= nColors) + throw PICTException("illegal color index in palette definition"); + } + + UInt8 r = (f.Read() >> 8) & 0xFF; + UInt8 g = (f.Read() >> 8) & 0xFF; + UInt8 b = (f.Read() >> 8) & 0xFF; + palette[index] = Color(r, g, b); + } + } + + Rect srcRect = ReadRect(f); + Rect dstRect = ReadRect(f); + if (srcRect != dstRect) throw PICTException("unsupported src/dst rects"); + if (srcRect != canvasRect) throw PICTException("unsupported src/dst rects that aren't the same as the canvas rect"); + f.Skip(2); + + if (opcode == 0x0091 || opcode == 0x0099 || opcode == 0x009b) + { + throw PICTException("unimplemented opcode"); + } + + if (!directBitsOpcode && (rowbytes & 0x8000) == 0) + { + throw PICTException("negative rowbytes"); + } + else + { + int cw = Width(canvasRect); + int ch = Height(canvasRect); + + switch (packType) + { + case 0: // 8-bit indexed color, packed bytewise + return Unpack0(f, cw, ch, palette); + + case 3: // 16-bit color, stored chunky, packed pixelwise + return Unpack3(f, cw, ch, rowbytes); + + case 4: // 24- or 32-bit color, stored planar, packed bytewise + return Unpack4(f, cw, ch, rowbytes, componentCount); + + default: + throw PICTException("don't know how to unpack this pixel size"); + } + } +} + +ARGBPixmap Pomme::Graphics::ReadPICT(std::istream& theF, bool skip512) +{ + BigEndianIStream f(theF); + + LOG << "-----------------------------\n"; + auto startOff = f.Tell(); + + if (skip512) + f.Skip(512); // junk + + f.Skip(2); // Version 1 picture size. Meaningless for "modern" picts that can easily exceed 65,535 bytes. + + Rect canvasRect = ReadRect(f); + if (Width(canvasRect) < 0 || Height(canvasRect) < 0) + { + LOG << "canvas rect: " << canvasRect << "\n"; + throw PICTException("invalid width/height"); + } + + LOG << std::dec << "Pict canvas: " << canvasRect << "\n"; + + if (0x0011 != f.Read()) + throw PICTException("didn't find version opcode in PICT header"); + if (0x02 != f.Read()) throw PICTException("unrecognized PICT version"); + if (0xFF != f.Read()) throw PICTException("bad PICT header"); + + ARGBPixmap pm(0, 0); + bool readPixmap = false; + + while (true) + { + int opcode; + + // Align position to short + f.Skip((f.Tell() - startOff) % 2); + + opcode = f.Read(); + + //printf("~~~~~ OPCODE %04X ~~~~~\n", opcode); + + // Skip reserved opcodes + if (opcode >= 0x0100 && opcode <= 0x7FFF) + { + f.Skip((opcode >> 7) & 0xFF); + continue; + } + + switch (opcode) + { + case 0x0C00: + f.Skip(12); + break; + + case 0x0000: // nop + case 0x001E: // DefHilite + case 0x0048: // frameSameRRect + LOG << __func__ << ": skipping opcode " << opcode << "\n"; + break; + + case 0x001F: // OpColor + LOG << __func__ << ": skipping opcode " << opcode << "\n"; + f.Skip(6); + break; + + case 0x0001: // clip + { + auto length = f.Read(); + if (length != 0x0A) f.Skip(length - 2); + Rect frameRect = ReadRect(f); + //LOG << "CLIP:" << frameRect << "\n"; + if (frameRect.left < 0 || frameRect.top < 0) throw PICTException("illegal frame rect"); + if (frameRect != canvasRect) + { + std::cerr << "Warning: clip rect " << frameRect << " isn't the same as the canvas rect " << canvasRect << ", using clip rect\n"; + canvasRect = frameRect; + } + break; + } + + case 0x0098: // PackBitsRect + case 0x009A: // DirectBitsRect + if (readPixmap) + throw PICTException("already read one pixmap!"); + pm = ReadPICTBits(f, opcode, canvasRect); + readPixmap = true; + break; + + case 0x00A1: // Long comment + f.Skip(2); + f.Skip(f.Read()); + break; + + case 0x00FF: // done + case 0xFFFF: + return pm; + + default: + std::cerr << "unsupported opcode " << opcode << " at offset " << f.Tell() << "\n"; + throw PICTException("unsupported PICT opcode"); + } + } + + return pm; +} diff --git a/src/Graphics/SysFont.h b/src/Graphics/SysFont.h new file mode 100644 index 0000000..6d5c90e --- /dev/null +++ b/src/Graphics/SysFont.h @@ -0,0 +1,122 @@ +namespace Pomme::SysFont +{ + static const int firstCodepoint = 32; + static const int lastCodepoint = 126; + static const int charSpacing = 2; + static const int rows = 14; + static const int leftMargin = 2; + static const int ascend = 12; + static const int descend = 2; + static const int widthBits = 16; + + static const struct Glyph + { + short width; + unsigned short bits[14]; + } glyphs[95] = { + { 2, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, //space + { 3, { 0, 0, 0, 24, 24, 24, 24, 24, 24, 24, 0, 24, 0, 0 } }, //! + { 4, { 0, 0, 0, 40, 40, 40, 0, 0, 0, 0, 0, 0, 0, 0 } }, //" + { 8, { 0, 0, 288, 288,1016, 144, 144, 144, 508, 72, 72, 0, 0, 0 } }, //# + { 7, { 0, 0, 32, 504, 44, 44, 44, 248, 416, 416, 416, 252, 32, 0 } }, //$ + { 9, { 0, 0, 0, 256, 280, 164, 164, 88, 832,1184,1184, 784, 16, 0 } }, //% + { 7, { 0, 0, 0, 120, 76, 12, 408, 204, 204, 204, 204, 120, 0, 0 } }, //& + { 2, { 0, 0, 0, 12, 12, 8, 4, 0, 0, 0, 0, 0, 0, 0 } }, //' + { 4, { 0, 0, 48, 24, 24, 12, 12, 12, 12, 12, 24, 24, 48, 0 } }, //( + { 4, { 0, 0, 12, 24, 24, 48, 48, 48, 48, 48, 24, 24, 12, 0 } }, //) + { 7, { 0, 0, 0, 0, 0, 216, 112, 508, 112, 216, 0, 0, 0, 0 } }, //* + { 6, { 0, 0, 0, 0, 0, 48, 48, 252, 252, 48, 48, 0, 0, 0 } }, //+ + { 2, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 8, 4 } }, //, + { 4, { 0, 0, 0, 0, 0, 0, 0, 60, 60, 0, 0, 0, 0, 0 } }, //- + { 3, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 0, 0 } }, //. + { 6, { 0, 0, 0, 192, 192, 96, 96, 48, 48, 24, 24, 12, 12, 0 } }, /// + { 6, { 0, 0, 0, 120, 204, 204, 236, 220, 204, 204, 204, 120, 0, 0 } }, //0 + { 6, { 0, 0, 0, 56, 48, 48, 48, 48, 48, 48, 48, 252, 0, 0 } }, //1 + { 6, { 0, 0, 0, 124, 192, 192, 192, 96, 48, 24, 12, 252, 0, 0 } }, //2 + { 6, { 0, 0, 0, 124, 192, 192, 112, 192, 192, 192, 192, 124, 0, 0 } }, //3 + { 6, { 0, 0, 0, 192, 224, 208, 200, 204, 204, 252, 192, 192, 0, 0 } }, //4 + { 6, { 0, 0, 0, 252, 12, 12, 124, 192, 192, 192, 192, 124, 0, 0 } }, //5 + { 6, { 0, 0, 0, 248, 12, 12, 124, 204, 204, 204, 204, 120, 0, 0 } }, //6 + { 6, { 0, 0, 0, 252, 192, 224, 112, 56, 24, 24, 24, 24, 0, 0 } }, //7 + { 6, { 0, 0, 0, 120, 204, 204, 204, 120, 204, 204, 204, 120, 0, 0 } }, //8 + { 6, { 0, 0, 0, 120, 204, 204, 204, 248, 192, 192, 192, 120, 0, 0 } }, //9 + { 3, { 0, 0, 0, 0, 0, 0, 24, 24, 0, 0, 24, 24, 0, 0 } }, //: + { 3, { 0, 0, 0, 0, 0, 0, 24, 24, 0, 0, 24, 24, 16, 8 } }, //; + { 4, { 0, 0, 0, 0, 0, 32, 48, 24, 12, 24, 48, 32, 0, 0 } }, //< + { 5, { 0, 0, 0, 0, 0, 0, 0, 124, 0, 124, 0, 0, 0, 0 } }, //= + { 4, { 0, 0, 0, 0, 0, 4, 12, 24, 48, 24, 12, 4, 0, 0 } }, //> + { 7, { 0, 0, 0, 240, 408, 408, 448, 224, 96, 96, 0, 96, 0, 0 } }, //? + { 7, { 0, 0, 0, 0, 240, 264, 356, 340, 244, 4, 56, 0, 0, 0 } }, //@ + { 6, { 0, 0, 0, 120, 204, 204, 204, 252, 204, 204, 204, 204, 0, 0 } }, //A + { 6, { 0, 0, 0, 124, 204, 204, 204, 124, 204, 204, 204, 124, 0, 0 } }, //B + { 5, { 0, 0, 0, 120, 12, 12, 12, 12, 12, 12, 12, 120, 0, 0 } }, //C + { 6, { 0, 0, 0, 124, 204, 204, 204, 204, 204, 204, 204, 124, 0, 0 } }, //D + { 5, { 0, 0, 0, 124, 12, 12, 12, 60, 12, 12, 12, 124, 0, 0 } }, //E + { 5, { 0, 0, 0, 124, 12, 12, 12, 60, 12, 12, 12, 12, 0, 0 } }, //F + { 6, { 0, 0, 0, 248, 12, 12, 12, 204, 204, 204, 204, 184, 0, 0 } }, //G + { 6, { 0, 0, 0, 204, 204, 204, 204, 252, 204, 204, 204, 204, 0, 0 } }, //H + { 4, { 0, 0, 0, 60, 24, 24, 24, 24, 24, 24, 24, 60, 0, 0 } }, //I + { 6, { 0, 0, 0, 192, 192, 192, 192, 192, 192, 204, 204, 120, 0, 0 } }, //J + { 7, { 0, 0, 0, 396, 204, 108, 60, 28, 60, 108, 204, 396, 0, 0 } }, //K + { 5, { 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 124, 0, 0 } }, //L + { 10, { 0, 0, 0,2044,3276,3276,3276,3276,3276,3276,3276,3276, 0, 0 } }, //M + { 6, { 0, 0, 0, 124, 204, 204, 204, 204, 204, 204, 204, 204, 0, 0 } }, //N + { 6, { 0, 0, 0, 120, 204, 204, 204, 204, 204, 204, 204, 120, 0, 0 } }, //O + { 6, { 0, 0, 0, 124, 204, 204, 204, 124, 12, 12, 12, 12, 0, 0 } }, //P + { 6, { 0, 0, 0, 120, 204, 204, 204, 204, 204, 204, 204, 120, 224, 0 } }, //Q + { 6, { 0, 0, 0, 124, 204, 204, 204, 124, 204, 204, 204, 204, 0, 0 } }, //R + { 6, { 0, 0, 0, 248, 12, 12, 28, 120, 224, 192, 192, 124, 0, 0 } }, //S + { 6, { 0, 0, 0, 252, 48, 48, 48, 48, 48, 48, 48, 48, 0, 0 } }, //T + { 6, { 0, 0, 0, 204, 204, 204, 204, 204, 204, 204, 204, 120, 0, 0 } }, //U + { 6, { 0, 0, 0, 204, 204, 204, 204, 204, 204, 204, 76, 60, 0, 0 } }, //V + { 10, { 0, 0, 0,3276,3276,3276,3276,3276,3276,3276,1228,1020, 0, 0 } }, //W + { 6, { 0, 0, 0, 204, 204, 204, 204, 120, 204, 204, 204, 204, 0, 0 } }, //X + { 6, { 0, 0, 0, 204, 204, 204, 204, 120, 48, 48, 48, 48, 0, 0 } }, //Y + { 6, { 0, 0, 0, 252, 192, 192, 96, 48, 24, 12, 12, 252, 0, 0 } }, //Z + { 3, { 0, 0, 28, 12, 12, 12, 12, 12, 12, 12, 12, 12, 28, 0 } }, //[ + { 6, { 0, 0, 0, 12, 12, 24, 24, 48, 48, 96, 96, 192, 192, 0 } }, //backslash + { 3, { 0, 0, 28, 24, 24, 24, 24, 24, 24, 24, 24, 24, 28, 0 } }, //] + { 7, { 0, 0, 0, 32, 112, 216, 396, 0, 0, 0, 0, 0, 0, 0 } }, //^ + { 5, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0 } }, //_ + { 2, { 0, 0, 0, 12, 12, 4, 8, 0, 0, 0, 0, 0, 0, 0 } }, //` + { 6, { 0, 0, 0, 0, 0, 120, 192, 248, 204, 204, 204, 184, 0, 0 } }, //a + { 6, { 0, 0, 0, 12, 12, 124, 204, 204, 204, 204, 204, 116, 0, 0 } }, //b + { 5, { 0, 0, 0, 0, 0, 120, 12, 12, 12, 12, 12, 120, 0, 0 } }, //c + { 6, { 0, 0, 0, 192, 192, 248, 204, 204, 204, 204, 204, 184, 0, 0 } }, //d + { 6, { 0, 0, 0, 0, 0, 120, 204, 204, 252, 12, 12, 248, 0, 0 } }, //e + { 5, { 0, 0, 0, 112, 24, 60, 24, 24, 24, 24, 24, 24, 0, 0 } }, //f + { 6, { 0, 0, 0, 0, 0, 184, 204, 204, 204, 204, 204, 248, 192, 124 } }, //g + { 6, { 0, 0, 0, 12, 12, 108, 220, 204, 204, 204, 204, 204, 0, 0 } }, //h + { 2, { 0, 0, 0, 12, 0, 12, 12, 12, 12, 12, 12, 12, 0, 0 } }, //i + { 2, { 0, 0, 0, 12, 0, 12, 12, 12, 12, 12, 12, 12, 12, 6 } }, //j + { 6, { 0, 0, 0, 12, 12, 204, 108, 60, 28, 60, 108, 204, 0, 0 } }, //k + { 3, { 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 24, 0, 0 } }, //l + { 10, { 0, 0, 0, 0, 0,1908,3276,3276,3276,3276,3276,3276, 0, 0 } }, //m + { 6, { 0, 0, 0, 0, 0, 116, 204, 204, 204, 204, 204, 204, 0, 0 } }, //n + { 6, { 0, 0, 0, 0, 0, 120, 204, 204, 204, 204, 204, 120, 0, 0 } }, //o + { 6, { 0, 0, 0, 0, 0, 116, 204, 204, 204, 204, 204, 124, 12, 12 } }, //p + { 6, { 0, 0, 0, 0, 0, 184, 204, 204, 204, 204, 204, 248, 192, 192 } }, //q + { 5, { 0, 0, 0, 0, 0, 108, 28, 12, 12, 12, 12, 12, 0, 0 } }, //r + { 6, { 0, 0, 0, 0, 0, 248, 12, 12, 120, 192, 192, 124, 0, 0 } }, //s + { 4, { 0, 0, 0, 24, 24, 60, 24, 24, 24, 24, 24, 48, 0, 0 } }, //t + { 6, { 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 204, 184, 0, 0 } }, //u + { 6, { 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 76, 60, 0, 0 } }, //v + { 10, { 0, 0, 0, 0, 0,3276,3276,3276,3276,3276,1228,1020, 0, 0 } }, //w + { 6, { 0, 0, 0, 0, 0, 204, 204, 204, 120, 204, 204, 204, 0, 0 } }, //x + { 6, { 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 204, 248, 192, 124 } }, //y + { 6, { 0, 0, 0, 0, 0, 252, 192, 96, 48, 24, 12, 252, 0, 0 } }, //z + { 4, { 0, 0, 48, 24, 24, 24, 24, 12, 24, 24, 24, 24, 48, 0 } }, //{ + { 2, { 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 } }, //| + { 4, { 0, 0, 12, 24, 24, 24, 24, 48, 24, 24, 24, 24, 12, 0 } }, //} + }; + + static const Glyph& placeholder = glyphs['?' - firstCodepoint]; + + static inline const Glyph& GetGlyph(char codePoint) + { + if (codePoint < firstCodepoint || codePoint > lastCodepoint) + return placeholder; + else + return glyphs[codePoint - firstCodepoint]; + } +} diff --git a/src/Graphics/SystemPalettes.cpp b/src/Graphics/SystemPalettes.cpp new file mode 100644 index 0000000..4611c51 --- /dev/null +++ b/src/Graphics/SystemPalettes.cpp @@ -0,0 +1,43 @@ +#include "PommeGraphics.h" + +const uint32_t Pomme::Graphics::clut8[256] = +{ + 0xFFFFFFFF, 0xFFFFFFCC, 0xFFFFFF99, 0xFFFFFF66, 0xFFFFFF33, 0xFFFFFF00, 0xFFFFCCFF, 0xFFFFCCCC, + 0xFFFFCC99, 0xFFFFCC66, 0xFFFFCC33, 0xFFFFCC00, 0xFFFF99FF, 0xFFFF99CC, 0xFFFF9999, 0xFFFF9966, + 0xFFFF9933, 0xFFFF9900, 0xFFFF66FF, 0xFFFF66CC, 0xFFFF6699, 0xFFFF6666, 0xFFFF6633, 0xFFFF6600, + 0xFFFF33FF, 0xFFFF33CC, 0xFFFF3399, 0xFFFF3366, 0xFFFF3333, 0xFFFF3300, 0xFFFF00FF, 0xFFFF00CC, + 0xFFFF0099, 0xFFFF0066, 0xFFFF0033, 0xFFFF0000, 0xFFCCFFFF, 0xFFCCFFCC, 0xFFCCFF99, 0xFFCCFF66, + 0xFFCCFF33, 0xFFCCFF00, 0xFFCCCCFF, 0xFFCCCCCC, 0xFFCCCC99, 0xFFCCCC66, 0xFFCCCC33, 0xFFCCCC00, + 0xFFCC99FF, 0xFFCC99CC, 0xFFCC9999, 0xFFCC9966, 0xFFCC9933, 0xFFCC9900, 0xFFCC66FF, 0xFFCC66CC, + 0xFFCC6699, 0xFFCC6666, 0xFFCC6633, 0xFFCC6600, 0xFFCC33FF, 0xFFCC33CC, 0xFFCC3399, 0xFFCC3366, + 0xFFCC3333, 0xFFCC3300, 0xFFCC00FF, 0xFFCC00CC, 0xFFCC0099, 0xFFCC0066, 0xFFCC0033, 0xFFCC0000, + 0xFF99FFFF, 0xFF99FFCC, 0xFF99FF99, 0xFF99FF66, 0xFF99FF33, 0xFF99FF00, 0xFF99CCFF, 0xFF99CCCC, + 0xFF99CC99, 0xFF99CC66, 0xFF99CC33, 0xFF99CC00, 0xFF9999FF, 0xFF9999CC, 0xFF999999, 0xFF999966, + 0xFF999933, 0xFF999900, 0xFF9966FF, 0xFF9966CC, 0xFF996699, 0xFF996666, 0xFF996633, 0xFF996600, + 0xFF9933FF, 0xFF9933CC, 0xFF993399, 0xFF993366, 0xFF993333, 0xFF993300, 0xFF9900FF, 0xFF9900CC, + 0xFF990099, 0xFF990066, 0xFF990033, 0xFF990000, 0xFF66FFFF, 0xFF66FFCC, 0xFF66FF99, 0xFF66FF66, + 0xFF66FF33, 0xFF66FF00, 0xFF66CCFF, 0xFF66CCCC, 0xFF66CC99, 0xFF66CC66, 0xFF66CC33, 0xFF66CC00, + 0xFF6699FF, 0xFF6699CC, 0xFF669999, 0xFF669966, 0xFF669933, 0xFF669900, 0xFF6666FF, 0xFF6666CC, + 0xFF666699, 0xFF666666, 0xFF666633, 0xFF666600, 0xFF6633FF, 0xFF6633CC, 0xFF663399, 0xFF663366, + 0xFF663333, 0xFF663300, 0xFF6600FF, 0xFF6600CC, 0xFF660099, 0xFF660066, 0xFF660033, 0xFF660000, + 0xFF33FFFF, 0xFF33FFCC, 0xFF33FF99, 0xFF33FF66, 0xFF33FF33, 0xFF33FF00, 0xFF33CCFF, 0xFF33CCCC, + 0xFF33CC99, 0xFF33CC66, 0xFF33CC33, 0xFF33CC00, 0xFF3399FF, 0xFF3399CC, 0xFF339999, 0xFF339966, + 0xFF339933, 0xFF339900, 0xFF3366FF, 0xFF3366CC, 0xFF336699, 0xFF336666, 0xFF336633, 0xFF336600, + 0xFF3333FF, 0xFF3333CC, 0xFF333399, 0xFF333366, 0xFF333333, 0xFF333300, 0xFF3300FF, 0xFF3300CC, + 0xFF330099, 0xFF330066, 0xFF330033, 0xFF330000, 0xFF00FFFF, 0xFF00FFCC, 0xFF00FF99, 0xFF00FF66, + 0xFF00FF33, 0xFF00FF00, 0xFF00CCFF, 0xFF00CCCC, 0xFF00CC99, 0xFF00CC66, 0xFF00CC33, 0xFF00CC00, + 0xFF0099FF, 0xFF0099CC, 0xFF009999, 0xFF009966, 0xFF009933, 0xFF009900, 0xFF0066FF, 0xFF0066CC, + 0xFF006699, 0xFF006666, 0xFF006633, 0xFF006600, 0xFF0033FF, 0xFF0033CC, 0xFF003399, 0xFF003366, + 0xFF003333, 0xFF003300, 0xFF0000FF, 0xFF0000CC, 0xFF000099, 0xFF000066, 0xFF000033, 0xFFEE0000, + 0xFFDD0000, 0xFFBB0000, 0xFFAA0000, 0xFF880000, 0xFF770000, 0xFF550000, 0xFF440000, 0xFF220000, + 0xFF110000, 0xFF00EE00, 0xFF00DD00, 0xFF00BB00, 0xFF00AA00, 0xFF008800, 0xFF007700, 0xFF005500, + 0xFF004400, 0xFF002200, 0xFF001100, 0xFF0000EE, 0xFF0000DD, 0xFF0000BB, 0xFF0000AA, 0xFF000088, + 0xFF000077, 0xFF000055, 0xFF000044, 0xFF000022, 0xFF000011, 0xFFEEEEEE, 0xFFDDDDDD, 0xFFBBBBBB, + 0xFFAAAAAA, 0xFF888888, 0xFF777777, 0xFF555555, 0xFF444444, 0xFF222222, 0xFF111111, 0xFF000000, +}; + +const uint32_t Pomme::Graphics::clut4[16] = +{ + 0xffffffff, 0xfffcf305, 0xffff6402, 0xffdd0806, 0xfff20884, 0xff4600a5, 0xff0000d4, 0xff02abea, + 0xff1fb714, 0xff006411, 0xff562c05, 0xff90713a, 0xffc0c0c0, 0xff808080, 0xff404040, 0xff000000, +}; diff --git a/src/Input/SDLInput.cpp b/src/Input/SDLInput.cpp new file mode 100644 index 0000000..b69302d --- /dev/null +++ b/src/Input/SDLInput.cpp @@ -0,0 +1,191 @@ +#include + +#include "Pomme.h" +#include "PommeInput.h" + +#include +#include + +//----------------------------------------------------------------------------- +// Input + +static char scancodeLookupTable[256]; + +static void InitScancodeLookupTable() +{ + const unsigned char NO_MAC_VK = 0xFF; + + memset(scancodeLookupTable, NO_MAC_VK, sizeof(scancodeLookupTable)); + char* T = scancodeLookupTable; + + T[0] = NO_MAC_VK; // 0 + T[1] = NO_MAC_VK; + T[2] = NO_MAC_VK; + T[3] = NO_MAC_VK; + T[SDL_SCANCODE_A] = kVK_ANSI_A; + T[SDL_SCANCODE_B] = kVK_ANSI_B; + T[SDL_SCANCODE_C] = kVK_ANSI_C; + T[SDL_SCANCODE_D] = kVK_ANSI_D; + T[SDL_SCANCODE_E] = kVK_ANSI_E; + T[SDL_SCANCODE_F] = kVK_ANSI_F; + T[SDL_SCANCODE_G] = kVK_ANSI_G; // 10 + T[SDL_SCANCODE_H] = kVK_ANSI_H; + T[SDL_SCANCODE_I] = kVK_ANSI_I; + T[SDL_SCANCODE_J] = kVK_ANSI_J; + T[SDL_SCANCODE_K] = kVK_ANSI_K; + T[SDL_SCANCODE_L] = kVK_ANSI_L; + T[SDL_SCANCODE_M] = kVK_ANSI_M; + T[SDL_SCANCODE_N] = kVK_ANSI_N; + T[SDL_SCANCODE_O] = kVK_ANSI_O; + T[SDL_SCANCODE_P] = kVK_ANSI_P; + T[SDL_SCANCODE_Q] = kVK_ANSI_Q; // 20 + T[SDL_SCANCODE_R] = kVK_ANSI_R; + T[SDL_SCANCODE_S] = kVK_ANSI_S; + T[SDL_SCANCODE_T] = kVK_ANSI_T; + T[SDL_SCANCODE_U] = kVK_ANSI_U; + T[SDL_SCANCODE_V] = kVK_ANSI_V; + T[SDL_SCANCODE_W] = kVK_ANSI_W; + T[SDL_SCANCODE_X] = kVK_ANSI_X; + T[SDL_SCANCODE_Y] = kVK_ANSI_Y; + T[SDL_SCANCODE_Z] = kVK_ANSI_Z; + T[SDL_SCANCODE_1] = kVK_ANSI_1; // 30 + T[SDL_SCANCODE_2] = kVK_ANSI_2; + T[SDL_SCANCODE_3] = kVK_ANSI_3; + T[SDL_SCANCODE_4] = kVK_ANSI_4; + T[SDL_SCANCODE_5] = kVK_ANSI_5; + T[SDL_SCANCODE_6] = kVK_ANSI_6; + T[SDL_SCANCODE_7] = kVK_ANSI_7; + T[SDL_SCANCODE_8] = kVK_ANSI_8; + T[SDL_SCANCODE_9] = kVK_ANSI_9; + T[SDL_SCANCODE_0] = kVK_ANSI_0; + T[SDL_SCANCODE_RETURN] = kVK_Return; // 40 + T[SDL_SCANCODE_ESCAPE] = kVK_Escape; + T[SDL_SCANCODE_BACKSPACE] = kVK_Delete; + T[SDL_SCANCODE_TAB] = kVK_Tab; + T[SDL_SCANCODE_SPACE] = kVK_Space; + T[SDL_SCANCODE_MINUS] = kVK_ANSI_Minus; + T[SDL_SCANCODE_EQUALS] = kVK_ANSI_Equal; + T[SDL_SCANCODE_LEFTBRACKET] = kVK_ANSI_LeftBracket; + T[SDL_SCANCODE_RIGHTBRACKET]= kVK_ANSI_RightBracket; + T[SDL_SCANCODE_BACKSLASH] = kVK_ANSI_Backslash; + T[SDL_SCANCODE_NONUSHASH] = NO_MAC_VK; // 50 + T[SDL_SCANCODE_SEMICOLON] = kVK_ANSI_Semicolon; + T[SDL_SCANCODE_APOSTROPHE] = kVK_ANSI_Quote; + T[SDL_SCANCODE_GRAVE] = kVK_ANSI_Grave; + T[SDL_SCANCODE_COMMA] = kVK_ANSI_Comma; + T[SDL_SCANCODE_PERIOD] = kVK_ANSI_Period; + T[SDL_SCANCODE_SLASH] = kVK_ANSI_Slash; + T[SDL_SCANCODE_CAPSLOCK] = kVK_CapsLock; + T[SDL_SCANCODE_F1] = kVK_F1; + T[SDL_SCANCODE_F2] = kVK_F2; + T[SDL_SCANCODE_F3] = kVK_F3; // 60 + T[SDL_SCANCODE_F4] = kVK_F4; + T[SDL_SCANCODE_F5] = kVK_F5; + T[SDL_SCANCODE_F6] = kVK_F6; + T[SDL_SCANCODE_F7] = kVK_F7; + T[SDL_SCANCODE_F8] = kVK_F8; + T[SDL_SCANCODE_F9] = kVK_F9; + T[SDL_SCANCODE_F10] = kVK_F10; + T[SDL_SCANCODE_F11] = kVK_F11; + T[SDL_SCANCODE_F12] = kVK_F12; + T[SDL_SCANCODE_PRINTSCREEN] = NO_MAC_VK; // could be F13 // 70 + T[SDL_SCANCODE_SCROLLLOCK] = NO_MAC_VK; // could be F14 + T[SDL_SCANCODE_PAUSE] = NO_MAC_VK; // could be F15 + T[SDL_SCANCODE_INSERT] = kVK_Help; // 'help' is there on an ext'd kbd + T[SDL_SCANCODE_HOME] = kVK_Home; + T[SDL_SCANCODE_PAGEUP] = kVK_PageUp; + T[SDL_SCANCODE_DELETE] = kVK_ForwardDelete; + T[SDL_SCANCODE_END] = kVK_End; + T[SDL_SCANCODE_PAGEDOWN] = kVK_PageDown; + T[SDL_SCANCODE_RIGHT] = kVK_RightArrow; + T[SDL_SCANCODE_LEFT] = kVK_LeftArrow; // 80 + T[SDL_SCANCODE_DOWN] = kVK_DownArrow; + T[SDL_SCANCODE_UP] = kVK_UpArrow; + T[SDL_SCANCODE_NUMLOCKCLEAR]= kVK_ANSI_KeypadClear; // 83 + T[SDL_SCANCODE_KP_DIVIDE] = kVK_ANSI_KeypadDivide; // 84 + T[SDL_SCANCODE_KP_MULTIPLY] = kVK_ANSI_KeypadMultiply; + T[SDL_SCANCODE_KP_MINUS] = kVK_ANSI_KeypadMinus; + T[SDL_SCANCODE_KP_PLUS] = kVK_ANSI_KeypadPlus; + T[SDL_SCANCODE_KP_ENTER] = kVK_ANSI_KeypadEnter; + T[SDL_SCANCODE_KP_1] = kVK_ANSI_Keypad1; + T[SDL_SCANCODE_KP_2] = kVK_ANSI_Keypad2; // 90 + T[SDL_SCANCODE_KP_3] = kVK_ANSI_Keypad3; + T[SDL_SCANCODE_KP_4] = kVK_ANSI_Keypad4; + T[SDL_SCANCODE_KP_5] = kVK_ANSI_Keypad5; + T[SDL_SCANCODE_KP_6] = kVK_ANSI_Keypad6; + T[SDL_SCANCODE_KP_7] = kVK_ANSI_Keypad7; + T[SDL_SCANCODE_KP_8] = kVK_ANSI_Keypad8; + T[SDL_SCANCODE_KP_9] = kVK_ANSI_Keypad9; + T[SDL_SCANCODE_KP_0] = kVK_ANSI_Keypad0; + T[SDL_SCANCODE_KP_PERIOD] = kVK_ANSI_KeypadDecimal; + T[SDL_SCANCODE_NONUSBACKSLASH] = NO_MAC_VK; // 100 + T[SDL_SCANCODE_APPLICATION] = NO_MAC_VK; + T[SDL_SCANCODE_POWER] = NO_MAC_VK; + T[SDL_SCANCODE_KP_EQUALS] = kVK_ANSI_KeypadEquals; // 103 + T[SDL_SCANCODE_F13] = kVK_F13; // 104 + T[SDL_SCANCODE_F14] = kVK_F14; + T[SDL_SCANCODE_F15] = kVK_F15; + T[SDL_SCANCODE_F16] = kVK_F16; + T[SDL_SCANCODE_F17] = kVK_F17; + T[SDL_SCANCODE_F18] = kVK_F18; + T[SDL_SCANCODE_F19] = kVK_F19; // 110 + T[SDL_SCANCODE_F20] = kVK_F20; // 111 + T[SDL_SCANCODE_F21] = NO_MAC_VK; + T[SDL_SCANCODE_F22] = NO_MAC_VK; + T[SDL_SCANCODE_F23] = NO_MAC_VK; + T[SDL_SCANCODE_F24] = NO_MAC_VK; // 115 + T[SDL_SCANCODE_HELP] = kVK_Help; // also INSERT + + // --snip-- + + T[SDL_SCANCODE_LCTRL] = kVK_Control; // 224 + T[SDL_SCANCODE_LSHIFT] = kVK_Shift; + T[SDL_SCANCODE_LALT] = kVK_Option; + T[SDL_SCANCODE_LGUI] = kVK_Command; + T[SDL_SCANCODE_RCTRL] = kVK_RightControl; + T[SDL_SCANCODE_RSHIFT] = kVK_RightShift; + T[SDL_SCANCODE_RALT] = kVK_RightOption; + T[SDL_SCANCODE_RGUI] = kVK_Command; // no right command I guess +} + +void GetKeys(KeyMap km) +{ + km[0] = 0; + km[1] = 0; + km[2] = 0; + km[3] = 0; + + SDL_PumpEvents(); + int numkeys = 0; + const UInt8* keystate = SDL_GetKeyboardState(&numkeys); + + numkeys = std::min((int) sizeof(scancodeLookupTable), numkeys); + + for (int sdlScancode = 0; sdlScancode < numkeys; sdlScancode++) + { + if (!keystate[sdlScancode]) + continue; + int vk = scancodeLookupTable[sdlScancode]; + if (vk == 0xFF) + continue; + int byteNo = vk >> 5; // =vk/32 + int bitNo = vk & 31; + km[byteNo] |= 1 << bitNo; + } + +#if POMME_DEBUG_INPUT + if (km[0] || km[1] || km[2] || km[3]) + printf("GK %08x%08x%08x%08x\n", km[0], km[1], km[2], km[3]); +#endif +} + +Boolean Button(void) +{ + ONCE(TODOMINOR()); + return false; +} + +void Pomme::Input::Init() +{ + InitScancodeLookupTable(); +} diff --git a/src/Memory/Memory.cpp b/src/Memory/Memory.cpp new file mode 100644 index 0000000..61279cb --- /dev/null +++ b/src/Memory/Memory.cpp @@ -0,0 +1,185 @@ +#include +#include +#include + +#include "Pomme.h" +#include "Utilities/FixedPool.h" + +#define LOG POMME_GENLOG(POMME_DEBUG_MEMORY, "MEMO") + +//----------------------------------------------------------------------------- +// Implementation-specific stuff + +static const int HANDLE_MAGIC_LENGTH = 8; +static const char HANDLE_MAGIC[HANDLE_MAGIC_LENGTH] = "LIVEhdl"; +static const char HANDLE_MAGIC_DEAD[HANDLE_MAGIC_LENGTH] = "DEADhdl"; + +struct BlockDescriptor +{ + Ptr buf; + char magic[HANDLE_MAGIC_LENGTH]; + Size size; +}; + +// these must not move around +static Pomme::FixedPool blocks; + +static BlockDescriptor* HandleToBlock(Handle h) +{ + auto bd = (BlockDescriptor*) h; + if (0 != memcmp(bd->magic, HANDLE_MAGIC, HANDLE_MAGIC_LENGTH)) + throw std::runtime_error("corrupted handle"); + return bd; +} + +//----------------------------------------------------------------------------- +// Memory: Handle + +Handle NewHandle(Size s) +{ + if (s < 0) throw std::invalid_argument("trying to alloc negative size handle"); + + BlockDescriptor* block = blocks.Alloc(); + block->buf = new char[s]; + memcpy(block->magic, HANDLE_MAGIC, HANDLE_MAGIC_LENGTH); + block->size = s; + + if ((Ptr) &block->buf != (Ptr) block) + throw std::runtime_error("buffer address mismatches block address"); + + LOG << (void*) block->buf << ", size " << s << "\n"; + + return &block->buf; +} + +Handle NewHandleClear(Size s) +{ + Handle h = NewHandle(s); + memset(*h, 0, s); + return h; +} + +Handle TempNewHandle(Size s, OSErr* err) +{ + Handle h = NewHandle(s); + *err = noErr; + return h; +} + +Size GetHandleSize(Handle h) +{ + return HandleToBlock(h)->size; +} + +void SetHandleSize(Handle handle, Size byteCount) +{ + TODOFATAL(); +} + +void DisposeHandle(Handle h) +{ + LOG << (void*) *h << "\n"; + BlockDescriptor* b = HandleToBlock(h); + delete[] b->buf; + b->buf = 0; + b->size = -1; + memcpy(b->magic, HANDLE_MAGIC_DEAD, HANDLE_MAGIC_LENGTH); + blocks.Dispose(b); +} + +//----------------------------------------------------------------------------- +// Memory: Ptr + +Ptr NewPtr(Size byteCount) +{ + if (byteCount < 0) throw std::invalid_argument("trying to NewPtr negative size"); + return new char[byteCount]; +} + +Ptr NewPtrSys(Size byteCount) +{ + if (byteCount < 0) throw std::invalid_argument("trying to NewPtrSys negative size"); + return new char[byteCount]; +} + +void DisposePtr(Ptr p) +{ + delete[] p; +} + +//----------------------------------------------------------------------------- +// Memory: BlockMove + +void BlockMove(const void* srcPtr, void* destPtr, Size byteCount) +{ + memcpy(destPtr, srcPtr, byteCount); +} + +void BlockMoveData(const void* srcPtr, void* destPtr, Size byteCount) +{ + TODOFATAL(); +} + +//----------------------------------------------------------------------------- +// No-op memory junk + +void MaxApplZone(void) +{ + // No-op +} + +void MoreMasters(void) +{ + // No-op +} + +Size CompactMem(Size) +{ + // No-op + // TODO: what should we actually return? + return 0; +} + +Size CompactMemSys(Size) +{ + // No-op + // TODO: what should we actually return? + return 0; +} + +void PurgeMem(Size) +{ + // No-op +} + +void PurgeMemSys(Size) +{ + // No-op +} + +Size MaxMem(Size*) +{ + // No-op + // TODO: what should we actually return? + return 0; +} + +void HNoPurge(Handle) +{ + // No-op +} + +void HLock(Handle) +{ + // No-op +} + +void HLockHi(Handle) +{ + // No-op +} + +void NoPurgePixels(PixMapHandle) +{ + // No-op +} diff --git a/src/Platform/Windows/PommeWindows.cpp b/src/Platform/Windows/PommeWindows.cpp new file mode 100644 index 0000000..674ac36 --- /dev/null +++ b/src/Platform/Windows/PommeWindows.cpp @@ -0,0 +1,17 @@ +#include "Platform/Windows/PommeWindows.h" + +#include + +std::filesystem::path Pomme::Platform::Windows::GetPreferencesFolder() +{ + wchar_t* wpath = nullptr; + SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &wpath); + auto path = std::filesystem::path(wpath); + CoTaskMemFree(static_cast(wpath)); + return path; +} + +void Pomme::Platform::Windows::SysBeep() +{ + MessageBeep(0); +} diff --git a/src/Platform/Windows/PommeWindows.h b/src/Platform/Windows/PommeWindows.h new file mode 100644 index 0000000..daf35d4 --- /dev/null +++ b/src/Platform/Windows/PommeWindows.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace Pomme::Platform::Windows +{ + std::filesystem::path GetPreferencesFolder(); + + void SysBeep(); +} diff --git a/src/Pomme.cpp b/src/Pomme.cpp new file mode 100644 index 0000000..3d30a98 --- /dev/null +++ b/src/Pomme.cpp @@ -0,0 +1,82 @@ +#include +#include + +#include "Pomme.h" +#include "PommeInit.h" +#include "PommeTime.h" +#include "PommeFiles.h" +#include "PommeGraphics.h" +#include "PommeSound.h" +#include "PommeInput.h" + +#include "SDL.h" + +#if _WIN32 + #include "Platform/Windows/PommeWindows.h" +#endif + +//----------------------------------------------------------------------------- +// Our own utils + +const char* Pomme::QuitRequest::what() const noexcept +{ + return "the user has requested to quit the application"; +} + +//----------------------------------------------------------------------------- +// Misc + +void ExitToShell() +{ + throw Pomme::QuitRequest(); +} + +void SysBeep(short duration) +{ +#ifdef _WIN32 + Pomme::Platform::Windows::SysBeep(); +#else + TODOMINOR(); +#endif +} + +void FlushEvents(short, short) +{ + TODOMINOR(); +} + +void NumToStringC(long theNum, Str255 theString) +{ + snprintf(theString, 256, "%ld", theNum); +} + +//----------------------------------------------------------------------------- +// Mouse cursor + +void InitCursor() +{ + SDL_ShowCursor(1); +} + +void HideCursor() +{ + SDL_ShowCursor(0); +} + +//----------------------------------------------------------------------------- +// Our own init + +void Pomme::Init(const char* windowName) +{ + Pomme::Time::Init(); + Pomme::Files::Init(); + Pomme::Graphics::Init(windowName, 640, 480); + Pomme::Sound::Init(); + Pomme::Input::Init(); +} + +void Pomme::Shutdown() +{ + Pomme::Sound::Shutdown(); + Pomme::Graphics::Shutdown(); +} diff --git a/src/Pomme.h b/src/Pomme.h new file mode 100644 index 0000000..2d12fe5 --- /dev/null +++ b/src/Pomme.h @@ -0,0 +1,336 @@ +#pragma once + +#include "PommeTypes.h" +#include "PommeEnums.h" +#include "PommeDebug.h" + +//----------------------------------------------------------------------------- +// Structure unpacking + +#include "Utilities/structpack.h" + +//----------------------------------------------------------------------------- +// PowerPC intrinsics + +#define __fres(x) (1.0f/x) +#define __fabs(x) fabs(x) +#define __frsqrte(x) (1.0f/sqrtf(x)) + +//----------------------------------------------------------------------------- +// Source code compat + +#define nil NULL + +#ifdef __cplusplus +extern "C" +{ +#endif + +//----------------------------------------------------------------------------- +// FSSpec + +OSErr FSMakeFSSpec(short vRefNum, long dirID, const char* cstrFileName, FSSpec* spec); + +short FSpOpenResFile(const FSSpec* spec, char permission); + +// Open a file's data fork +OSErr FSpOpenDF(const FSSpec* spec, char permission, short* refNum); + +// Open a file's resource fork +OSErr FSpOpenRF(const FSSpec* spec, char permission, short* refNum); + +OSErr FSpCreate(const FSSpec* spec, OSType creator, OSType fileType, ScriptCode scriptTag); + +OSErr FSpDelete(const FSSpec* spec); + +OSErr ResolveAlias(const FSSpec* spec, AliasHandle alias, FSSpec* target, Boolean* wasChanged); + +OSErr FindFolder(short vRefNum, OSType folderType, Boolean createFolder, short* foundVRefNum, long* foundDirID); + +OSErr DirCreate(short vRefNum, long parentDirID, const char* cstrDirectoryName, long* createdDirID); + +//----------------------------------------------------------------------------- +// File I/O + +OSErr FSRead(short refNum, long* count, Ptr buffPtr); + +OSErr FSWrite(short refNum, long* count, Ptr buffPtr); + +OSErr FSClose(short refNum); + +OSErr GetEOF(short refNum, long* logEOF); + +OSErr SetEOF(short refNum, long logEOF); + +//----------------------------------------------------------------------------- +// Resource file management + +// MoreMacintoshToolbox.pdf p174 +OSErr ResError(void); + +void UseResFile(short refNum); + +// Gets the file reference number of the current resource file. +short CurResFile(); + +void CloseResFile(short refNum); + +// Returns total number of resources of the given type +// in the current resource file only. +short Count1Resources(ResType); + +Handle GetResource(ResType theType, short theID); + +void ReleaseResource(Handle theResource); + +void RemoveResource(Handle theResource); + +void AddResource(Handle theData, ResType theType, short theID, const char* name); + +void WriteResource(Handle theResource); + +void DetachResource(Handle theResource); + +long GetResourceSizeOnDisk(Handle); + +long SizeResource(Handle); + +//----------------------------------------------------------------------------- +// QuickDraw 2D + +void SetRect(Rect* r, short left, short top, short right, short bottom); + +PicHandle GetPicture(short PICTresourceID); + +void DisposeGWorld(GWorldPtr offscreenGWorld); + +// IM:QD:6-16 +QDErr NewGWorld( + GWorldPtr* offscreenGWorld, + short pixelDepth, + const Rect* boundsRect, + void* junk1, // CTabHandle cTable + void* junk2, // GDHandle aGDevice + long junk3 // long flags +); + +void GetGWorld(CGrafPtr* port, GDHandle* gdh); + +void SetGWorld(CGrafPtr port, GDHandle gdh); + +void SetPort(GrafPtr port); + +void GetPort(GrafPtr* port); + +void MoveTo(short h, short v); + +void GetForeColor(RGBColor* rgb); + +void ForeColor(long color); + +void BackColor(long color); + +void RGBBackColor(const RGBColor* color); + +void RGBForeColor(const RGBColor* color); + +// Pomme extension (not part of the original Toolbox API). +void RGBBackColor2(UInt32 color); + +// Pomme extension (not part of the original Toolbox API). +void RGBForeColor2(UInt32 color); + +void PaintRect(const Rect* r); + +void EraseRect(const Rect* r); + +void LineTo(short h, short v); + +void FrameRect(const Rect*); + +//short TextWidth(const char* textBuf, short firstByte, short byteCount); + +short TextWidthC(const char* cstr); + +void DrawChar(char c); + +void DrawStringC(const char* cstr); + +// IM:QD:7-44 +void DrawPicture(PicHandle myPicture, const Rect* dstRect); + +// IM:QD:6-31 +PixMapHandle GetGWorldPixMap(GWorldPtr offscreenGWorld); + +// IM:QD:6-38 +Ptr GetPixBaseAddr(PixMapHandle pm); + +void CopyBits( + const PixMap* srcBits, + PixMap* dstBits, + const Rect* srcRect, + const Rect* dstRect, + short mode, + void* maskRgn +); + +//----------------------------------------------------------------------------- +// QuickDraw 2D extensions + +// Returns true if the current port is "damaged". +// Pomme extension (not part of the original Toolbox API). +Boolean IsPortDamaged(void); + +// Stores current port's damaged region into "r". +// You should only call this after having checked that IsPortDamaged() is true. +// Pomme extension (not part of the original Toolbox API). +void GetPortDamageRegion(Rect* r); + +// Sets current port as undamaged. +// Pomme extension (not part of the original Toolbox API). +void ClearPortDamage(void); + +// Extends the current port's damage region to include the given rectangle. +// Pomme extension (not part of the original Toolbox API). +void DamagePortRegion(const Rect*); + +// Writes the current port to a Targa image. +// Pomme extension (not part of the original Toolbox API). +void DumpPortTGA(const char* path); + +//----------------------------------------------------------------------------- +// Misc + +void ExitToShell(); + +void SysBeep(short duration); + +void FlushEvents(short, short); + +void NumToStringC(long theNum, Str255 theString); + +//----------------------------------------------------------------------------- +// Input + +void GetKeys(KeyMap); + +Boolean Button(void); + +//----------------------------------------------------------------------------- +// Memory: No-op + +void MaxApplZone(void); + +void MoreMasters(void); + +Size CompactMem(Size); + +// Compact system heap zone manually. +Size CompactMemSys(Size); + +void PurgeMem(Size); + +void PurgeMemSys(Size); + +Size MaxMem(Size* grow); + +void HNoPurge(Handle); + +void HLock(Handle); + +void HLockHi(Handle); + +void NoPurgePixels(PixMapHandle); + +// To prevent the base address for an offscreen pixel image from being moved +// while you draw into or copy from its pixel map. +Boolean LockPixels(PixMapHandle); // shall return true always + +//----------------------------------------------------------------------------- +// Memory: Handle + +Handle NewHandle(Size); + +// Allocate prezeroed memory +Handle NewHandleClear(Size); + +// Allocate temp memory +Handle TempNewHandle(Size, OSErr*); + +Size GetHandleSize(Handle); + +// Change the logical size of the relocatable block corresponding to a handle +void SetHandleSize(Handle, Size); + +void DisposeHandle(Handle); + +//----------------------------------------------------------------------------- +// Memory: Ptr + +Ptr NewPtr(Size); + +Ptr NewPtrSys(Size); + +void DisposePtr(Ptr p); + +//----------------------------------------------------------------------------- +// Memory: BlockMove + +// Copies a sequence of bytes from one location in memory to another +void BlockMove(const void* srcPtr, void* destPtr, Size byteCount); + +void BlockMoveData(const void* srcPtr, void* destPtr, Size byteCount); + +//----------------------------------------------------------------------------- +// Time Manager + +// Number of seconds elapsed since 1904-01-01 00:00 +void GetDateTime(unsigned long* secs); + +// Number of usecs elapsed since system startup +void Microseconds(UnsignedWide* microTickCount); + +// Number of ticks elapsed since system startup (1 tick = approx. 1/60 of a second) +UInt32 TickCount(); + +//----------------------------------------------------------------------------- +// Mouse cursor + +void HideCursor(); + +void InitCursor(); + +//----------------------------------------------------------------------------- +// Sound Manager + +OSErr GetDefaultOutputVolume(long*); + +OSErr SetDefaultOutputVolume(long); + +OSErr SndNewChannel(SndChannelPtr* chan, short synth, long init, SndCallBackProcPtr userRoutine); + +OSErr SndDisposeChannel(SndChannelPtr chan, Boolean quietNow); + +OSErr SndChannelStatus(SndChannelPtr chan, short theLength, SCStatusPtr theStatus); + +OSErr SndDoImmediate(SndChannelPtr chan, const SndCommand* cmd); + +OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset); + +OSErr SndStartFilePlay(SndChannelPtr chan, short fRefNum, short resNum, long bufferSize, Ptr theBuffer, /*AudioSelectionPtr*/ void* theSelection, FilePlayCompletionUPP theCompletion, Boolean async); + +OSErr SndPauseFilePlay(SndChannelPtr chan); + +OSErr SndStopFilePlay(SndChannelPtr chan, Boolean quietNow); + +NumVersion SndSoundManagerVersion(); + +// Pomme extension +Boolean Pomme_DecompressSoundResource(SndListHandle* sndHandlePtr, long* offsetToHeader); + +// Pomme extension +void Pomme_PauseLoopingChannels(Boolean pause); + +#ifdef __cplusplus +} +#endif diff --git a/src/PommeDebug.cpp b/src/PommeDebug.cpp new file mode 100644 index 0000000..3ea46a4 --- /dev/null +++ b/src/PommeDebug.cpp @@ -0,0 +1,57 @@ +#include "PommeDebug.h" + +#include +#include +#include + +void ImplementMe(const char* fn, std::string msg, int severity) +{ + if (severity >= 0) + { + std::stringstream ss; + ss << "[TODO] \x1b[1m" << fn << "\x1b[22m"; + if (!msg.empty()) + { + ss << ": " << msg; + } + auto str = ss.str(); + std::cerr << (severity > 0 ? "\x1b[31m" : "\x1b[33m") << str << "\x1b[0m\n"; + } + + if (severity >= 2) + { + std::stringstream ss; + ss << fn << "()"; + if (!msg.empty()) ss << "\n" << msg; + + auto str = ss.str(); + + int mbflags = SDL_MESSAGEBOX_ERROR; + if (severity == 0) mbflags = SDL_MESSAGEBOX_INFORMATION; + if (severity == 1) mbflags = SDL_MESSAGEBOX_WARNING; + + SDL_ShowSimpleMessageBox(mbflags, "Source port TODO", str.c_str(), nullptr); + } + + if (severity >= 2) + { + abort(); + } +} + +std::string Pomme::FourCCString(uint32_t t, char filler) +{ + char b[5]; + *(uint32_t*) b = t; +#if !(TARGET_RT_BIGENDIAN) + std::reverse(b, b + 4); +#endif + // replace non-ascii with '?' + for (int i = 0; i < 4; i++) + { + char c = b[i]; + if (c < ' ' || c > '~') b[i] = filler; + } + b[4] = '\0'; + return b; +} diff --git a/src/PommeDebug.h b/src/PommeDebug.h new file mode 100644 index 0000000..b16b5b3 --- /dev/null +++ b/src/PommeDebug.h @@ -0,0 +1,51 @@ +#pragma once + +#if __cplusplus + +#include +#include + +#define POMME_DEBUG_MEMORY false +#define POMME_DEBUG_SOUND false +#define POMME_DEBUG_PICT false +#define POMME_DEBUG_FILES false +#define POMME_DEBUG_RESOURCES false +#define POMME_DEBUG_INPUT false + +#define POMME_GENLOG(define, prefix) if (!define) {} else std::cout << "[" << prefix << "] " << __func__ << ":\t" +#define POMME_GENLOG_NOPREFIX(define) if (!define) {} else std::cout + +namespace Pomme +{ + std::string FourCCString(uint32_t t, char filler = '?'); +} + +//----------------------------------------------------------------------------- +// My todos + +void ImplementMe(const char* fn, std::string msg, int severity); + +#define TODOCUSTOM(message, severity) { \ + std::stringstream ss; \ + ss << message; \ + ImplementMe(__func__, ss.str(), severity); \ +} + +#define TODOMINOR() TODOCUSTOM("", 0) +#define TODOMINOR2(x) TODOCUSTOM(x, 0) +#define TODO() TODOCUSTOM("", 1) +#define TODO2(x) TODOCUSTOM(x, 1) +#define TODOFATAL() TODOCUSTOM("", 2) +#define TODOFATAL2(x) TODOCUSTOM(x, 2) + +#define ONCE(x) { \ + static bool once = false; \ + if (!once) { \ + once = true; \ + {x} \ + printf(" \x1b[90m\\__ this todo won't be shown again\x1b[0m\n"); \ + } \ +} + + +#endif diff --git a/src/PommeEnums.h b/src/PommeEnums.h new file mode 100644 index 0000000..3e73a45 --- /dev/null +++ b/src/PommeEnums.h @@ -0,0 +1,397 @@ +#pragma once + +//----------------------------------------------------------------------------- +// Filesystem permission char + +enum EFSPermissions +{ + fsCurPerm = 0, + fsRdPerm = 1, + fsWrPerm = 2, + fsRdWrPerm = 3, +}; + +//----------------------------------------------------------------------------- +// Folder types + +enum +{ + kSystemFolderType = 'macs', + kDesktopFolderType = 'desk', + kSystemDesktopFolderType = 'sdsk', + kTrashFolderType = 'trsh', + kSystemTrashFolderType = 'strs', + kWhereToEmptyTrashFolderType = 'empt', + kPrintMonitorDocsFolderType = 'prnt', + kStartupFolderType = 'strt', + kShutdownFolderType = 'shdf', + kAppleMenuFolderType = 'amnu', + kControlPanelFolderType = 'ctrl', + kSystemControlPanelFolderType = 'sctl', + kExtensionFolderType = 'extn', + kFontsFolderType = 'font', + kPreferencesFolderType = 'pref', + kSystemPreferencesFolderType = 'sprf', + kTemporaryFolderType = 'temp' +}; + +enum { + kOnSystemDisk = -32768L, + kOnAppropriateDisk = -32767, + kSystemDomain = -32766, + kLocalDomain = -32765, + kNetworkDomain = -32764, + kUserDomain = -32763, + kClassicDomain = -32762 +}; + +#define kCreateFolder true +#define kDontCreateFolder false + +//----------------------------------------------------------------------------- +// Error codes (OSErr) + +enum EErrors +{ + noErr = 0, + + unimpErr = -4, // unimplemented core routine + + controlErr = -17, // I/O System Errors + statusErr = -18, // I/O System Errors + readErr = -19, // I/O System Errors + writErr = -20, // I/O System Errors + badUnitErr = -21, // I/O System Errors + unitEmptyErr = -22, // I/O System Errors + openErr = -23, // I/O System Errors + closErr = -24, // I/O System Errors + + abortErr = -27, // IO call aborted by KillIO + notOpenErr = -28, // Couldn't rd/wr/ctl/sts cause driver not opened + dirFulErr = -33, // File directory full + dskFulErr = -34, // Disk or volume full + nsvErr = -35, // Volume doesn't exist + ioErr = -36, // I/O error + bdNamErr = -37, // Bad file or volume name + fnOpnErr = -38, // File not open + eofErr = -39, // End-of-file reached + posErr = -40, // Attempt to position mark before start of file + mFulErr = -41, // Memory full (open) or file won't fit (load) + tmfoErr = -42, // Too many files open + fnfErr = -43, // File not found (FSSpec is still valid) + wPrErr = -44, // Volume is hardware-locked + fLckdErr = -45, // File is locked + vLckdErr = -46, // Volume is software-locked + fBsyErr = -47, // File is busy + dupFNErr = -48, // Duplicate filename + opWrErr = -49, // File already open for writing + paramErr = -50, // Invalid value passed in parameter + rfNumErr = -51, // Invalid reference number + gfpErr = -52, // Error during a GetFPos family function + volOffLinErr = -53, // Volume is offline + permErr = -54, // Permission error + volOnLinErr = -55, // Volume already online + nsDrvErr = -56, // No such Drive + noMacDskErr = -57, // Foreign disk + extFSErr = -58, // Volume belongs to external file system + fsRnErr = -59, // Couldn't rename + badMDBErr = -60, // Bad master directory block + wrPermErr = -61, // Read/write permission doesn't allow writing + + memROZWarn = -99, // soft error in ROZ + memROZError = -99, // hard error in ROZ + memROZErr = -99, // hard error in ROZ + memFullErr = -108, // Not enough room in heap zone + nilHandleErr = -109, // Master Pointer was NIL in HandleZone or other + memWZErr = -111, // WhichZone failed (applied to free block) + memPurErr = -112, // trying to purge a locked or non-purgeable block + memAdrErr = -110, // address was odd; or out of range + memAZErr = -113, // Address in zone check failed + memPCErr = -114, // Pointer Check failed + memBCErr = -115, // Block Check failed + memSCErr = -116, // Size Check failed + memLockedErr = -117, // trying to move a locked block (MoveHHi) + + dirNFErr = -120, // Directory not found + tmwdoErr = -121, // Too many working directories open + badMovErr = -122, // Couldn't move + wrgVolTypErr = -123, // Unrecognized volume (not HFS) + volGoneErr = -124, // Server volume disconnected + fsDSIntErr = -127, // Non-hardware internal file system error + + userCanceledErr = -128, + + badExtResource = -185, // extended resource has a bad format + CantDecompress = -186, // resource bent ("the bends") - can't decompress a compressed resource + resourceInMemory= -188, // Resource already in memory + writingPastEnd = -189, // Writing past end of file + inputOutOfBounds= -190, // Offset of Count out of bounds + resNotFound = -192, // Resource not found + resFNotFound = -193, // Resource file not found + addResFailed = -194, // AddResource failed + addRefFailed = -195, // AddReference failed + rmvResFailed = -196, // RmveResource failed + rmvRefFailed = -197, // RmveReference failed + resAttrErr = -198, // attribute inconsistent with operation + mapReadErr = -199, // map inconsistent with operation + + // Sound Manager error codes + + notEnoughHardwareErr = -201, + queueFull = -203, + resProblem = -204, + badChannel = -205, // channel is corrupt or unusable + badFormat = -206, // resource is corrupt or unusable + notEnoughBufferSpace = -207, // insufficient memory available + badFileFormat = -208, // file is corrupt or unusable, or not AIFF or AIFF-C + channelBusy = -209, + buffersTooSmall = -210, + siInvalidCompression = -223, +}; + + +//----------------------------------------------------------------------------- +// Script Manager enums + +enum EScriptManager +{ + smSystemScript = -1, + smCurrentScript = -2, + smAllScripts = -3 +}; + +//----------------------------------------------------------------------------- + +enum EEvents +{ + everyEvent = ~0 +}; + +//----------------------------------------------------------------------------- +// Memory enums + +enum EMemory +{ + maxSize = 0x7FFFFFF0 // the largest block possible +}; + +//----------------------------------------------------------------------------- +// Resource types + +enum EResTypes +{ + rAliasType = 'alis', +}; + +//----------------------------------------------------------------------------- +// Sound Manager enums + +enum ESndPitch +{ + kMiddleC = 60L, +}; + +enum ESndSynth +{ + squareWaveSynth = 1, + waveTableSynth = 3, + sampledSynth = 5 +}; + +enum ESndInit +{ + initChanLeft = 0x0002, // left stereo channel + initChanRight = 0x0003, // right stereo channel + initMono = 0x0080, // monophonic channel + initStereo = 0x00C0, // stereo channel + initMACE3 = 0x0300, // 3:1 compression + initMACE6 = 0x0400, // 6:1 compression + initNoInterp = 0x0004, // no linear interpolation + initNoDrop = 0x0008, // no drop-sample conversion +}; + +// Sound commands +enum ESndCmds +{ + nullCmd = 0, + initCmd = 1, + freeCmd = 2, + quietCmd = 3, + flushCmd = 4, + reInitCmd = 5, + waitCmd = 10, + pauseCmd = 11, + resumeCmd = 12, + callBackCmd = 13, + syncCmd = 14, + availableCmd = 24, + versionCmd = 25, + totalLoadCmd = 26, + loadCmd = 27, + freqDurationCmd = 40, + restCmd = 41, + freqCmd = 42, + ampCmd = 43, + timbreCmd = 44, + getAmpCmd = 45, + volumeCmd = 46, // sound manager 3.0 or later only + getVolumeCmd = 47, // sound manager 3.0 or later only + clockComponentCmd = 50, // sound manager 3.2.1 or later only + getClockComponentCmd = 51, // sound manager 3.2.1 or later only + scheduledSoundCmd = 52, // sound manager 3.3 or later only + linkSoundComponentsCmd = 53, // sound manager 3.3 or later only + waveTableCmd = 60, + phaseCmd = 61, + soundCmd = 80, + bufferCmd = 81, + rateCmd = 82, + continueCmd = 83, + doubleBufferCmd = 84, + getRateCmd = 85, + rateMultiplierCmd = 86, + getRateMultiplierCmd = 87, + sizeCmd = 90, // obsolete command + convertCmd = 91, // obsolete MACE command + pommeSetLoopCmd = 0x7001, + // Do not define commands above 0x7FFF -- the high bit means a 'snd ' resource has associated sound data +}; + +//----------------------------------------------------------------------------- +// Keyboard enums + +enum +{ + // key positions on US keyboard + kVK_ANSI_A = 0x00, + kVK_ANSI_S = 0x01, + kVK_ANSI_D = 0x02, + kVK_ANSI_F = 0x03, + kVK_ANSI_H = 0x04, + kVK_ANSI_G = 0x05, + kVK_ANSI_Z = 0x06, + kVK_ANSI_X = 0x07, + kVK_ANSI_C = 0x08, + kVK_ANSI_V = 0x09, + kVK_ANSI_B = 0x0B, + kVK_ANSI_Q = 0x0C, + kVK_ANSI_W = 0x0D, + kVK_ANSI_E = 0x0E, + kVK_ANSI_R = 0x0F, + kVK_ANSI_Y = 0x10, + kVK_ANSI_T = 0x11, + kVK_ANSI_1 = 0x12, + kVK_ANSI_2 = 0x13, + kVK_ANSI_3 = 0x14, + kVK_ANSI_4 = 0x15, + kVK_ANSI_6 = 0x16, + kVK_ANSI_5 = 0x17, + kVK_ANSI_Equal = 0x18, + kVK_ANSI_9 = 0x19, + kVK_ANSI_7 = 0x1A, + kVK_ANSI_Minus = 0x1B, + kVK_ANSI_8 = 0x1C, + kVK_ANSI_0 = 0x1D, + kVK_ANSI_RightBracket = 0x1E, + kVK_ANSI_O = 0x1F, + kVK_ANSI_U = 0x20, + kVK_ANSI_LeftBracket = 0x21, + kVK_ANSI_I = 0x22, + kVK_ANSI_P = 0x23, + kVK_ANSI_L = 0x25, + kVK_ANSI_J = 0x26, + kVK_ANSI_Quote = 0x27, + kVK_ANSI_K = 0x28, + kVK_ANSI_Semicolon = 0x29, + kVK_ANSI_Backslash = 0x2A, + kVK_ANSI_Comma = 0x2B, + kVK_ANSI_Slash = 0x2C, + kVK_ANSI_N = 0x2D, + kVK_ANSI_M = 0x2E, + kVK_ANSI_Period = 0x2F, + kVK_ANSI_Grave = 0x32, + kVK_ANSI_KeypadDecimal = 0x41, + kVK_ANSI_KeypadMultiply = 0x43, + kVK_ANSI_KeypadPlus = 0x45, + kVK_ANSI_KeypadClear = 0x47, + kVK_ANSI_KeypadDivide = 0x4B, + kVK_ANSI_KeypadEnter = 0x4C, + kVK_ANSI_KeypadMinus = 0x4E, + kVK_ANSI_KeypadEquals = 0x51, + kVK_ANSI_Keypad0 = 0x52, + kVK_ANSI_Keypad1 = 0x53, + kVK_ANSI_Keypad2 = 0x54, + kVK_ANSI_Keypad3 = 0x55, + kVK_ANSI_Keypad4 = 0x56, + kVK_ANSI_Keypad5 = 0x57, + kVK_ANSI_Keypad6 = 0x58, + kVK_ANSI_Keypad7 = 0x59, + kVK_ANSI_Keypad8 = 0x5B, + kVK_ANSI_Keypad9 = 0x5C +}; + +// keycodes for keys that are independent of keyboard layout +enum +{ + kVK_Return = 0x24, + kVK_Tab = 0x30, + kVK_Space = 0x31, + kVK_Delete = 0x33, + kVK_Escape = 0x35, + kVK_Command = 0x37, + kVK_Shift = 0x38, + kVK_CapsLock = 0x39, + kVK_Option = 0x3A, + kVK_Control = 0x3B, + kVK_RightShift = 0x3C, + kVK_RightOption = 0x3D, + kVK_RightControl = 0x3E, + kVK_Function = 0x3F, + kVK_F17 = 0x40, + kVK_VolumeUp = 0x48, + kVK_VolumeDown = 0x49, + kVK_Mute = 0x4A, + kVK_F18 = 0x4F, + kVK_F19 = 0x50, + kVK_F20 = 0x5A, + kVK_F5 = 0x60, + kVK_F6 = 0x61, + kVK_F7 = 0x62, + kVK_F3 = 0x63, + kVK_F8 = 0x64, + kVK_F9 = 0x65, + kVK_F11 = 0x67, + kVK_F13 = 0x69, + kVK_F16 = 0x6A, + kVK_F14 = 0x6B, + kVK_F10 = 0x6D, + kVK_F12 = 0x6F, + kVK_F15 = 0x71, + kVK_Help = 0x72, + kVK_Home = 0x73, + kVK_PageUp = 0x74, + kVK_ForwardDelete = 0x75, + kVK_F4 = 0x76, + kVK_End = 0x77, + kVK_F2 = 0x78, + kVK_PageDown = 0x79, + kVK_F1 = 0x7A, + kVK_LeftArrow = 0x7B, + kVK_RightArrow = 0x7C, + kVK_DownArrow = 0x7D, + kVK_UpArrow = 0x7E +}; + +//----------------------------------------------------------------------------- +// QD2D + +enum +{ + whiteColor = 30, + blackColor = 33, + yellowColor = 69, + magentaColor = 137, + redColor = 205, + cyanColor = 273, + greenColor = 341, + blueColor = 409, +}; diff --git a/src/PommeFiles.h b/src/PommeFiles.h new file mode 100644 index 0000000..28abb48 --- /dev/null +++ b/src/PommeFiles.h @@ -0,0 +1,41 @@ +#pragma once + +#include "PommeTypes.h" + +#include +#include +#include "CompilerSupport/filesystem.h" + +namespace Pomme::Files +{ + struct ResourceMetadata + { + short forkRefNum; + OSType type; + SInt16 id; + Byte flags; + SInt32 size; + UInt32 dataOffset; + std::string name; + }; + + struct ResourceFork + { + SInt16 fileRefNum; + std::map > resourceMap; + }; + + void Init(); + + bool IsRefNumLegal(short refNum); + + bool IsStreamOpen(short refNum); + + bool IsStreamPermissionAllowed(short refNum, char perm); + + std::iostream& GetStream(short refNum); + + void CloseStream(short refNum); + + FSSpec HostPathToFSSpec(const fs::path& fullPath); +} diff --git a/src/PommeGraphics.h b/src/PommeGraphics.h new file mode 100644 index 0000000..f25ea92 --- /dev/null +++ b/src/PommeGraphics.h @@ -0,0 +1,66 @@ +#pragma once + +#include "PommeTypes.h" +#include +#include + +namespace Pomme::Graphics +{ + struct Color + { + UInt8 a, r, g, b; + + Color(UInt8 red, UInt8 green, UInt8 blue); + + Color(UInt8 red, UInt8 green, UInt8 blue, UInt8 alpha); + + Color(); + }; + + struct ARGBPixmap + { + int width; + int height; + std::vector data; + + ARGBPixmap(); + + ARGBPixmap(int w, int h); + + ARGBPixmap(ARGBPixmap&& other) noexcept; + + ARGBPixmap& operator=(ARGBPixmap&& other) noexcept; + + void Fill(UInt8 red, UInt8 green, UInt8 blue, UInt8 alpha = 0xFF); + + void Plot(int x, int y, UInt32 color); + + void WriteTGA(const char* path) const; + + inline UInt32* GetPtr(int x, int y) + { return (UInt32*) &data.data()[4 * (y * width + x)]; } + }; + + void Init(const char* windowTitle, int windowWidth, int windowHeight); + + void Shutdown(); + + ARGBPixmap ReadPICT(std::istream& f, bool skip512 = true); + + void DumpTGA(const char* path, short width, short height, const char* argbData); + + void DrawARGBPixmap(int left, int top, ARGBPixmap& p); + + CGrafPtr GetScreenPort(void); + + void SetWindowIconFromIcl8Resource(short i); + + inline int Width(const Rect& r) + { return r.right - r.left; } + + inline int Height(const Rect& r) + { return r.bottom - r.top; } + + extern const uint32_t clut8[256]; + extern const uint32_t clut4[16]; +} diff --git a/src/PommeInit.h b/src/PommeInit.h new file mode 100644 index 0000000..1a4c7fb --- /dev/null +++ b/src/PommeInit.h @@ -0,0 +1,16 @@ +#pragma once + +namespace Pomme +{ + // Throw this exception to interrupt the game's main loop + class QuitRequest : public std::exception + { + public: + virtual const char* what() const noexcept; + }; + + void Init(const char* applName); + + void Shutdown(); +} + diff --git a/src/PommeInput.h b/src/PommeInput.h new file mode 100644 index 0000000..7875678 --- /dev/null +++ b/src/PommeInput.h @@ -0,0 +1,6 @@ +#pragma once + +namespace Pomme::Input +{ + void Init(); +} diff --git a/src/PommeSound.h b/src/PommeSound.h new file mode 100644 index 0000000..5014446 --- /dev/null +++ b/src/PommeSound.h @@ -0,0 +1,70 @@ +#pragma once + +#include "CompilerSupport/span.h" +#include +#include +#include +#include "Sound/cmixer.h" + +namespace Pomme::Sound +{ + void Init(); + + void Shutdown(); + + void ReadAIFF(std::istream& input, cmixer::WavStream& output); + + class Codec + { + public: + virtual ~Codec() + {} + + virtual int SamplesPerPacket() = 0; + + virtual int BytesPerPacket() = 0; + + virtual void Decode(const int nChannels, const std::span input, const std::span output) = 0; + }; + + class MACE : public Codec + { + public: + int SamplesPerPacket() override + { return 6; } + + int BytesPerPacket() override + { return 2; } + + void Decode(const int nChannels, const std::span input, const std::span output) override; + }; + + class IMA4 : public Codec + { + public: + int SamplesPerPacket() override + { return 64; } + + int BytesPerPacket() override + { return 34; } + + void Decode(const int nChannels, const std::span input, const std::span output) override; + }; + + class xlaw : public Codec + { + const int16_t* xlawToPCM; + public: + xlaw(uint32_t codecFourCC); + + int SamplesPerPacket() override + { return 1; } + + int BytesPerPacket() override + { return 1; } + + void Decode(const int nChannels, const std::span input, const std::span output) override; + }; + + std::unique_ptr GetCodec(uint32_t fourCC); +} diff --git a/src/PommeTime.h b/src/PommeTime.h new file mode 100644 index 0000000..4ccd08e --- /dev/null +++ b/src/PommeTime.h @@ -0,0 +1,6 @@ +#pragma once + +namespace Pomme::Time +{ + void Init(); +} diff --git a/src/PommeTypes.h b/src/PommeTypes.h new file mode 100644 index 0000000..9954612 --- /dev/null +++ b/src/PommeTypes.h @@ -0,0 +1,258 @@ +#pragma once + +#include +#include + +//----------------------------------------------------------------------------- +// Integer types + +typedef char SignedByte; +typedef char SInt8; +typedef short SInt16; +typedef int SInt32; +typedef long long SInt64; + +typedef unsigned char Byte; +typedef unsigned char UInt8; +typedef unsigned char Boolean; +typedef unsigned short UInt16; +typedef unsigned int UInt32; +typedef unsigned long long UInt64; + +#if TARGET_RT_BIGENDIAN +typedef struct { UInt32 hi, lo; } UnsignedWide; +#else +typedef struct { UInt32 lo, hi; } UnsignedWide; +#endif + +//----------------------------------------------------------------------------- +// Fixed/fract types + +typedef SInt32 Fixed; +typedef SInt32 Fract; +typedef UInt32 UnsignedFixed; +typedef SInt16 ShortFixed; +typedef Fixed* FixedPtr; +typedef Fract* FractPtr; +typedef UnsignedFixed* UnsignedFixedPtr; +typedef ShortFixed* ShortFixedPtr; + +//----------------------------------------------------------------------------- +// Basic system types + +typedef SInt16 OSErr; +typedef SInt32 OSStatus; +typedef void* LogicalAddress; +typedef const void* ConstLogicalAddress; +typedef void* PhysicalAddress; +typedef UInt8* BytePtr; +typedef unsigned long ByteCount; +typedef unsigned long ByteOffset; +typedef SInt32 Duration; +typedef UnsignedWide AbsoluteTime; +typedef UInt32 OptionBits; +typedef unsigned long ItemCount; +typedef UInt32 PBVersion; +typedef SInt16 ScriptCode; +typedef SInt16 LangCode; +typedef SInt16 RegionCode; +typedef UInt32 FourCharCode; +typedef FourCharCode OSType; +typedef FourCharCode ResType; +typedef OSType* OSTypePtr; +typedef ResType* ResTypePtr; +typedef char* Ptr; // Pointer to a non-relocatable block +typedef Ptr* Handle; // Pointer to a master pointer to a relocatable block +typedef long Size; // Number of bytes in a block (signed for historical reasons) +typedef void (*ProcPtr); + +//----------------------------------------------------------------------------- +// (Pascal) String types + +typedef char Str32[33]; +typedef char Str255[256]; +typedef char* StringPtr; +typedef const char* ConstStr255Param; + +//----------------------------------------------------------------------------- +// Point & Rect types + +typedef struct Point { SInt16 v, h; } Point; +typedef struct Rect { SInt16 top, left, bottom, right; } Rect; + +typedef Point* PointPtr; +typedef Rect* RectPtr; + +typedef struct FixedPoint { Fixed x, y; } FixedPoint; +typedef struct FixedRect { Fixed left, top, right, bottom; } FixedRect; + +//----------------------------------------------------------------------------- +// FSSpec types + +typedef struct FSSpec +{ + // Volume reference number of the volume containing the specified file or directory. + short vRefNum; + + // Parent directory ID of the specified file or directory (the directory ID of the directory containing the given file or directory). + long parID; + + // The name of the specified file or directory. In Carbon, this name must be a leaf name; the name cannot contain a semicolon. + // WARNING: this is a C string encoded as UTF-8, NOT a pascal string encoded as MacRoman! + // Mac application code expecting "name" (the pascal string) must be adjusted. + Str255 cName; +} FSSpec; + +typedef Handle AliasHandle; + +//----------------------------------------------------------------------------- +// QuickDraw types + +typedef SInt16 QDErr; + +typedef struct RGBColor +{ + UInt16 red; + UInt16 green; + UInt16 blue; +} RGBColor; + +typedef struct Picture +{ + // Version 1 size. + // Not used for version 2 PICTs, as the size may easily exceed 16 bits. + SInt16 picSize; + + Rect picFrame; + + // Raw raster image decoded from PICT opcodes. Shouldn't be accessed + // directly by the Mac application as it is stored in a format internal + // to the Pomme implementation for rendering (typically ARGB32). + Ptr __pomme_pixelsARGB32; +} Picture; + + +typedef Picture* PicPtr; +typedef PicPtr* PicHandle; +typedef Handle GDHandle; // GDevice handle. Game code doesn't care about GDevice internals, so we just alias a generic Handle +// http://mirror.informatimago.com/next/developer.apple.com/documentation/Carbon/Reference/QuickDraw_Ref/qdref_main/data_type_41.html +typedef struct PixMap +{ + Rect bounds; + short pixelSize; + Ptr _impl; // Points to ARGBPixmap +} PixMap; +typedef PixMap* PixMapPtr; +typedef PixMapPtr* PixMapHandle; +typedef struct GrafPort +{ + Rect portRect; + void* _impl; // Points to GrafPortImpl +} GrafPort; +typedef GrafPort* GrafPtr; +typedef GrafPtr WindowPtr; +typedef GrafPort CGrafPort; +typedef GrafPtr CGrafPtr; +typedef CGrafPtr GWorldPtr; + +//----------------------------------------------------------------------------- +// Sound Manager types + +typedef struct SndCommand +{ + unsigned short cmd; + short param1; + union { + long param2; + Ptr ptr; // pomme addition to pass 64-bit clean pointers + }; +} SndCommand; + +typedef struct SCStatus +{ + UnsignedFixed scStartTime; // starting time for play from disk (based on audio selection record) + UnsignedFixed scEndTime; // ending time for play from disk (based on audio selection record) + UnsignedFixed scCurrentTime; // current time for play from disk + Boolean scChannelBusy; // true if channel is processing commands + Boolean scChannelDisposed; // reserved + Boolean scChannelPaused; // true if channel is paused + Boolean scUnused; // reserved + unsigned long scChannelAttributes; // attributes of this channel + long scCPULoad; // cpu load for this channel ("obsolete") +} SCStatus; + +typedef struct SndChannel* SndChannelPtr; + +typedef void (*SndCallBackProcPtr)(SndChannelPtr chan, SndCommand* cmd); +// for pomme implementation purposes we don't care about the 68000/ppc specifics of universal procedure pointers +typedef SndCallBackProcPtr SndCallbackUPP; + +typedef struct SndChannel +{ + SndChannelPtr nextChan; + Ptr firstMod; // reserved for the Sound Manager (Pomme: used as internal ptr) + SndCallBackProcPtr callBack; + long long userInfo; // free for application's use (Pomme: made it 64 bit so app can store ptrs) +#if 0 + long wait; // The following is for internal Sound Manager use only. + SndCommand cmdInProgress; + short flags; + short qLength; + short qHead; + short qTail; + SndCommand queue[128]; +#endif +} SndChannel; + +typedef struct ModRef +{ + unsigned short modNumber; + long modInit; +} ModRef; + +typedef struct SndListResource +{ + short format; + short numModifiers; + // flexible array hack + ModRef modifierPart[1]; + short numCommands; + // flexible array hack + SndCommand commandPart[1]; + UInt8 dataPart[1]; +} SndListResource; + +typedef SCStatus* SCStatusPtr; +typedef SndListResource* SndListPtr; +typedef SndListPtr* SndListHandle; +typedef SndListHandle SndListHndl; +typedef void(*FilePlayCompletionProcPtr)(SndChannelPtr chan); +typedef FilePlayCompletionProcPtr FilePlayCompletionUPP; +#define NewFilePlayCompletionProc(userRoutine) (userRoutine) + +//----------------------------------------------------------------------------- +// Keyboard input types + +typedef UInt32 KeyMap[4]; + +//----------------------------------------------------------------------------- +// 'vers' resource + +#if TARGET_RT_BIG_ENDIAN +//BCD encoded, e.g. "4.2.1a3" is 0x04214003 +typedef struct NumVersion +{ + UInt8 majorRev; // 1st part of version number in BCD + UInt8 minorAndBugRev; // 2nd & 3rd part of version number share a byte + UInt8 stage; // stage code: dev, alpha, beta, final + UInt8 nonRelRev; // revision level of non-released version +} NumVersion; +#else +typedef struct NumVersion +{ + UInt8 nonRelRev; // revision level of non-released version + UInt8 stage; // stage code: dev, alpha, beta, final + UInt8 minorAndBugRev; // 2nd & 3rd part of version number share a byte + UInt8 majorRev; // 1st part of version number in BCD +} NumVersion; +#endif diff --git a/src/PommeVideo.h b/src/PommeVideo.h new file mode 100644 index 0000000..851081a --- /dev/null +++ b/src/PommeVideo.h @@ -0,0 +1,29 @@ +#pragma once + +#include "PommeTypes.h" +#include "Sound/cmixer.h" + +#include +#include +#include + +namespace Pomme::Video +{ + struct Movie + { + int width; + int height; + FourCharCode videoFormat; + float videoFrameRate; + std::queue> videoFrames; + + FourCharCode audioFormat; + int audioSampleRate; + int audioBitDepth; + int audioNChannels; + cmixer::WavStream audioStream; + unsigned audioSampleCount; + }; + + Movie ReadMoov(std::istream& f); +} diff --git a/src/Sound/AIFF.cpp b/src/Sound/AIFF.cpp new file mode 100644 index 0000000..048eb67 --- /dev/null +++ b/src/Sound/AIFF.cpp @@ -0,0 +1,88 @@ +#include "PommeSound.h" +#include "Utilities/BigEndianIStream.h" +#include + +static void AIFFAssert(bool condition, const char* message) +{ + if (!condition) + { + throw std::runtime_error(message); + } +} + +void Pomme::Sound::ReadAIFF(std::istream& input, cmixer::WavStream& output) +{ + BigEndianIStream f(input); + + AIFFAssert('FORM' == f.Read(), "AIFF: invalid FORM"); + auto formSize = f.Read(); + auto endOfForm = f.Tell() + std::streampos(formSize); + auto formType = f.Read(); + AIFFAssert(formType == 'AIFF' || formType == 'AIFC', "AIFF: not an AIFF or AIFC file"); + + // COMM chunk contents + int nChannels = 0; + int nPackets = 0; + int sampleRate = 0; + uint32_t compressionType = 'NONE'; + + bool gotCOMM = false; + + while (f.Tell() != endOfForm) + { + auto ckID = f.Read(); + auto ckSize = f.Read(); + std::streampos endOfChunk = f.Tell() + std::streampos(ckSize); + + switch (ckID) + { + case 'FVER': + { + auto timestamp = f.Read(); + AIFFAssert(timestamp == 0xA2805140u, "AIFF: unrecognized FVER"); + break; + } + + case 'COMM': // common chunk, 2-85 + { + nChannels = f.Read(); + nPackets = f.Read(); + f.Skip(2); // sample bit depth (UInt16) + sampleRate = (int)f.Read80BitFloat(); + if (formType == 'AIFC') + { + compressionType = f.Read(); + f.ReadPascalString(); // This is a human-friendly compression name. Skip it. + } + gotCOMM = true; + break; + } + + case 'SSND': + { + AIFFAssert(gotCOMM, "AIFF: reached SSND before COMM"); + AIFFAssert(0 == f.Read(), "AIFF: unexpected offset/blockSize in SSND"); + // sampled sound data is here + + const int ssndSize = ckSize - 8; + auto ssnd = std::vector(ssndSize); + f.Read(ssnd.data(), ssndSize); + + // TODO: if the compression type is 'NONE' (raw PCM), just init the WavStream without decoding (not needed for Nanosaur though) + auto codec = Pomme::Sound::GetCodec(compressionType); + auto spanIn = std::span(ssnd); + auto spanOut = output.GetBuffer(nChannels * nPackets * codec->SamplesPerPacket() * 2); + codec->Decode(nChannels, spanIn, spanOut); + output.Init(sampleRate, 16, nChannels, false, spanOut); + break; + } + + default: + f.Goto(int(endOfChunk)); + break; + } + + AIFFAssert(f.Tell() == endOfChunk, "AIFF: incorrect end-of-chunk position"); + } +} + diff --git a/src/Sound/IMA4.cpp b/src/Sound/IMA4.cpp new file mode 100644 index 0000000..b053416 --- /dev/null +++ b/src/Sound/IMA4.cpp @@ -0,0 +1,161 @@ +// Adapted from ffmpeg. Look at libavcodec/adpcm{,_data}.{c,h} + +/* + * Portions Copyright (c) 2001-2003 The FFmpeg project + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "PommeSound.h" + +#include +#include +#include + +const int8_t ff_adpcm_index_table[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8, +}; + +const int16_t ff_adpcm_step_table[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 +}; + +struct ADPCMChannelStatus +{ + int predictor; + int16_t step_index; + int step; +}; + +static inline int sign_extend(int val, unsigned bits) +{ + unsigned shift = 8 * sizeof(int) - bits; + union { unsigned u; int s; } v = { (unsigned)val << shift }; + return v.s >> shift; +} + +static inline int adpcm_ima_qt_expand_nibble(ADPCMChannelStatus* c, int nibble, int shift) +{ + int step_index; + int predictor; + int diff, step; + + step = ff_adpcm_step_table[c->step_index]; + step_index = c->step_index + ff_adpcm_index_table[nibble]; + step_index = std::clamp(step_index, 0, 88); + + diff = step >> 3; + if (nibble & 4) diff += step; + if (nibble & 2) diff += step >> 1; + if (nibble & 1) diff += step >> 2; + + if (nibble & 8) + predictor = c->predictor - diff; + else + predictor = c->predictor + diff; + + c->predictor = std::clamp(predictor, -32768, 32767); + c->step_index = step_index; + + return c->predictor; +} + +// In QuickTime, IMA is encoded by chunks of 34 bytes (=64 samples). Channel data is interleaved per-chunk. +static void DecodeIMA4Chunk( + const uint8_t** input, + int16_t** output, + std::vector& ctx) +{ + const size_t nChannels = ctx.size(); + const unsigned char* in = *input; + int16_t* out = *output; + + for (size_t chan = 0; chan < nChannels; chan++) + { + ADPCMChannelStatus& cs = ctx[chan]; + + // Bits 15-7 are the _top_ 9 bits of the 16-bit initial predictor value + int predictor = sign_extend((in[0] << 8) | in[1], 16); + int step_index = predictor & 0x7F; + predictor &= ~0x7F; + + in += 2; + + if (cs.step_index == step_index) + { + int diff = predictor - cs.predictor; + if (diff < 0x00) diff = -diff; + if (diff > 0x7f) goto update; + } + else + { +update: + cs.step_index = step_index; + cs.predictor = predictor; + } + + if (cs.step_index > 88) + throw std::invalid_argument("step_index[chan]>88!"); + + size_t pos = chan; + for (int m = 0; m < 32; m++) + { + int byte = (uint8_t) (*in++); + out[pos] = adpcm_ima_qt_expand_nibble(&cs, byte & 0x0F, 3); + pos += nChannels; + out[pos] = adpcm_ima_qt_expand_nibble(&cs, byte >> 4, 3); + pos += nChannels; + } + } + + *input = in; + *output += 64 * nChannels; +} + +void Pomme::Sound::IMA4::Decode( + const int nChannels, + const std::span input, + const std::span output) +{ + if (input.size() % 34 != 0) + throw std::invalid_argument("odd input buffer size"); + + const size_t nChunks = input.size() / (34 * nChannels); + const size_t nSamples = 64 * nChunks; + + if (output.size() != nSamples * nChannels * 2) + throw std::invalid_argument("incorrect output size"); + + const uint8_t* in = reinterpret_cast(input.data()); + int16_t* out = reinterpret_cast(output.data()); + std::vector ctx(nChannels); + + for (size_t chunk = 0; chunk < nChunks; chunk++) + { + DecodeIMA4Chunk(&in, &out, ctx); + } + + assert(reinterpret_cast(in) == input.data() + input.size()); + assert(reinterpret_cast(out) == output.data() + output.size()); +} diff --git a/src/Sound/MACE.cpp b/src/Sound/MACE.cpp new file mode 100644 index 0000000..975ed33 --- /dev/null +++ b/src/Sound/MACE.cpp @@ -0,0 +1,229 @@ +// Adapted from ffmpeg + +// ---- Begin ffmpeg copyright notices ---- + +/* + * MACE decoder + * Copyright (c) 2002 Laszlo Torok + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Adapted to libavcodec by Francois Revol + * (removed 68k REG stuff, changed types, added some statics and consts, + * libavcodec api, context stuff, interlaced stereo out). + */ + +// ---- End ffmpeg copyright notices ---- + +#include "PommeSound.h" + +#include + +static const int16_t MACEtab1[] = {-13, 8, 76, 222, 222, 76, 8, -13}; + +static const int16_t MACEtab3[] = {-18, 140, 140, -18}; + +static const int16_t MACEtab2[][4] = { + { 37, 116, 206, 330}, { 39, 121, 216, 346}, + { 41, 127, 225, 361}, { 42, 132, 235, 377}, + { 44, 137, 245, 392}, { 46, 144, 256, 410}, + { 48, 150, 267, 428}, { 51, 157, 280, 449}, + { 53, 165, 293, 470}, { 55, 172, 306, 490}, + { 58, 179, 319, 511}, { 60, 187, 333, 534}, + { 63, 195, 348, 557}, { 66, 205, 364, 583}, + { 69, 214, 380, 609}, { 72, 223, 396, 635}, + { 75, 233, 414, 663}, { 79, 244, 433, 694}, + { 82, 254, 453, 725}, { 86, 265, 472, 756}, + { 90, 278, 495, 792}, { 94, 290, 516, 826}, + { 98, 303, 538, 862}, { 102, 316, 562, 901}, + { 107, 331, 588, 942}, { 112, 345, 614, 983}, + { 117, 361, 641, 1027}, { 122, 377, 670, 1074}, + { 127, 394, 701, 1123}, { 133, 411, 732, 1172}, + { 139, 430, 764, 1224}, { 145, 449, 799, 1280}, + { 152, 469, 835, 1337}, { 159, 490, 872, 1397}, + { 166, 512, 911, 1459}, { 173, 535, 951, 1523}, + { 181, 558, 993, 1590}, { 189, 584, 1038, 1663}, + { 197, 610, 1085, 1738}, { 206, 637, 1133, 1815}, + { 215, 665, 1183, 1895}, { 225, 695, 1237, 1980}, + { 235, 726, 1291, 2068}, { 246, 759, 1349, 2161}, + { 257, 792, 1409, 2257}, { 268, 828, 1472, 2357}, + { 280, 865, 1538, 2463}, { 293, 903, 1606, 2572}, + { 306, 944, 1678, 2688}, { 319, 986, 1753, 2807}, + { 334, 1030, 1832, 2933}, { 349, 1076, 1914, 3065}, + { 364, 1124, 1999, 3202}, { 380, 1174, 2088, 3344}, + { 398, 1227, 2182, 3494}, { 415, 1281, 2278, 3649}, + { 434, 1339, 2380, 3811}, { 453, 1398, 2486, 3982}, + { 473, 1461, 2598, 4160}, { 495, 1526, 2714, 4346}, + { 517, 1594, 2835, 4540}, { 540, 1665, 2961, 4741}, + { 564, 1740, 3093, 4953}, { 589, 1818, 3232, 5175}, + { 615, 1898, 3375, 5405}, { 643, 1984, 3527, 5647}, + { 671, 2072, 3683, 5898}, { 701, 2164, 3848, 6161}, + { 733, 2261, 4020, 6438}, { 766, 2362, 4199, 6724}, + { 800, 2467, 4386, 7024}, { 836, 2578, 4583, 7339}, + { 873, 2692, 4786, 7664}, { 912, 2813, 5001, 8008}, + { 952, 2938, 5223, 8364}, { 995, 3070, 5457, 8739}, + { 1039, 3207, 5701, 9129}, { 1086, 3350, 5956, 9537}, + { 1134, 3499, 6220, 9960}, { 1185, 3655, 6497, 10404}, + { 1238, 3818, 6788, 10869}, { 1293, 3989, 7091, 11355}, + { 1351, 4166, 7407, 11861}, { 1411, 4352, 7738, 12390}, + { 1474, 4547, 8084, 12946}, { 1540, 4750, 8444, 13522}, + { 1609, 4962, 8821, 14126}, { 1680, 5183, 9215, 14756}, + { 1756, 5415, 9626, 15415}, { 1834, 5657, 10057, 16104}, + { 1916, 5909, 10505, 16822}, { 2001, 6173, 10975, 17574}, + { 2091, 6448, 11463, 18356}, { 2184, 6736, 11974, 19175}, + { 2282, 7037, 12510, 20032}, { 2383, 7351, 13068, 20926}, + { 2490, 7679, 13652, 21861}, { 2601, 8021, 14260, 22834}, + { 2717, 8380, 14897, 23854}, { 2838, 8753, 15561, 24918}, + { 2965, 9144, 16256, 26031}, { 3097, 9553, 16982, 27193}, + { 3236, 9979, 17740, 28407}, { 3380, 10424, 18532, 29675}, + { 3531, 10890, 19359, 31000}, { 3688, 11375, 20222, 32382}, + { 3853, 11883, 21125, 32767}, { 4025, 12414, 22069, 32767}, + { 4205, 12967, 23053, 32767}, { 4392, 13546, 24082, 32767}, + { 4589, 14151, 25157, 32767}, { 4793, 14783, 26280, 32767}, + { 5007, 15442, 27452, 32767}, { 5231, 16132, 28678, 32767}, + { 5464, 16851, 29957, 32767}, { 5708, 17603, 31294, 32767}, + { 5963, 18389, 32691, 32767}, { 6229, 19210, 32767, 32767}, + { 6507, 20067, 32767, 32767}, { 6797, 20963, 32767, 32767}, + { 7101, 21899, 32767, 32767}, { 7418, 22876, 32767, 32767}, + { 7749, 23897, 32767, 32767}, { 8095, 24964, 32767, 32767}, + { 8456, 26078, 32767, 32767}, { 8833, 27242, 32767, 32767}, + { 9228, 28457, 32767, 32767}, { 9639, 29727, 32767, 32767} +}; + +static const int16_t MACEtab4[][2] = { + { 64, 216}, { 67, 226}, { 70, 236}, { 74, 246}, + { 77, 257}, { 80, 268}, { 84, 280}, { 88, 294}, + { 92, 307}, { 96, 321}, { 100, 334}, { 104, 350}, + { 109, 365}, { 114, 382}, { 119, 399}, { 124, 416}, + { 130, 434}, { 136, 454}, { 142, 475}, { 148, 495}, + { 155, 519}, { 162, 541}, { 169, 564}, { 176, 590}, + { 185, 617}, { 193, 644}, { 201, 673}, { 210, 703}, + { 220, 735}, { 230, 767}, { 240, 801}, { 251, 838}, + { 262, 876}, { 274, 914}, { 286, 955}, { 299, 997}, + { 312, 1041}, { 326, 1089}, { 341, 1138}, { 356, 1188}, + { 372, 1241}, { 388, 1297}, { 406, 1354}, { 424, 1415}, + { 443, 1478}, { 462, 1544}, { 483, 1613}, { 505, 1684}, + { 527, 1760}, { 551, 1838}, { 576, 1921}, { 601, 2007}, + { 628, 2097}, { 656, 2190}, { 686, 2288}, { 716, 2389}, + { 748, 2496}, { 781, 2607}, { 816, 2724}, { 853, 2846}, + { 891, 2973}, { 930, 3104}, { 972, 3243}, { 1016, 3389}, + { 1061, 3539}, { 1108, 3698}, { 1158, 3862}, { 1209, 4035}, + { 1264, 4216}, { 1320, 4403}, { 1379, 4599}, { 1441, 4806}, + { 1505, 5019}, { 1572, 5244}, { 1642, 5477}, { 1715, 5722}, + { 1792, 5978}, { 1872, 6245}, { 1955, 6522}, { 2043, 6813}, + { 2134, 7118}, { 2229, 7436}, { 2329, 7767}, { 2432, 8114}, + { 2541, 8477}, { 2655, 8854}, { 2773, 9250}, { 2897, 9663}, + { 3026, 10094}, { 3162, 10546}, { 3303, 11016}, { 3450, 11508}, + { 3604, 12020}, { 3765, 12556}, { 3933, 13118}, { 4108, 13703}, + { 4292, 14315}, { 4483, 14953}, { 4683, 15621}, { 4892, 16318}, + { 5111, 17046}, { 5339, 17807}, { 5577, 18602}, { 5826, 19433}, + { 6086, 20300}, { 6358, 21205}, { 6642, 22152}, { 6938, 23141}, + { 7248, 24173}, { 7571, 25252}, { 7909, 26380}, { 8262, 27557}, + { 8631, 28786}, { 9016, 30072}, { 9419, 31413}, { 9839, 32767}, + { 10278, 32767}, { 10737, 32767}, { 11216, 32767}, { 11717, 32767}, + { 12240, 32767}, { 12786, 32767}, { 13356, 32767}, { 13953, 32767}, + { 14576, 32767}, { 15226, 32767}, { 15906, 32767}, { 16615, 32767} +}; + +static const struct +{ + const int16_t *tab1; + const int16_t *tab2; + int stride; +} tabs[] = { + {MACEtab1, &MACEtab2[0][0], 4}, + {MACEtab3, &MACEtab4[0][0], 2}, + {MACEtab1, &MACEtab2[0][0], 4} +}; + +#define QT_8S_2_16S(x) (((x) & 0xFF00) | (((x) >> 8) & 0xFF)) + +struct ChannelData +{ + int16_t index, factor, prev2, previous, level; +}; + +struct MACEContext +{ + ChannelData chd[2]; +}; + +static inline int16_t mace_broken_clip_int16(int n) +{ + if (n > 32767) + return 32767; + else if (n < -32768) + return -32767; + else + return n; +} + +static int16_t read_table(ChannelData* chd, uint8_t val, int tab_idx) +{ + int16_t current; + + if (val < tabs[tab_idx].stride) + current = tabs[tab_idx].tab2[((chd->index & 0x7f0) >> 4) * tabs[tab_idx].stride + val]; + else + current = - 1 - tabs[tab_idx].tab2[((chd->index & 0x7f0) >> 4)*tabs[tab_idx].stride + 2*tabs[tab_idx].stride-val-1]; + + if (( chd->index += tabs[tab_idx].tab1[val]-(chd->index >> 5) ) < 0) + chd->index = 0; + + return current; +} + +void Pomme::Sound::MACE::Decode( + const int nChannels, + const std::span input, + const std::span output) +{ + if (input.size() % (nChannels * 2) != 0) + throw std::invalid_argument("odd input buffer size"); + + size_t nSamples = 3 * int(input.size()) / nChannels; + + if (output.size() != nSamples * nChannels * 2) + throw std::invalid_argument("incorrect output size"); + + int16_t* out = reinterpret_cast(output.data()); + + MACEContext ctx = {}; + + for (int chan = 0; chan < nChannels; chan++) + for (size_t j = 0; j < input.size() / (nChannels * 2); j++) + for (int k = 0; k < 2; k++) + { + uint8_t pkt = (uint8_t)input[(chan * 2) + (j * nChannels * 2) + k]; + + int val2[3] = { pkt & 7, (pkt >> 3) & 3, pkt >> 5 }; + + for (int l = 0; l < 3; l++) + { + int16_t current = read_table(&ctx.chd[chan], (uint8_t)val2[l], l); + current = mace_broken_clip_int16(current + ctx.chd[chan].level); + ctx.chd[chan].level = current - (current >> 3); + current = QT_8S_2_16S(current); + *out = current; + out++; + } + } + + assert(reinterpret_cast(out) == output.data() + output.size()); +} diff --git a/src/Sound/SoundManager.cpp b/src/Sound/SoundManager.cpp new file mode 100644 index 0000000..fef9969 --- /dev/null +++ b/src/Sound/SoundManager.cpp @@ -0,0 +1,782 @@ +#include "Pomme.h" +#include "PommeFiles.h" +#include "Sound/cmixer.h" +#include "PommeSound.h" +#include "Utilities/BigEndianIStream.h" +#include "Utilities/memstream.h" + +#include +#include +#include +#include +#include + +#define LOG POMME_GENLOG(POMME_DEBUG_SOUND, "SOUN") +#define LOG_NOPREFIX POMME_GENLOG_NOPREFIX(POMME_DEBUG_SOUND) + +static struct ChannelImpl* headChan = nullptr; +static int nManagedChans = 0; +static double midiNoteFrequencies[128]; + +//----------------------------------------------------------------------------- +// Cookie-cutter sound command list. +// Used to generate 'snd ' resources. + +static const uint8_t kSampledSoundCommandList[20] = { + 0,1, // format + 0,1, // modifier count + 0,5, // modifier "sampled synth" + 0,0,0,0, // init bits + 0,1, // command count + 0x80,soundCmd, // command soundCmd (high bit set) + 0,0, // param1 + 0,0,0,20, // param2 (offset) + // Sample data follows +}; + +constexpr int kSampledSoundCommandListLength = sizeof(kSampledSoundCommandList); + +//----------------------------------------------------------------------------- +// 'snd ' resource header + +struct SampledSoundHeader +{ + UInt32 zero; + union // the meaning of union this is decided by the encoding type + { + SInt32 stdSH_nBytes; + SInt32 cmpSH_nChannels; + SInt32 extSH_nChannels; + SInt32 nativeSH_nBytes; + }; + Fixed fixedSampleRate; + UInt32 loopStart; + UInt32 loopEnd; + Byte encoding; + Byte baseFrequency; // 0-127, see Table 2-2, IM:S:2-43 + + unsigned int sampleRate() const + { + return (static_cast(fixedSampleRate) >> 16) & 0xFFFF; + } +}; + +static_assert(sizeof(SampledSoundHeader) >= 22 && sizeof(SampledSoundHeader) <= 24, + "unexpected SampledSoundHeader size"); + +constexpr int kSampledSoundHeaderLength = 22; +constexpr const char* kSampledSoundHeaderPackFormat = "IiIIIbb"; + +enum SampledSoundEncoding +{ + stdSH = 0x00, + nativeSH_mono16 = 0x10, // pomme extension + nativeSH_stereo16 = 0x11, // pomme extension + cmpSH = 0xFE, + extSH = 0xFF, +}; + +//----------------------------------------------------------------------------- +// Internal channel info + +struct ChannelImpl +{ +private: + ChannelImpl* prev; + ChannelImpl* next; + +public: + SndChannelPtr macChannel; + + bool macChannelStructAllocatedByPomme; + cmixer::WavStream source; + FilePlayCompletionProcPtr onComplete; + + Byte baseNote = kMiddleC; + Byte playbackNote = kMiddleC; + double pitchMult = 1; + bool temporaryPause = false; + + ChannelImpl(SndChannelPtr _macChannel, bool transferMacChannelOwnership) + : macChannel(_macChannel) + , macChannelStructAllocatedByPomme(transferMacChannelOwnership) + , source() + , onComplete(nullptr) + , baseNote(kMiddleC) + , playbackNote(kMiddleC) + , pitchMult(1.0) + { + macChannel->firstMod = (Ptr) this; + + Link(); // Link chan into our list of managed chans + } + + ~ChannelImpl() + { + Unlink(); + + macChannel->firstMod = nullptr; + + if (macChannelStructAllocatedByPomme) + { + delete macChannel; + } + } + + void Recycle() + { + source.Clear(); + baseNote = kMiddleC; + playbackNote = kMiddleC; + pitchMult = 1; + temporaryPause = false; + } + + void ApplyPitch() + { + if (!source.active) + { + return; + } + double baseFreq = midiNoteFrequencies[baseNote]; + double playbackFreq = midiNoteFrequencies[playbackNote]; + source.SetPitch(pitchMult * playbackFreq / baseFreq); + } + + ChannelImpl* GetPrev() const + { + return prev; + } + + ChannelImpl* GetNext() const + { + return next; + } + + void SetPrev(ChannelImpl* newPrev) + { + prev = newPrev; + } + + void SetNext(ChannelImpl* newNext) + { + next = newNext; + macChannel->nextChan = newNext ? newNext->macChannel : nullptr; + } + + void Link() + { + if (!headChan) + { + SetNext(nullptr); + } + else + { + assert(nullptr == headChan->GetPrev()); + headChan->SetPrev(this); + SetNext(headChan); + } + + headChan = this; + SetPrev(nullptr); + + nManagedChans++; + } + + void Unlink() + { + if (headChan == this) + { + headChan = GetNext(); + } + + if (nullptr != GetPrev()) + { + GetPrev()->SetNext(GetNext()); + } + + if (nullptr != GetNext()) + { + GetNext()->SetPrev(GetPrev()); + } + + SetPrev(nullptr); + SetNext(nullptr); + + nManagedChans--; + } +}; + +//----------------------------------------------------------------------------- +// Internal utilities + +static inline ChannelImpl& GetImpl(SndChannelPtr chan) +{ + return *(ChannelImpl*) chan->firstMod; +} + +//----------------------------------------------------------------------------- +// MIDI note utilities + +// Note: these names are according to IM:S:2-43. +// These names won't match real-world names. +// E.g. for note 67 (A 440Hz), this will return "A6", whereas the real-world +// convention for that note is "A4". +static std::string GetMidiNoteName(int i) +{ + static const char* gamme[12] = {"A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"}; + + int octave = 1 + (i + 3) / 12; + int semitonesFromA = (i + 3) % 12; + + std::stringstream ss; + ss << gamme[semitonesFromA] << octave; + return ss.str(); +} + +static void InitMidiFrequencyTable() +{ + // powers of twelfth root of two + double gamme[12]; + gamme[0] = 1.0; + for (int i = 1; i < 12; i++) + { + gamme[i] = gamme[i - 1] * 1.059630943592952646; + } + + for (int i = 0; i < 128; i++) + { + int octave = 1 + (i + 3) / 12; // A440 and middle C are in octave 7 + int semitone = (i + 3) % 12; // halfsteps up from A in current octave + if (octave < 7) + midiNoteFrequencies[i] = gamme[semitone] * 440.0 / (1 << (7 - octave)); // 440/(2**octaveDiff) + else + midiNoteFrequencies[i] = gamme[semitone] * 440.0 * (1 << (octave - 7)); // 440*(2**octaveDiff) + //LOG << i << "\t" << GetMidiNoteName(i) << "\t" << midiNoteFrequencies[i] << "\n"; + } +} + +//----------------------------------------------------------------------------- +// Sound Manager + +OSErr GetDefaultOutputVolume(long* stereoLevel) +{ + unsigned short g = (unsigned short) (cmixer::GetMasterGain() * 256.0); + *stereoLevel = (g << 16) | g; + return noErr; +} + +// See IM:S:2-139, "Controlling Volume Levels". +OSErr SetDefaultOutputVolume(long stereoLevel) +{ + unsigned short left = 0xFFFF & stereoLevel; + unsigned short right = 0xFFFF & (stereoLevel >> 16); + if (right != left) + TODOMINOR2("setting different volumes for left & right is not implemented"); + LOG << left / 256.0 << "\n"; + cmixer::SetMasterGain(left / 256.0); + return noErr; +} + +// IM:S:2-127 +OSErr SndNewChannel(SndChannelPtr* macChanPtr, short synth, long init, SndCallBackProcPtr userRoutine) +{ + if (synth != sampledSynth) + { + TODO2("unimplemented synth type " << sampledSynth); + return unimpErr; + } + + //--------------------------- + // Allocate Mac channel record if needed + + bool transferMacChannelOwnership = false; + + if (!*macChanPtr) + { + *macChanPtr = new SndChannel; + (**macChanPtr) = {}; + transferMacChannelOwnership = true; + } + + //--------------------------- + // Set up + + (**macChanPtr).callBack = userRoutine; + new ChannelImpl(*macChanPtr, transferMacChannelOwnership); + + //--------------------------- + // Done + + LOG << "New channel created, init = $" << std::hex << init << std::dec << ", total managed channels = " << nManagedChans << "\n"; + + return noErr; +} + +// IM:S:2-129 +OSErr SndDisposeChannel(SndChannelPtr macChanPtr, Boolean quietNow) +{ + if (!quietNow) + { + TODO2("SndDisposeChannel: quietNow == false is not implemented"); + } + delete &GetImpl(macChanPtr); + return noErr; +} + +OSErr SndChannelStatus(SndChannelPtr chan, short theLength, SCStatusPtr theStatus) +{ + *theStatus = {}; + + auto& source = GetImpl(chan).source; + + theStatus->scChannelPaused = source.GetState() == cmixer::CM_STATE_PAUSED; + theStatus->scChannelBusy = source.GetState() == cmixer::CM_STATE_PLAYING; + + return noErr; +} + +static void ProcessSoundCmd(SndChannelPtr chan, const Ptr sndhdr) +{ + // Install a sampled sound as a voice in a channel. If the high bit of the + // command is set, param2 is interpreted as an offset from the beginning of + // the 'snd ' resource containing the command to the sound header. If the + // high bit is not set, param2 is interpreted as a pointer to the sound + // header. + + auto& impl = GetImpl(chan); + + impl.Recycle(); + + // PACKED RECORD + memstream headerInput(sndhdr, kSampledSoundHeaderLength + 42); + Pomme::BigEndianIStream f(headerInput); + + SampledSoundHeader sh; + f.Read(reinterpret_cast(&sh), kSampledSoundHeaderLength); + ByteswapStructs(kSampledSoundHeaderPackFormat, kSampledSoundHeaderLength, 1, reinterpret_cast(&sh)); + + if (sh.zero != 0) + { + // The first field can be a pointer to the sampled-sound data. + // In practice it's always gonna be 0. + TODOFATAL2("expected 0 at the beginning of an snd"); + } + + int sampleRate = sh.sampleRate(); + impl.baseNote = sh.baseFrequency; + + LOG << sampleRate << "Hz, " << GetMidiNoteName(sh.baseFrequency) << ", loop " << sh.loopStart << "->" << sh.loopEnd << ", "; + + switch (sh.encoding) + { + case 0x00: // stdSH - standard sound header - IM:S:2-104 + { + // noncompressed sample data (8-bit mono) from this point on + char* here = sndhdr + f.Tell(); + impl.source.Init(sampleRate, 8, 1, false, std::span(here, sh.stdSH_nBytes)); + LOG_NOPREFIX << "stdSH: 8-bit mono, " << sh.stdSH_nBytes << " frames\n"; + break; + } + + case nativeSH_mono16: + case nativeSH_stereo16: + { + int nChannels = sh.encoding == nativeSH_mono16 ? 1 : 2; + char* here = sndhdr + f.Tell(); + auto span = std::span(here, sh.nativeSH_nBytes); + impl.source.Init(sampleRate, 16, nChannels, false, span); + LOG_NOPREFIX << "nativeSH\n"; + break; + } + + case 0xFF: // extSH - extended sound header - IM:S:2-106 + { + // fields that follow baseFrequency + SInt32 nFrames = f.Read(); + f.Skip(22); + SInt16 bitDepth = f.Read(); + f.Skip(14); + + int nBytes = sh.extSH_nChannels * nFrames * bitDepth / 8; + + // noncompressed sample data (big endian) from this point on + char* here = sndhdr + f.Tell(); + + LOG_NOPREFIX << "extSH: " << bitDepth << "-bit " << (sh.extSH_nChannels == 1? "mono": "stereo") << ", " << nFrames << " frames\n"; + + impl.source.Init(sampleRate, bitDepth, sh.extSH_nChannels, true, std::span(here, nBytes)); + break; + } + + case 0xFE: // cmpSH - compressed sound header - IM:S:2-108 + { + // fields that follow baseFrequency + SInt32 nCompressedChunks = f.Read(); + f.Skip(14); + OSType format = f.Read(); + f.Skip(20); + + if (format == 0) + { + // Assume MACE-3. It should've been set in the init options in the snd pre-header, + // but Nanosaur doesn't actually init the sound channels for MACE-3. So I guess the Mac + // assumes by default that any unspecified compression is MACE-3. + // If it wasn't MACE-3, it would've been caught by GetSoundHeaderOffset. + format = 'MAC3'; + } + + // compressed sample data from this point on + char* here = sndhdr + f.Tell(); + + std::cout << "cmpSH: " << Pomme::FourCCString(format) << " " << (sh.cmpSH_nChannels == 1 ? "mono" : "stereo") << ", " << nCompressedChunks << " ck\n"; + + std::unique_ptr codec = Pomme::Sound::GetCodec(format); + + // Decompress + int nBytesIn = sh.cmpSH_nChannels * nCompressedChunks * codec->BytesPerPacket(); + int nBytesOut = sh.cmpSH_nChannels * nCompressedChunks * codec->SamplesPerPacket() * 2; + + auto spanIn = std::span(here, nBytesIn); + auto spanOut = impl.source.GetBuffer(nBytesOut); + + codec->Decode(sh.cmpSH_nChannels, spanIn, spanOut); + impl.source.Init(sampleRate, 16, sh.cmpSH_nChannels, false, spanOut); + + break; + } + + default: + TODOFATAL2("unsupported snd header encoding " << (int)sh.encoding); + } + + if (sh.loopEnd - sh.loopStart <= 1) + { + // don't loop + } + else if (sh.loopStart == 0) + { + impl.source.SetLoop(true); + } + else + { + TODO2("looping on a portion of the snd isn't supported yet"); + impl.source.SetLoop(true); + } + + impl.ApplyPitch(); + impl.temporaryPause = false; + impl.source.Play(); +} + +OSErr SndDoImmediate(SndChannelPtr chan, const SndCommand* cmd) +{ + auto& impl = GetImpl(chan); + + // Discard the high bit of the command (it indicates whether an 'snd ' resource has associated data). + switch (cmd->cmd & 0x7FFF) + { + case nullCmd: + break; + + case flushCmd: + // flushCmd is a no-op for now because we don't support queuing commands-- + // all commands are executed immediately in the current implementation. + break; + + case quietCmd: + impl.source.Stop(); + break; + + case soundCmd: + ProcessSoundCmd(chan, cmd->ptr); + break; + + case ampCmd: + impl.source.SetGain(cmd->param1 / 255.0); + LOG << "ampCmd " << impl.source.gain << "\n"; + break; + + case freqCmd: + LOG << "freqCmd " << cmd->param2 << " " << GetMidiNoteName(cmd->param2) << " " << midiNoteFrequencies[cmd->param2] << "\n"; + impl.playbackNote = Byte(cmd->param2); + impl.ApplyPitch(); + break; + + case rateCmd: + // IM:S says it's a fixed-point multiplier of 22KHz, but Nanosaur uses rate "1" everywhere, + // even for sounds sampled at 44Khz, so I'm treating it as just a pitch multiplier. + impl.pitchMult = cmd->param2 / 65536.0; + impl.ApplyPitch(); + break; + + case pommeSetLoopCmd: + impl.source.SetLoop(cmd->param1); + break; + + default: + TODOMINOR2(cmd->cmd << "(" << cmd->param1 << "," << cmd->param2 << ")"); + } + + return noErr; +} + +template +static void Expect(const T a, const T b, const char* msg) +{ + if (a != b) + throw std::runtime_error(msg); +} + +// IM:S:2-58 "MyGetSoundHeaderOffset" +OSErr GetSoundHeaderOffset(SndListHandle sndHandle, long* offset) +{ + memstream sndStream((Ptr) *sndHandle, GetHandleSize((Handle) sndHandle)); + Pomme::BigEndianIStream f(sndStream); + + // Skip everything before sound commands + Expect(1, f.Read(), "'snd ' format"); + Expect(1, f.Read(), "'snd ' modifier count"); + Expect(5, f.Read(), "'snd ' sampledSynth"); + UInt32 initBits = f.Read(); + + if (initBits & initMACE6) + TODOFATAL2("MACE-6 not supported yet"); + + SInt16 nCmds = f.Read(); + //LOG << nCmds << " commands\n"; + for (; nCmds >= 1; nCmds--) + { + UInt16 cmd = f.Read(); + f.Skip(2); // SInt16 param1 + SInt32 param2 = f.Read(); + cmd &= 0x7FFF; // See IM:S:2-75 + // When a sound command contained in an 'snd ' resource has associated sound data, + // the high bit of the command is set. This changes the meaning of the param2 field of the + // command from a pointer to a location in RAM to an offset value that specifies the offset + // in bytes from the resource's beginning to the location of the associated sound data (such + // as a sampled sound header). + if (cmd == bufferCmd || cmd == soundCmd) + { + *offset = param2; + return noErr; + } + } + + LOG << "didn't find offset in snd resource\n"; + return badFormat; +} + +OSErr SndStartFilePlay( + SndChannelPtr chan, + short fRefNum, + short resNum, + long bufferSize, + Ptr theBuffer, + /*AudioSelectionPtr*/ void* theSelection, + FilePlayCompletionUPP theCompletion, + Boolean async) +{ + if (resNum != 0) + { + TODO2("playing snd resource not implemented yet, resource " << resNum); + return unimpErr; + } + + if (!chan) + { + if (async) // async requires passing in a channel + return badChannel; + TODO2("nullptr chan for sync play, check IM:S:1-37"); + return unimpErr; + } + + if (theSelection) + { + TODO2("audio selection record not implemented"); + return unimpErr; + } + + auto& impl = GetImpl(chan); + impl.Recycle(); + + auto& stream = Pomme::Files::GetStream(fRefNum); + // Rewind -- the file might've been fully played already and we might just be trying to loop it + stream.seekg(0, std::ios::beg); + Pomme::Sound::ReadAIFF(stream, impl.source); + + if (theCompletion) + { + impl.source.onComplete = [=]() { theCompletion(chan); }; + } + + impl.temporaryPause = false; + impl.source.Play(); + + if (!async) + { + while (impl.source.GetState() != cmixer::CM_STATE_STOPPED) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + theCompletion(chan); + impl.Recycle(); + return noErr; + } + + return noErr; +} + +OSErr SndPauseFilePlay(SndChannelPtr chan) +{ + // TODO: check that chan is being used for play from disk + GetImpl(chan).source.TogglePause(); + return noErr; +} + +OSErr SndStopFilePlay(SndChannelPtr chan, Boolean quietNow) +{ + // TODO: check that chan is being used for play from disk + if (!quietNow) + TODO2("quietNow==false not supported yet, sound will be cut off immediately instead"); + GetImpl(chan).source.Stop(); + return noErr; +} + +NumVersion SndSoundManagerVersion() +{ + NumVersion v = {}; + v.majorRev = 3; + v.minorAndBugRev = 9; + v.stage = 0x80; + v.nonRelRev = 0; + return v; +} + +//----------------------------------------------------------------------------- +// Extension: decompress + +Boolean Pomme_DecompressSoundResource(SndListHandle* sndHandlePtr, long* offsetToHeader) +{ + // Prep the BE reader on the header. + Ptr sndhdr = (Ptr) (**sndHandlePtr) + *offsetToHeader; + memstream headerInput(sndhdr, kSampledSoundHeaderLength + 42); + Pomme::BigEndianIStream f(headerInput); + + // Read in SampledSoundHeader and unpack it. + SampledSoundHeader sh; + f.Read(reinterpret_cast(&sh), kSampledSoundHeaderLength); + ByteswapStructs(kSampledSoundHeaderPackFormat, kSampledSoundHeaderLength, 1, reinterpret_cast(&sh)); + + // We only handle cmpSH (compressed) 'snd ' resources. + if (sh.encoding != cmpSH) + { + return false; + } + + // Fields that follow SampledSoundHeader when the encoding is cmpSH. + const auto nCompressedChunks = f.Read(); + f.Skip(14); + const auto format = f.Read(); + f.Skip(20); + + // Compressed sample data in the input stream from this point on. + + const char* here = sndhdr + f.Tell(); + + int outInitialSize = kSampledSoundCommandListLength + kSampledSoundHeaderLength; + + std::unique_ptr codec = Pomme::Sound::GetCodec(format); + + // Decompress + const int nBytesIn = sh.cmpSH_nChannels * nCompressedChunks * codec->BytesPerPacket(); + const int nBytesOut = sh.cmpSH_nChannels * nCompressedChunks * codec->SamplesPerPacket() * 2; + SndListHandle outHandle = (SndListHandle) NewHandle(outInitialSize + nBytesOut); + auto spanIn = std::span(here, nBytesIn); + auto spanOut = std::span((char*) *outHandle + outInitialSize, nBytesOut); + codec->Decode(sh.cmpSH_nChannels, spanIn, spanOut); + + // ------------------------------------------------------ + // Now we have the PCM data. + // Put the output 'snd ' resource together. + + SampledSoundHeader shOut = sh; + shOut.zero = 0; + shOut.encoding = sh.cmpSH_nChannels == 2 ? nativeSH_stereo16 : nativeSH_mono16; + shOut.nativeSH_nBytes = nBytesOut; + ByteswapStructs(kSampledSoundHeaderPackFormat, kSampledSoundHeaderLength, 1, reinterpret_cast(&shOut)); + + memcpy(*outHandle, kSampledSoundCommandList, kSampledSoundCommandListLength); + memcpy((char*) *outHandle + kSampledSoundCommandListLength, &shOut, kSampledSoundHeaderLength); + + // Nuke compressed sound handle, replace it with the decopmressed one we've just created + DisposeHandle((Handle) *sndHandlePtr); + *sndHandlePtr = outHandle; + *offsetToHeader = kSampledSoundCommandListLength; + + long offsetCheck = 0; + OSErr err = GetSoundHeaderOffset(outHandle, &offsetCheck); + if (err != noErr || offsetCheck != kSampledSoundCommandListLength) + { + throw std::runtime_error("Incorrect decompressed sound header offset"); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Extension: pause/unpause looping channels + +void Pomme_PauseLoopingChannels(Boolean pause) +{ + for (auto* chan = headChan; chan; chan = chan->GetNext()) + { + auto& source = chan->source; + if (pause && source.state == cmixer::CM_STATE_PLAYING && !chan->temporaryPause) + { + source.Pause(); + chan->temporaryPause = true; + } + else if (!pause && source.state == cmixer::CM_STATE_PAUSED && chan->temporaryPause) + { + source.Play(); + chan->temporaryPause = false; + } + } +} + +//----------------------------------------------------------------------------- +// Init Sound Manager + +void Pomme::Sound::Init() +{ + InitMidiFrequencyTable(); + cmixer::InitWithSDL(); +} + +void Pomme::Sound::Shutdown() +{ + cmixer::ShutdownWithSDL(); + while (headChan) + { + SndDisposeChannel(headChan->macChannel, true); + } +} + + +std::unique_ptr Pomme::Sound::GetCodec(uint32_t fourCC) +{ + switch (fourCC) + { + case 0: // Assume MACE-3 by default. + case 'MAC3': + return std::make_unique(); + case 'ima4': + return std::make_unique(); + case 'alaw': + case 'ulaw': + return std::make_unique(fourCC); + default: + throw std::runtime_error("Unknown audio codec: " + Pomme::FourCCString(fourCC)); + } +} \ No newline at end of file diff --git a/src/Sound/cmixer.cpp b/src/Sound/cmixer.cpp new file mode 100644 index 0000000..7c5a731 --- /dev/null +++ b/src/Sound/cmixer.cpp @@ -0,0 +1,649 @@ +// Adapted from cmixer by rxi (https://github.com/rxi/cmixer) + +/* +** Copyright (c) 2017 rxi +** +** 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 "Sound/cmixer.h" +#include "Utilities/structpack.h" +#include + +#include +#include +#include + +using namespace cmixer; + +#define CLAMP(x, a, b) ((x) < (a) ? (a) : (x) > (b) ? (b) : (x)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#define FX_BITS (12) +#define FX_UNIT (1 << FX_BITS) +#define FX_MASK (FX_UNIT - 1) +#define FX_FROM_FLOAT(f) ((long)((f) * FX_UNIT)) +#define DOUBLE_FROM_FX(f) ((double)f / FX_UNIT) +#define FX_LERP(a, b, p) ((a) + ((((b) - (a)) * (p)) >> FX_BITS)) + +#define BUFFER_MASK (BUFFER_SIZE - 1) + +static constexpr bool INTERPOLATED_RESAMPLING = false; + +//----------------------------------------------------------------------------- +// Global mixer + +static struct Mixer +{ + SDL_mutex* sdlAudioMutex; + + std::list sources; // Linked list of active (playing) sources + int32_t pcmmixbuf[BUFFER_SIZE]; // Internal master buffer + int samplerate; // Master samplerate + int gain; // Master gain (fixed point) + + void Init(int samplerate); + + void Process(int16_t* dst, int len); + + void Lock(); + + void Unlock(); + + void SetMasterGain(double newGain); +} gMixer = {}; + +//----------------------------------------------------------------------------- +// Global init/shutdown + +static bool sdlAudioSubSystemInited = false; +static SDL_AudioDeviceID sdlDeviceID = 0; + +void cmixer::InitWithSDL() +{ + if (sdlAudioSubSystemInited) + throw std::runtime_error("SDL audio subsystem already inited"); + + if (0 != SDL_InitSubSystem(SDL_INIT_AUDIO)) + throw std::runtime_error("couldn't init SDL audio subsystem"); + + sdlAudioSubSystemInited = true; + + // Init SDL audio + SDL_AudioSpec fmt = {}; + fmt.freq = 44100; + fmt.format = AUDIO_S16; + fmt.channels = 2; + fmt.samples = 1024; + fmt.callback = [](void* udata, Uint8* stream, int size) + { + gMixer.Process((int16_t*) stream, size / 2); + }; + + SDL_AudioSpec got; + sdlDeviceID = SDL_OpenAudioDevice(NULL, 0, &fmt, &got, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + if (!sdlDeviceID) + throw std::runtime_error(SDL_GetError()); + + // Init library + gMixer.Init(got.freq); + gMixer.SetMasterGain(0.5); + + // Start audio + SDL_PauseAudioDevice(sdlDeviceID, 0); +} + +void cmixer::ShutdownWithSDL() +{ + if (sdlDeviceID) + { + SDL_CloseAudioDevice(sdlDeviceID); + sdlDeviceID = 0; + } + if (gMixer.sdlAudioMutex) + { + SDL_DestroyMutex(gMixer.sdlAudioMutex); + gMixer.sdlAudioMutex = nullptr; + } + if (sdlAudioSubSystemInited) + { + SDL_QuitSubSystem(SDL_INIT_AUDIO); + sdlAudioSubSystemInited = false; + } +} + +double cmixer::GetMasterGain() +{ + return DOUBLE_FROM_FX(gMixer.gain); +} + +void cmixer::SetMasterGain(double newGain) +{ + gMixer.SetMasterGain(newGain); +} + +//----------------------------------------------------------------------------- +// Global mixer impl + +void Mixer::Lock() +{ + SDL_LockMutex(sdlAudioMutex); +} + +void Mixer::Unlock() +{ + SDL_UnlockMutex(sdlAudioMutex); +} + +void Mixer::Init(int newSamplerate) +{ + sdlAudioMutex = SDL_CreateMutex(); + + samplerate = newSamplerate; + gain = FX_UNIT; +} + +void Mixer::SetMasterGain(double newGain) +{ + if (newGain < 0) + newGain = 0; + gain = FX_FROM_FLOAT(newGain); +} + +void Mixer::Process(int16_t* dst, int len) +{ + // Process in chunks of BUFFER_SIZE if `len` is larger than BUFFER_SIZE + while (len > BUFFER_SIZE) + { + Process(dst, BUFFER_SIZE); + dst += BUFFER_SIZE; + len -= BUFFER_SIZE; + } + + // Zeroset internal buffer + memset(pcmmixbuf, 0, len * sizeof(pcmmixbuf[0])); + + // Process active sources + Lock(); + for (auto si = sources.begin(); si != sources.end();) + { + auto& s = **si; + s.Process(len); + // Remove source from list if it is no longer playing + if (s.state != CM_STATE_PLAYING) + { + s.active = false; + si = sources.erase(si); + } + else + { + ++si; + } + } + Unlock(); + + // Copy internal buffer to destination and clip + for (int i = 0; i < len; i++) + { + int x = (pcmmixbuf[i] * gain) >> FX_BITS; + dst[i] = CLAMP(x, -32768, 32767); + } +} + +//----------------------------------------------------------------------------- +// Source implementation + +Source::Source() +{ + active = false; + Clear(); +} + +void Source::Clear() +{ + samplerate = 0; + length = 0; + end = 0; + state = CM_STATE_STOPPED; + position = 0; + lgain = 0; + rgain = 0; + rate = 0; + nextfill = 0; + loop = false; + rewind = true; + // DON'T touch active. The source may still be in gMixer! + gain = 0; + pan = 0; + onComplete = nullptr; +} + +void Source::Init(int theSampleRate, int theLength) +{ + this->samplerate = theSampleRate; + this->length = theLength; + SetGain(1); + SetPan(0); + SetPitch(1); + SetLoop(false); + Stop(); +} + +Source::~Source() +{ + gMixer.Lock(); + if (active) + { + gMixer.sources.remove(this); + } + gMixer.Unlock(); +} + +void Source::Rewind() +{ + Rewind2(); + position = 0; + rewind = false; + end = length; + nextfill = 0; +} + +void Source::FillBuffer(int offset, int fillLength) +{ + FillBuffer(pcmbuf + offset, fillLength); +} + +void Source::Process(int len) +{ + int32_t* dst = gMixer.pcmmixbuf; + + // Do rewind if flag is set + if (rewind) + { + Rewind(); + } + + // Don't process if not playing + if (state != CM_STATE_PLAYING) + { + return; + } + + // Process audio + while (len > 0) + { + // Get current position frame + int frame = int(position >> FX_BITS); + + // Fill buffer if required + if (frame + 3 >= nextfill) + { + FillBuffer((nextfill * 2) & BUFFER_MASK, BUFFER_SIZE / 2); + nextfill += BUFFER_SIZE / 4; + } + + // Handle reaching the end of the playthrough + if (frame >= end) + { + // As streams continiously fill the raw buffer in a loop we simply + // increment the end idx by one length and continue reading from it for + // another play-through + end = frame + this->length; + // Set state and stop processing if we're not set to loop + if (!loop) + { + state = CM_STATE_STOPPED; + if (onComplete != nullptr) + onComplete(); + break; + } + } + + // Work out how many frames we should process in the loop + int n = MIN(nextfill - 2, end) - frame; + int count = (n << FX_BITS) / rate; + count = MAX(count, 1); + count = MIN(count, len / 2); + len -= count * 2; + + // Add audio to master buffer + if (rate == FX_UNIT) + { + // Add audio to buffer -- basic + n = frame * 2; + for (int i = 0; i < count; i++) + { + dst[0] += (pcmbuf[(n ) & BUFFER_MASK] * lgain) >> FX_BITS; + dst[1] += (pcmbuf[(n + 1) & BUFFER_MASK] * rgain) >> FX_BITS; + n += 2; + dst += 2; + } + this->position += count * FX_UNIT; + } + else if (INTERPOLATED_RESAMPLING) + { + // Resample audio (with linear interpolation) and add to buffer + for (int i = 0; i < count; i++) + { + n = int(position >> FX_BITS) * 2; + int p = position & FX_MASK; + int a = pcmbuf[(n ) & BUFFER_MASK]; + int b = pcmbuf[(n + 2) & BUFFER_MASK]; + dst[0] += (FX_LERP(a, b, p) * lgain) >> FX_BITS; + n++; + a = pcmbuf[(n ) & BUFFER_MASK]; + b = pcmbuf[(n + 2) & BUFFER_MASK]; + dst[1] += (FX_LERP(a, b, p) * rgain) >> FX_BITS; + position += rate; + dst += 2; + } + } + else + { + // Resample audio (without interpolation) and add to buffer + for (int i = 0; i < count; i++) + { + n = int(position >> FX_BITS) * 2; + dst[0] += (pcmbuf[(n ) & BUFFER_MASK] * lgain) >> FX_BITS; + dst[1] += (pcmbuf[(n + 1) & BUFFER_MASK] * rgain) >> FX_BITS; + position += rate; + dst += 2; + } + } + } +} + +double Source::GetLength() const +{ + return length / (double) samplerate; +} + +double Source::GetPosition() const +{ + return ((position >> FX_BITS) % length) / (double) samplerate; +} + +int Source::GetState() const +{ + return state; +} + +void Source::RecalcGains() +{ + double l = this->gain * (pan <= 0. ? 1. : 1. - pan); + double r = this->gain * (pan >= 0. ? 1. : 1. + pan); + this->lgain = FX_FROM_FLOAT(l); + this->rgain = FX_FROM_FLOAT(r); +} + +void Source::SetGain(double newGain) +{ + gain = newGain; + RecalcGains(); +} + +void Source::SetPan(double newPan) +{ + pan = CLAMP(newPan, -1.0, 1.0); + RecalcGains(); +} + +void Source::SetPitch(double newPitch) +{ + double newRate; + if (newPitch > 0.) + { + newRate = samplerate / (double) gMixer.samplerate * newPitch; + } + else + { + newRate = 0.001; + } + rate = FX_FROM_FLOAT(newRate); +} + +void Source::SetLoop(bool newLoop) +{ + loop = newLoop; +} + +void Source::Play() +{ + gMixer.Lock(); + state = CM_STATE_PLAYING; + if (!active) + { + active = true; + gMixer.sources.push_front(this); + } + gMixer.Unlock(); +} + +void Source::Pause() +{ + state = CM_STATE_PAUSED; +} + +void Source::TogglePause() +{ + if (state == CM_STATE_PAUSED) + Play(); + else if (state == CM_STATE_PLAYING) + Pause(); +} + +void Source::Stop() +{ + state = CM_STATE_STOPPED; + rewind = true; +} + +//----------------------------------------------------------------------------- +// WavStream implementation + +#define WAV_PROCESS_LOOP(X) \ + while (n--) \ + { \ + X \ + dst += 2; \ + idx++; \ + } + +WavStream::WavStream() + : Source() + , bitdepth(0) + , channels(0) + , bigEndian(false) + , idx(0) + , userBuffer() +{} + +void WavStream::Clear() +{ + Source::Clear(); + bitdepth = 0; + channels = 0; + bigEndian = false; + idx = 0; + userBuffer.clear(); +} + +void WavStream::Init( + int theSampleRate, + int theBitDepth, + int theNChannels, + bool theBigEndian, + std::span theSpan) +{ + Clear(); + Source::Init(theSampleRate, int((theSpan.size() / (theBitDepth / 8)) / theNChannels)); + this->bitdepth = theBitDepth; + this->channels = theNChannels; + this->idx = 0; + this->span = theSpan; + this->bigEndian = theBigEndian; +} + +std::span WavStream::GetBuffer(int nBytesOut) +{ + userBuffer.clear(); + userBuffer.reserve(nBytesOut); + return std::span(userBuffer.data(), nBytesOut); +} + +std::span WavStream::SetBuffer(std::vector&& data) +{ + userBuffer = std::move(data); + return std::span(userBuffer.data(), userBuffer.size()); +} + +void WavStream::Rewind2() +{ + idx = 0; +} + +void WavStream::FillBuffer(int16_t* dst, int len) +{ + int x, n; + + len /= 2; + + while (len > 0) + { + n = MIN(len, length - idx); + len -= n; + if (bigEndian && bitdepth == 16 && channels == 1) + { + WAV_PROCESS_LOOP({ + dst[0] = dst[1] = ByteswapScalar(data16()[idx]); + }); + } + else if (bigEndian && bitdepth == 16 && channels == 2) + { + WAV_PROCESS_LOOP({ + x = idx * 2; + dst[0] = ByteswapScalar(data16()[x]); + dst[1] = ByteswapScalar(data16()[x + 1]); + }); + } + else if (bitdepth == 16 && channels == 1) + { + WAV_PROCESS_LOOP({ + dst[0] = dst[1] = data16()[idx]; + }); + } + else if (bitdepth == 16 && channels == 2) + { + WAV_PROCESS_LOOP({ + x = idx * 2; + dst[0] = data16()[x]; + dst[1] = data16()[x + 1]; + }); + } + else if (bitdepth == 8 && channels == 1) + { + WAV_PROCESS_LOOP({ + dst[0] = dst[1] = (data8()[idx] - 128) << 8; + }); + } + else if (bitdepth == 8 && channels == 2) + { + WAV_PROCESS_LOOP({ + x = idx * 2; + dst[0] = (data8()[x] - 128) << 8; + dst[1] = (data8()[x + 1] - 128) << 8; + }); + } + // Loop back and continue filling buffer if we didn't fill the buffer + if (len > 0) + { + idx = 0; + } + } +} + +#if 0 +//----------------------------------------------------------------------------- +// LoadWAVFromFile for testing + +static std::vector LoadFile(char const* filename) +{ + std::ifstream ifs(filename, std::ios::binary | std::ios::ate); + auto pos = ifs.tellg(); + std::vector bytes(pos); + ifs.seekg(0, std::ios::beg); + ifs.read(&bytes[0], pos); + return bytes; +} + +static const char* FindChunk(const char* data, int len, const char* id, int* size) +{ + // TODO : Error handling on malformed wav file + int idlen = strlen(id); + const char* p = data + 12; +next: + *size = *((cm_UInt32*)(p + 4)); + if (memcmp(p, id, idlen)) { + p += 8 + *size; + if (p > data + len) return NULL; + goto next; + } + return p + 8; +} + +WavStream cmixer::LoadWAVFromFile(const char* path) +{ + int sz; + auto filebuf = LoadFile(path); + auto len = filebuf.size(); + const char* data = filebuf.data(); + const char* p = (char*)data; + + // Check header + if (memcmp(p, "RIFF", 4) || memcmp(p + 8, "WAVE", 4)) + throw std::invalid_argument("bad wav header"); + + // Find fmt subchunk + p = FindChunk(data, len, "fmt ", &sz); + if (!p) + throw std::invalid_argument("no fmt subchunk"); + + // Load fmt info + int format = *((cm_UInt16*)(p)); + int channels = *((cm_UInt16*)(p + 2)); + int samplerate = *((cm_UInt32*)(p + 4)); + int bitdepth = *((cm_UInt16*)(p + 14)); + if (format != 1) + throw std::invalid_argument("unsupported format"); + if (channels == 0 || samplerate == 0 || bitdepth == 0) + throw std::invalid_argument("bad format"); + + // Find data subchunk + p = FindChunk(data, len, "data", &sz); + if (!p) + throw std::invalid_argument("no data subchunk"); + + return WavStream( + samplerate, + bitdepth, + channels, + std::vector(p, p + sz)); +} +#endif diff --git a/src/Sound/cmixer.h b/src/Sound/cmixer.h new file mode 100644 index 0000000..303c282 --- /dev/null +++ b/src/Sound/cmixer.h @@ -0,0 +1,155 @@ +// Adapted from cmixer by rxi (https://github.com/rxi/cmixer) + +/* +** Copyright (c) 2017 rxi +** +** 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 + +#include +#include +#include +#include "CompilerSupport/span.h" + +#define BUFFER_SIZE (512) + +namespace cmixer +{ + + enum + { + CM_STATE_STOPPED, + CM_STATE_PLAYING, + CM_STATE_PAUSED + }; + + struct Source + { + int16_t pcmbuf[BUFFER_SIZE]; // Internal buffer with raw stereo PCM + int samplerate; // Stream's native samplerate + int length; // Stream's length in frames + int end; // End index for the current play-through + int state; // Current state (playing|paused|stopped) + int64_t position; // Current playhead position (fixed point) + int lgain, rgain; // Left and right gain (fixed point) + int rate; // Playback rate (fixed point) + int nextfill; // Next frame idx where the buffer needs to be filled + bool loop; // Whether the source will loop when `end` is reached + bool rewind; // Whether the source will rewind before playing + bool active; // Whether the source is part of `sources` list + double gain; // Gain set by `cm_set_gain()` + double pan; // Pan set by `cm_set_pan()` + std::function onComplete; // Callback + + protected: + Source(); + + void Init(int samplerate, int length); + + virtual void Rewind2() = 0; + + virtual void FillBuffer(int16_t* buffer, int length) = 0; + + public: + virtual ~Source(); + + virtual void Clear(); + + void Rewind(); + + void RecalcGains(); + + void FillBuffer(int offset, int length); + + void Process(int len); + + double GetLength() const; + + double GetPosition() const; + + int GetState() const; + + void SetGain(double gain); + + void SetPan(double pan); + + void SetPitch(double pitch); + + void SetLoop(bool loop); + + void Play(); + + void Pause(); + + void TogglePause(); + + void Stop(); + }; + + class WavStream : public Source + { + int bitdepth; + int channels; + bool bigEndian; + int idx; + std::span span; + std::vector userBuffer; + + void Rewind2() override; + + void FillBuffer(int16_t* buffer, int length) override; + + inline uint8_t* data8() const + { return reinterpret_cast(span.data()); } + + inline int16_t* data16() const + { return reinterpret_cast(span.data()); } + + public: + WavStream(); + + virtual void Clear() override; + + void Init( + int theSampleRate, + int theBitDepth, + int nChannels, + bool bigEndian, + std::span data + ); + + std::span GetBuffer(int nBytesOut); + + std::span SetBuffer(std::vector&& data); + }; + + + void InitWithSDL(); + + void ShutdownWithSDL(); + + double GetMasterGain(); + + void SetMasterGain(double); + + WavStream LoadWAVFromFile(const char* path); + +} diff --git a/src/Sound/xlaw.cpp b/src/Sound/xlaw.cpp new file mode 100644 index 0000000..3b3c6c7 --- /dev/null +++ b/src/Sound/xlaw.cpp @@ -0,0 +1,91 @@ +#include "PommeSound.h" + +// Conversion tables to obtain 16-bit PCM from 8-bit a-law/mu-law. +// These tables are valid for *-law input bytes [0...127]. +// For inputs [-127...-1], mirror the table and negate the output. +// +// These tables were generated using alaw2linear() and ulaw2linear() +// from ffmpeg/libavcodec/pcm_tablegen.h. + +static const int16_t alawToPCM[128] = { + -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, + -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, + -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, + -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, +-22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944, +-30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136, +-11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, +-15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568, + -344, -328, -376, -360, -280, -264, -312, -296, + -472, -456, -504, -488, -408, -392, -440, -424, + -88, -72, -120, -104, -24, -8, -56, -40, + -216, -200, -248, -232, -152, -136, -184, -168, + -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, + -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, -592, + -944, -912, -1008, -976, -816, -784, -880, -848, +}; + +static const int16_t ulawToPCM[128] = { +-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, +-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, +-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, +-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, -0, +}; + +Pomme::Sound::xlaw::xlaw(uint32_t codecFourCC) +{ + switch (codecFourCC) + { + case 'ulaw': + xlawToPCM = ulawToPCM; + break; + case 'alaw': + xlawToPCM = alawToPCM; + break; + default: + throw std::runtime_error("unknown xlaw fourCC"); + } +} + +void Pomme::Sound::xlaw::Decode( + const int nChannels, + const std::span input, + const std::span output) +{ + if (2 * input.size() != output.size()) + { + throw std::runtime_error("ulaw: incorrect input/output buffer sizes"); + } + + const std::span out16( + reinterpret_cast(output.data()), + input.size()); + + for (size_t i = 0; i < input.size(); i++) + { + int8_t b = input[i]; // SIGNED! + if (b < 0) + { + // Mirror table and negate output + out16[i] = -xlawToPCM[128 + b]; + } + else + { + out16[i] = xlawToPCM[b]; + } + } +} + diff --git a/src/Time/TimeManager.cpp b/src/Time/TimeManager.cpp new file mode 100644 index 0000000..68be19e --- /dev/null +++ b/src/Time/TimeManager.cpp @@ -0,0 +1,42 @@ +#include "Pomme.h" +#include "PommeTime.h" +#include "PommeTypes.h" +#include "PommeDebug.h" + +#include +#include +#include +#include + +std::chrono::time_point bootTP; + +// timestamp (from unix epoch) of the mac epoch, Jan 1, 1904, 00:00:00 +constexpr int JANUARY_1_1904 = -2'082'844'800; + +//----------------------------------------------------------------------------- +// Time Manager + +void Pomme::Time::Init() +{ + bootTP = std::chrono::high_resolution_clock::now(); +} + +void GetDateTime(unsigned long* secs) +{ + *secs = (unsigned long) (std::time(nullptr) + JANUARY_1_1904); +} + +void Microseconds(UnsignedWide* usecs) +{ + auto now = std::chrono::high_resolution_clock::now(); + auto usecs1 = std::chrono::duration_cast(now - bootTP); + auto usecs2 = usecs1.count(); + usecs->lo = usecs2 & 0xFFFFFFFFL; + usecs->hi = (usecs2 >> 32) & 0xFFFFFFFFL; +} + +UInt32 TickCount() +{ + TODO(); + return 0; +} diff --git a/src/Utilities/BigEndianIStream.cpp b/src/Utilities/BigEndianIStream.cpp new file mode 100644 index 0000000..9c361d9 --- /dev/null +++ b/src/Utilities/BigEndianIStream.cpp @@ -0,0 +1,85 @@ +#include "Utilities/BigEndianIStream.h" +#include "Utilities/IEEEExtended.h" + +Pomme::StreamPosGuard::StreamPosGuard(std::istream& theStream) : + stream(theStream) + , backup(theStream.tellg()) + , active(true) +{ +} + +Pomme::StreamPosGuard::~StreamPosGuard() +{ + if (active) + { + stream.seekg(backup, std::ios_base::beg); + } +} + +void Pomme::StreamPosGuard::Cancel() +{ + active = false; +} + +Pomme::BigEndianIStream::BigEndianIStream(std::istream& theStream) : + stream(theStream) +{ +} + +void Pomme::BigEndianIStream::Read(char* dst, size_t n) +{ + stream.read(dst, n); + if (stream.eof()) + { + throw std::out_of_range("Read past end of stream!"); + } +} + +std::vector Pomme::BigEndianIStream::ReadBytes(size_t n) +{ + std::vector buf(n); + Read(reinterpret_cast(buf.data()), n); + return buf; +} + +std::string Pomme::BigEndianIStream::ReadPascalString() +{ + int length = Read(); + auto bytes = ReadBytes(length); + bytes.push_back('\0'); + return std::string((const char*) &bytes.data()[0]); +} + +std::string Pomme::BigEndianIStream::ReadPascalString_FixedLengthRecord(const int maxChars) +{ + int length = Read(); + char buf[256]; + stream.read(buf, maxChars); + return std::string(buf, length); +} + +double Pomme::BigEndianIStream::Read80BitFloat() +{ + auto bytes = ReadBytes(10); + return ConvertFromIeeeExtended((unsigned char*) bytes.data()); +} + +void Pomme::BigEndianIStream::Goto(std::streamoff absoluteOffset) +{ + stream.seekg(absoluteOffset, std::ios_base::beg); +} + +void Pomme::BigEndianIStream::Skip(size_t n) +{ + stream.seekg(n, std::ios_base::cur); +} + +std::streampos Pomme::BigEndianIStream::Tell() const +{ + return stream.tellg(); +} + +Pomme::StreamPosGuard Pomme::BigEndianIStream::GuardPos() +{ + return Pomme::StreamPosGuard(stream); +} \ No newline at end of file diff --git a/src/Utilities/BigEndianIStream.h b/src/Utilities/BigEndianIStream.h new file mode 100644 index 0000000..3fdc698 --- /dev/null +++ b/src/Utilities/BigEndianIStream.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +namespace Pomme +{ + class StreamPosGuard + { + std::istream& stream; + const std::streampos backup; + bool active; + + public: + StreamPosGuard(std::istream& f); + + ~StreamPosGuard(); + + void Cancel(); + }; + + class BigEndianIStream + { + std::istream& stream; + + public: + BigEndianIStream(std::istream& theStream); + + void Read(char* dst, size_t n); + + void Skip(size_t n); + + void Goto(std::streamoff absoluteOffset); + + std::streampos Tell() const; + + StreamPosGuard GuardPos(); + + std::vector ReadBytes(size_t n); + + std::string ReadPascalString(); + + std::string ReadPascalString_FixedLengthRecord(const int maxChars); + + double Read80BitFloat(); + + template + T Read() + { + char b[sizeof(T)]; + Read(b, sizeof(T)); +#if !(TARGET_RT_BIGENDIAN) + if constexpr (sizeof(T) > 1) + { + std::reverse(b, b + sizeof(T)); + } +#endif + return *(T*) b; + } + }; +} diff --git a/src/Utilities/FixedPool.h b/src/Utilities/FixedPool.h new file mode 100644 index 0000000..b428f95 --- /dev/null +++ b/src/Utilities/FixedPool.h @@ -0,0 +1,46 @@ +#pragma once + +namespace Pomme { + +template +class FixedPool +{ + // fixed size array so that pointers to the elements don't move around in memory + // (unlike how a vector might move around elements on resize) + TObj pool[MAX]; + std::vector freeIDs; + int inUse, inUsePeak; + +public: + FixedPool() + { + inUse = 0; + inUsePeak = 0; + freeIDs.reserve(MAX); + for (int i = MAX - 1; i >= 0; i--) + freeIDs.push_back(i); + } + + TObj* Alloc() + { + if (freeIDs.empty()) + throw std::length_error("pool exhausted"); + TId id = freeIDs.back(); + freeIDs.pop_back(); + inUse++; + if (inUse > inUsePeak) + inUsePeak = inUse; + return &pool[id]; + } + + void Dispose(TObj* obj) + { + intptr_t id = obj - &pool[0]; + if (id < 0 || id >= MAX) + throw std::invalid_argument("obj isn't stored in pool"); + inUse--; + freeIDs.push_back((TId)id); + } +}; + +} diff --git a/src/Utilities/GrowablePool.h b/src/Utilities/GrowablePool.h new file mode 100644 index 0000000..e1f66a2 --- /dev/null +++ b/src/Utilities/GrowablePool.h @@ -0,0 +1,90 @@ +#pragma once + +#include + +namespace Pomme +{ + + template + class GrowablePool + { + std::vector pool; + std::vector isInUse; + std::vector freeIDs; + TId inUse, inUsePeak; + + public: + GrowablePool() + { + inUse = 0; + inUsePeak = 0; + } + + TId Alloc() + { + if (IsFull()) throw std::length_error("too many items allocated"); + + inUse++; + if (inUse > inUsePeak) + { + inUsePeak = inUse; + } + + if (!freeIDs.empty()) + { + auto id = freeIDs.back(); + freeIDs.pop_back(); + isInUse[id] = true; + return id; + } + else + { + pool.emplace_back(); + isInUse.push_back(true); + return TId(pool.size() - 1); + } + } + + void Dispose(TId id) + { + if (!IsAllocated(id)) throw std::invalid_argument("id isn't allocated"); + inUse--; + freeIDs.push_back(id); + isInUse[id] = false; + Compact(); + } + + void Compact() + { + while (!freeIDs.empty() && freeIDs.back() == (TId) pool.size() - 1) + { + freeIDs.pop_back(); + pool.pop_back(); + isInUse.pop_back(); + } + } + + TObj& operator[](TId id) + { + if (!IsAllocated(id)) throw std::invalid_argument("id isn't allocated"); + return pool[id]; + } + + const TObj& operator[](TId id) const + { + if (!IsAllocated(id)) throw std::invalid_argument("id isn't allocated"); + return pool[id]; + } + + bool IsFull() const + { + return inUse >= MAX; + } + + bool IsAllocated(TId id) const + { + return id >= 0 && (unsigned) id < pool.size() && isInUse[id]; + } + }; + +} \ No newline at end of file diff --git a/src/Utilities/IEEEExtended.cpp b/src/Utilities/IEEEExtended.cpp new file mode 100644 index 0000000..41f900f --- /dev/null +++ b/src/Utilities/IEEEExtended.cpp @@ -0,0 +1,126 @@ +/* Copyright (C) 1988-1991 Apple Computer, Inc. + * All rights reserved. + * + * Machine-independent I/O routines for IEEE floating-point numbers. + * + * NaN's and infinities are converted to HUGE_VAL or HUGE, which + * happens to be infinity on IEEE machines. Unfortunately, it is + * impossible to preserve NaN's in a machine-independent way. + * Infinities are, however, preserved on IEEE machines. + * + * These routines have been tested on the following machines: + * Apple Macintosh, MPW 3.1 C compiler + * Apple Macintosh, THINK C compiler + * Silicon Graphics IRIS, MIPS compiler + * Cray X/MP and Y/MP + * Digital Equipment VAX + * + * + * Implemented by Malcolm Slaney and Ken Turkowski. + * + * Malcolm Slaney contributions during 1988-1990 include big- and little- + * endian file I/O, conversion to and from Motorola's extended 80-bit + * floating-point format, and conversions to and from IEEE single- + * precision floating-point format. + * + * In 1991, Ken Turkowski implemented the conversions to and from + * IEEE double-precision format, added more precision to the extended + * conversions, and accommodated conversions involving +/- infinity, + * NaN's, and denormalized numbers. + */ + +#include +#include "IEEEExtended.h" + +#ifndef HUGE_VAL +# define HUGE_VAL HUGE +#endif + +#define FloatToUnsigned(f) ((unsigned long)(((long)(f - 2147483648.0)) + 2147483647L) + 1) +#define UnsignedToFloat(u) (((double)((long)(u - 2147483647L - 1))) + 2147483648.0) + +void ConvertToIeeeExtended(double num, char* bytes) +{ + int sign; + int expon; + double fMant, fsMant; + unsigned long hiMant, loMant; + + if (num < 0) { + sign = 0x8000; + num *= -1; + } else { + sign = 0; + } + + if (num == 0) { + expon = 0; hiMant = 0; loMant = 0; + } + else { + fMant = frexp(num, &expon); + if ((expon > 16384) || !(fMant < 1)) { /* Infinity or NaN */ + expon = sign|0x7FFF; hiMant = 0; loMant = 0; /* infinity */ + } + else { /* Finite */ + expon += 16382; + if (expon < 0) { /* denormalized */ + fMant = ldexp(fMant, expon); + expon = 0; + } + expon |= sign; + fMant = ldexp(fMant, 32); + fsMant = floor(fMant); + hiMant = FloatToUnsigned(fsMant); + fMant = ldexp(fMant - fsMant, 32); + fsMant = floor(fMant); + loMant = FloatToUnsigned(fsMant); + } + } + + bytes[0] = char(expon >> 8); + bytes[1] = char(expon); + bytes[2] = char(hiMant >> 24); + bytes[3] = char(hiMant >> 16); + bytes[4] = char(hiMant >> 8); + bytes[5] = char(hiMant); + bytes[6] = char(loMant >> 24); + bytes[7] = char(loMant >> 16); + bytes[8] = char(loMant >> 8); + bytes[9] = char(loMant); +} + +double ConvertFromIeeeExtended(const unsigned char* bytes /* LCN */) +{ + double f; + int expon; + unsigned long hiMant, loMant; + + expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF); + hiMant = ((unsigned long)(bytes[2] & 0xFF) << 24) + | ((unsigned long)(bytes[3] & 0xFF) << 16) + | ((unsigned long)(bytes[4] & 0xFF) << 8) + | ((unsigned long)(bytes[5] & 0xFF)); + loMant = ((unsigned long)(bytes[6] & 0xFF) << 24) + | ((unsigned long)(bytes[7] & 0xFF) << 16) + | ((unsigned long)(bytes[8] & 0xFF) << 8) + | ((unsigned long)(bytes[9] & 0xFF)); + + if (expon == 0 && hiMant == 0 && loMant == 0) { + f = 0; + } + else { + if (expon == 0x7FFF) { /* Infinity or NaN */ + f = HUGE_VAL; + } + else { + expon -= 16383; + f = ldexp(UnsignedToFloat(hiMant), expon-=31); + f += ldexp(UnsignedToFloat(loMant), expon-=32); + } + } + + if (bytes[0] & 0x80) + return -f; + else + return f; +} diff --git a/src/Utilities/IEEEExtended.h b/src/Utilities/IEEEExtended.h new file mode 100644 index 0000000..c28f9d8 --- /dev/null +++ b/src/Utilities/IEEEExtended.h @@ -0,0 +1,5 @@ +#pragma once + +void ConvertToIeeeExtended(double num, char* bytes); + +double ConvertFromIeeeExtended(const unsigned char* bytes); diff --git a/src/Utilities/StringUtils.cpp b/src/Utilities/StringUtils.cpp new file mode 100644 index 0000000..d3421b9 --- /dev/null +++ b/src/Utilities/StringUtils.cpp @@ -0,0 +1,28 @@ +#include "Utilities/StringUtils.h" + +#include +#include + +std::string UppercaseCopy(const std::string& in) +{ + std::string out; + std::transform( + in.begin(), + in.end(), + std::back_inserter(out), + [](unsigned char c) -> unsigned char + { + return (c >= 'a' && c <= 'z') ? ('A' + c - 'a') : c; + }); + return out; +} + +u8string AsU8(const std::string s) +{ + return u8string(s.begin(), s.end()); +} + +std::string FromU8(const u8string u8s) +{ + return std::string(u8s.begin(), u8s.end()); +} diff --git a/src/Utilities/StringUtils.h b/src/Utilities/StringUtils.h new file mode 100644 index 0000000..1ca1cbf --- /dev/null +++ b/src/Utilities/StringUtils.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#if defined(__cplusplus) && __cplusplus > 201703L && defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201907L + typedef std::u8string u8string; +#else + typedef std::string u8string; +#endif + +std::string UppercaseCopy(const std::string&); + +u8string AsU8(const std::string); + +std::string FromU8(const u8string); diff --git a/src/Utilities/memstream.cpp b/src/Utilities/memstream.cpp new file mode 100644 index 0000000..a91e51e --- /dev/null +++ b/src/Utilities/memstream.cpp @@ -0,0 +1,80 @@ +#include "memstream.h" + +//----------------------------------------------------------------------------- +// membuf + +membuf::membuf(char* p, size_t n) +{ + begin = p; + end = p + n; + setg(p, p, p + n); + setp(p, p + n); +} + +membuf::membuf(std::vector& vector) + : membuf(vector.data(), vector.size()) +{ +} + +std::streambuf::pos_type membuf::seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which) +{ + pos_type ret = 0; + + if ((which & std::ios_base::in) > 0) + { + if (dir == std::ios_base::cur) + { + gbump((int32_t) off); + } + else if (dir == std::ios_base::end) + { + setg(begin, end + off, end); + } + else if (dir == std::ios_base::beg) + { + setg(begin, begin + off, end); + } + + ret = gptr() - eback(); + } + + if ((which & std::ios_base::out) > 0) + { + if (dir == std::ios_base::cur) + { + pbump((int32_t) off); + } + else if (dir == std::ios_base::end) + { + setp(begin, end + off); + } + else if (dir == std::ios_base::beg) + { + setp(begin, begin + off); + } + + ret = pptr() - pbase(); + } + + return ret; +} + +std::streambuf::pos_type membuf::seekpos(std::streampos pos, std::ios_base::openmode mode) +{ + return seekoff(pos - pos_type(off_type(0)), std::ios_base::beg, mode); +} + +//----------------------------------------------------------------------------- +// memstream + +memstream::memstream(char* p, size_t n) + : membuf(p, n) + , std::iostream(this) +{ +} + +memstream::memstream(std::vector& data) + : membuf(data) + , std::iostream(this) +{ +} diff --git a/src/Utilities/memstream.h b/src/Utilities/memstream.h new file mode 100644 index 0000000..93885b5 --- /dev/null +++ b/src/Utilities/memstream.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +class membuf : public std::basic_streambuf +{ + char* begin; + char* end; + +public: + membuf(char* p, size_t n); + + explicit membuf(std::vector&); + + virtual ~membuf() override = default; + + virtual pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which = std::ios_base::in) override; + + virtual pos_type seekpos(std::streampos pos, std::ios_base::openmode mode) override; +}; + +class memstream + : membuf + , public std::iostream +{ +public: + memstream(char* p, size_t n); + + explicit memstream(std::vector& data); + + virtual ~memstream() = default; +}; diff --git a/src/Utilities/structpack.cpp b/src/Utilities/structpack.cpp new file mode 100644 index 0000000..31b85b0 --- /dev/null +++ b/src/Utilities/structpack.cpp @@ -0,0 +1,122 @@ +#include "structpack.h" +#include +#include + +static int Unpack(const char* format, char* buffer) +{ + int totalBytes = 0; + int repeat = 0; + + for (const char* c2 = format; *c2; c2++) + { + char c = *c2; + int fieldLength = -1; + + switch (c) + { + case '>': // big endian indicator (for compat with python's struct) - OK just ignore + continue; + + case ' ': + case '\r': + case '\n': + case '\t': + continue; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (repeat) repeat *= 10; + repeat += c - '0'; + continue; + + case 'x': // pad byte + case 'c': // char + case 'b': // signed char + case 'B': // unsigned char + case '?': // bool + fieldLength = 1; + break; + + case 'h': // short + case 'H': // unsigned short + fieldLength = 2; + break; + + case 'i': // int + case 'I': // unsigned int + case 'l': // long + case 'L': // unsigned long + case 'f': // float + fieldLength = 4; + break; + + case 'q': // long long + case 'Q': // unsigned long long + case 'd': // double + fieldLength = 8; + break; + + default: + throw std::invalid_argument("unknown format char in structpack format"); + } + + if (totalBytes % fieldLength != 0) + { + throw std::invalid_argument("illegal word alignment in structpack format"); + } + + if (!repeat) + repeat = 1; + + bool doSwap = fieldLength > 1; + + if (buffer) + { + if (doSwap) + { + for (int i = 0; i < repeat; i++) + { + std::reverse(buffer, buffer + fieldLength); + buffer += fieldLength; + } + } + else + { + buffer += repeat * fieldLength; + } + } + + totalBytes += repeat * fieldLength; + repeat = 0; + } + + return totalBytes; +} + +int ByteswapStructs(const char* format, int structSize, int structCount, void* buffer) +{ + char* byteBuffer = (char*) buffer; + int totalBytes = 0; + for (int i = 0; i < structCount; i++) + { + int newSize = Unpack(format, byteBuffer); + byteBuffer += newSize; + totalBytes += newSize; + } + if (totalBytes != structSize * structCount) + { + throw std::invalid_argument("unexpected length after byteswap"); + } + return totalBytes; +} + +int ByteswapInts(int intSize, int intCount, void* buffer) +{ + char* byteBuffer = (char*) buffer; + for (int i = 0; i < intCount; i++) + { + std::reverse(byteBuffer, byteBuffer + intSize); + byteBuffer += intSize; + } + return intCount * intSize; +} diff --git a/src/Utilities/structpack.h b/src/Utilities/structpack.h new file mode 100644 index 0000000..02ac6e0 --- /dev/null +++ b/src/Utilities/structpack.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#ifdef __cplusplus + +#include + +template T ByteswapScalar(T x) +{ +#if TARGET_RT_BIGENDIAN + return x; +#else + char* b = (char*)&x; + std::reverse(b, b + sizeof(T)); + return x; +#endif +} + +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +int ByteswapStructs(const char* format, int structSize, int structCount, void* buffer); + +int ByteswapInts(int intSize, int intCount, void* buffer); + +static inline uint16_t Byteswap16(const void* p) +{ + uint16_t v; + v = + (*(const uint8_t*) p) + | ((*(const uint8_t*) p + 1) << 8); + return v; +} + +static inline uint32_t Byteswap32(const void* p) +{ + uint32_t v; + v = + (*(const uint8_t*) p) + | ((*(const uint8_t*) p + 1) << 8) + | ((*(const uint8_t*) p + 2) << 16) + | ((*(const uint8_t*) p + 3) << 24); + return v; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/Video/Cinepak.cpp b/src/Video/Cinepak.cpp new file mode 100644 index 0000000..e7a0e51 --- /dev/null +++ b/src/Video/Cinepak.cpp @@ -0,0 +1,400 @@ +// Adapted from ffmpeg + +// ---- Begin ffmpeg copyright notices ---- + +/* + * Cinepak Video Decoder + * Copyright (C) 2003 The FFmpeg project + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Cinepak video decoder + * @author Ewald Snel + * + * Cinepak colorspace support (c) 2013 Rl, Aetey Global Technologies AB + * @author Cinepak colorspace, Rl, Aetey Global Technologies AB + */ + +// ---- End ffmpeg copyright notices ---- + +#include "Video/Cinepak.h" +#include +#include + +class CinepakException : public std::runtime_error +{ +public: + CinepakException(const char* m) : std::runtime_error(m) + {} +}; + +static uint8_t av_clip_uint8(int x) +{ + return x > 255 ? 255 : (x < 0 ? 0 : x); +} + +static uint16_t AV_RB16(const uint8_t* in) +{ + return ((uint16_t)in[0] << 8) + | ((uint16_t)in[1]); +} + +static uint32_t AV_RB24(const uint8_t* in) +{ + return ((uint32_t)in[0] << 16) + | ((uint32_t)in[1] << 8) + | (uint32_t)in[2]; +} + +static uint32_t AV_RB32(const uint8_t* in) +{ + return ((uint32_t)in[0] << 24) + | ((uint32_t)in[1] << 16) + | ((uint32_t)in[2] << 8) + | (uint32_t)in[3]; +} + +static void cinepak_decode_codebook (cvid_codebook *codebook, + int chunk_id, int size, const uint8_t *data) +{ + const uint8_t *eod = (data + size); + uint32_t flag, mask; + int i, n; + uint8_t *p; + + /* check if this chunk contains 4- or 6-element vectors */ + n = (chunk_id & 0x04) ? 4 : 6; + flag = 0; + mask = 0; + + p = codebook[0]; + for (i=0; i < 256; i++) { + if ((chunk_id & 0x01) && !(mask >>= 1)) { + if ((data + 4) > eod) + break; + + flag = AV_RB32 (data); + data += 4; + mask = 0x80000000; + } + + if (!(chunk_id & 0x01) || (flag & mask)) { + int k, kk; + + if ((data + n) > eod) + break; + + for (k = 0; k < 4; ++k) { + int r = *data++; + for (kk = 0; kk < 3; ++kk) + *p++ = r; + } + if (n == 6) { + int r, g, b, u, v; + u = *(int8_t *)data++; + v = *(int8_t *)data++; + p -= 12; + for(k=0; k<4; ++k) { + r = *p++ + v*2; + g = *p++ - (u/2) - v; + b = *p + u*2; + p -= 2; + *p++ = av_clip_uint8(r); + *p++ = av_clip_uint8(g); + *p++ = av_clip_uint8(b); + } + } + } else { + p += 12; + } + } +} + +void cinepak_decode_vectors ( + CinepakContext *s, + cvid_strip *strip, + int chunk_id, + int size, + const uint8_t *data) +{ + const uint8_t *eod = (data + size); + uint32_t flag, mask; + uint8_t *cb0, *cb1, *cb2, *cb3; + int x, y; + uint8_t *ip0, *ip1, *ip2, *ip3; + + flag = 0; + mask = 0; + + for (y=strip->y1; y < strip->y2; y+=4) { + +/* take care of y dimension not being multiple of 4, such streams exist */ + ip0 = ip1 = ip2 = ip3 = s->frame_data0 + + (strip->x1*3) + (y * s->frame_linesize0); + if(s->avctx_height - y > 1) { + ip1 = ip0 + s->frame_linesize0; + if(s->avctx_height - y > 2) { + ip2 = ip1 + s->frame_linesize0; + if(s->avctx_height - y > 3) { + ip3 = ip2 + s->frame_linesize0; + } + } + } +/* to get the correct picture for not-multiple-of-4 cases let us fill each + * block from the bottom up, thus possibly overwriting the bottommost line + * more than once but ending with the correct data in place + * (instead of in-loop checking) */ + + for (x=strip->x1; x < strip->x2; x+=4) { + if ((chunk_id & 0x01) && !(mask >>= 1)) { + if ((data + 4) > eod) + throw CinepakException("invalid data"); + + flag = AV_RB32 (data); + data += 4; + mask = 0x80000000; + } + + if (!(chunk_id & 0x01) || (flag & mask)) { + if (!(chunk_id & 0x02) && !(mask >>= 1)) { + if ((data + 4) > eod) + throw CinepakException("invalid data"); + + flag = AV_RB32 (data); + data += 4; + mask = 0x80000000; + } + + if ((chunk_id & 0x02) || (~flag & mask)) { + uint8_t *p; + if (data >= eod) + throw CinepakException("invalid data"); + + p = strip->v1_codebook[*data++]; + + p += 6; + memcpy(ip3 + 0, p, 3); memcpy(ip3 + 3, p, 3); + memcpy(ip2 + 0, p, 3); memcpy(ip2 + 3, p, 3); + p += 3; /* ... + 9 */ + memcpy(ip3 + 6, p, 3); memcpy(ip3 + 9, p, 3); + memcpy(ip2 + 6, p, 3); memcpy(ip2 + 9, p, 3); + p -= 9; /* ... + 0 */ + memcpy(ip1 + 0, p, 3); memcpy(ip1 + 3, p, 3); + memcpy(ip0 + 0, p, 3); memcpy(ip0 + 3, p, 3); + p += 3; /* ... + 3 */ + memcpy(ip1 + 6, p, 3); memcpy(ip1 + 9, p, 3); + memcpy(ip0 + 6, p, 3); memcpy(ip0 + 9, p, 3); + + } else if (flag & mask) { + if ((data + 4) > eod) + throw CinepakException("invalid data"); + + cb0 = strip->v4_codebook[*data++]; + cb1 = strip->v4_codebook[*data++]; + cb2 = strip->v4_codebook[*data++]; + cb3 = strip->v4_codebook[*data++]; + + memcpy(ip3 + 0, cb2 + 6, 6); + memcpy(ip3 + 6, cb3 + 6, 6); + memcpy(ip2 + 0, cb2 + 0, 6); + memcpy(ip2 + 6, cb3 + 0, 6); + memcpy(ip1 + 0, cb0 + 6, 6); + memcpy(ip1 + 6, cb1 + 6, 6); + memcpy(ip0 + 0, cb0 + 0, 6); + memcpy(ip0 + 6, cb1 + 0, 6); + } + } + + ip0 += 12; ip1 += 12; + ip2 += 12; ip3 += 12; + } + } +} + +void cinepak_decode_strip ( + CinepakContext *s, + cvid_strip *strip, + const uint8_t *data, + int size) +{ + const uint8_t *eod = (data + size); + int chunk_id, chunk_size; + + /* coordinate sanity checks */ + if (strip->x2 > s->width || + strip->y2 > s->height || + strip->x1 >= strip->x2 || strip->y1 >= strip->y2) + throw CinepakException("invalid data"); + + while ((data + 4) <= eod) { + chunk_id = data[0]; + chunk_size = AV_RB24 (&data[1]) - 4; + if(chunk_size < 0) + throw CinepakException("invalid data"); + + data += 4; + chunk_size = ((data + chunk_size) > eod) ? (eod - data) : chunk_size; + + switch (chunk_id) { + + case 0x20: + case 0x21: + case 0x24: + case 0x25: + cinepak_decode_codebook (strip->v4_codebook, chunk_id, chunk_size, data); + break; + + case 0x22: + case 0x23: + case 0x26: + case 0x27: + cinepak_decode_codebook (strip->v1_codebook, chunk_id, chunk_size, data); + break; + + case 0x30: + case 0x31: + case 0x32: + cinepak_decode_vectors (s, strip, chunk_id, chunk_size, data); + return; + } + + data += chunk_size; + } + + throw CinepakException("invalid data"); +} + +void cinepak_predecode_check (CinepakContext *s) +{ + int num_strips; + int encoded_buf_size; + + num_strips = AV_RB16 (&s->data[8]); + encoded_buf_size = AV_RB24(&s->data[1]); + + if (s->size < encoded_buf_size) + throw CinepakException("invalid data"); + + if (s->size < 10 /*+ s->sega_film_skip_bytes*/ + num_strips * 12) + throw CinepakException("invalid data"); + + if (num_strips) { + const uint8_t* data = s->data + 10; //+ s->sega_film_skip_bytes; + int strip_size = AV_RB24 (data + 1); + if (strip_size < 12 || strip_size > encoded_buf_size) + throw CinepakException("invalid data"); + } +} + +static void cinepak_decode (CinepakContext *s) +{ + const uint8_t *eod = (s->data + s->size); + int i, strip_size, frame_flags, num_strips; + int y0 = 0; + + frame_flags = s->data[0]; + num_strips = AV_RB16 (&s->data[8]); + + s->data += 10; + + num_strips = std::min(num_strips, CINEPAK_MAX_STRIPS); + +// s->frame->key_frame = 0; + + for (i=0; i < num_strips; i++) { + if ((s->data + 12) > eod) + throw CinepakException("invalid data"); + + s->strips[i].id = s->data[0]; +/* zero y1 means "relative to the previous stripe" */ + if (!(s->strips[i].y1 = AV_RB16 (&s->data[4]))) + s->strips[i].y2 = (s->strips[i].y1 = y0) + AV_RB16 (&s->data[8]); + else + s->strips[i].y2 = AV_RB16 (&s->data[8]); + s->strips[i].x1 = AV_RB16 (&s->data[6]); + s->strips[i].x2 = AV_RB16 (&s->data[10]); + +// if (s->strips[i].id == 0x10) +// s->frame->key_frame = 1; + + strip_size = AV_RB24 (&s->data[1]) - 12; + if (strip_size < 0) + throw CinepakException("invalid data"); + s->data += 12; + strip_size = ((s->data + strip_size) > eod) ? (eod - s->data) : strip_size; + + if ((i > 0) && !(frame_flags & 0x01)) { + memcpy (s->strips[i].v4_codebook, s->strips[i-1].v4_codebook, + sizeof(s->strips[i].v4_codebook)); + memcpy (s->strips[i].v1_codebook, s->strips[i-1].v1_codebook, + sizeof(s->strips[i].v1_codebook)); + } + + cinepak_decode_strip (s, &s->strips[i], s->data, strip_size); + + s->data += strip_size; + y0 = s->strips[i].y2; + } +} + +CinepakContext::CinepakContext(int _width, int _height) + : strips(CINEPAK_MAX_STRIPS) +{ + avctx_width = _width; + avctx_height = _height; + width = (avctx_width + 3) & ~3; + height = (avctx_height + 3) & ~3; + + frame_data0 = new uint8_t[width * height * 3]; + frame_linesize0 = width*3; + if (!frame_data0) + throw CinepakException("couldn't allocate frame"); +} + +void CinepakContext::DecodeFrame(const uint8_t* packet_data, const int packet_size) +{ + if (packet_size < 10) + throw CinepakException("invalid data -- input buffer too small?"); + + this->data = packet_data; + this->size = packet_size; + + int num_strips = AV_RB16 (&this->data[8]); + + //Empty frame, do not waste time + if (!num_strips) + return; + + cinepak_predecode_check(this); + cinepak_decode(this); +} + +CinepakContext::~CinepakContext() +{ + delete[] frame_data0; +} + +void CinepakContext::DumpFrameTGA(const char* outFN) +{ + std::ofstream out(outFN, std::ios::out | std::ios::binary); + uint16_t TGAhead[] = { 0, 2, 0, 0, 0, 0, (uint16_t)width, (uint16_t)height, 24 }; + out.write(reinterpret_cast(&TGAhead), sizeof(TGAhead)); + out.write(reinterpret_cast(frame_data0), width*height*3); +} diff --git a/src/Video/Cinepak.h b/src/Video/Cinepak.h new file mode 100644 index 0000000..c299dbe --- /dev/null +++ b/src/Video/Cinepak.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +#define CINEPAK_MAX_STRIPS 32 + +typedef uint8_t cvid_codebook[12]; + +struct cvid_strip +{ + uint16_t id; + uint16_t x1, y1; + uint16_t x2, y2; + cvid_codebook v4_codebook[256]; + cvid_codebook v1_codebook[256]; +}; + +struct CinepakContext +{ + std::vector strips; + + int avctx_width; + int avctx_height; + int width, height; + + uint8_t* frame_data0; + int frame_linesize0; + + const uint8_t* data; + int size; + +public: + CinepakContext(int avctx_width, int avctx_height); + + ~CinepakContext(); + + void DecodeFrame(const uint8_t* packet_data, const int packet_size); + + void DumpFrameTGA(const char* outFN); +}; + diff --git a/src/Video/moov.cpp b/src/Video/moov.cpp new file mode 100644 index 0000000..9119cc6 --- /dev/null +++ b/src/Video/moov.cpp @@ -0,0 +1,422 @@ +#include "Video/Cinepak.h" +#include "PommeVideo.h" +#include "PommeSound.h" +#include "Utilities/BigEndianIStream.h" +#include "PommeDebug.h" + +#include +#include + +using namespace Pomme::Video; + +class MoovException : public std::runtime_error +{ +public: + MoovException(const std::string m) : std::runtime_error(m) + {} +}; + +//----------------------------------------------------------------------------- +// Utilities + +struct ChunkInfo +{ + UInt32 offset; + UInt32 samplesPerChunk; +}; + +static void MoovAssert(bool condition, const std::string msg) +{ + if (!condition) + { + throw MoovException(msg); + } +} + +template +static void Expect(Pomme::BigEndianIStream& f, const T value, const std::string msg) +{ + T found = f.Read(); + if (value != found) + { + std::stringstream ss; + ss << "moov parser: " << msg << ": incorrect value: expected " << value << ", found " << found; + throw MoovException(ss.str()); + } +} + +struct AtomGuard +{ + Pomme::BigEndianIStream& f; + FourCharCode fourCC; + std::streampos end; + + AtomGuard(Pomme::BigEndianIStream& inputStream, FourCharCode requiredAtomType) + : f(inputStream) + , fourCC(requiredAtomType) + { + auto start = f.Tell(); + auto atomSize = f.Read(); + Expect(f, requiredAtomType, "expected atom"); + end = start + (std::streampos) atomSize; + } + + ~AtomGuard() + { + if (f.Tell() != end) + { + std::cerr << "WARNING: " + << (f.Tell() < end ? "didn't reach " : "read past ") + << "end of atom " << Pomme::FourCCString(fourCC) << "\n"; + } + } +}; + +static void RequireAtomAndSkip(Pomme::BigEndianIStream& f, FourCharCode requiredAtomType) +{ + AtomGuard atom(f, requiredAtomType); + f.Goto(atom.end); +} + +static void SkipAtomIfPresent(Pomme::BigEndianIStream& f, const FourCharCode fourCCToSkip) +{ + auto posGuard = f.GuardPos(); + + auto atomSize = f.Read(); + auto atomType = f.Read(); + + if (atomType == fourCCToSkip) + { + f.Skip(atomSize - 8); + posGuard.Cancel(); + } +} + +//----------------------------------------------------------------------------- +// Atom parsers + +// Sample-to-Chunk +static std::vector Parse_stsc(Pomme::BigEndianIStream& f) +{ + std::vector chunkInfos; + AtomGuard stsc(f, 'stsc'); + Expect(f, 0, "bad stsc version + flags"); + const auto numberOfEntries = f.Read(); + for (UInt32 i = 0; i < numberOfEntries; i++) + { + ChunkInfo ci = {}; + ci.offset = 0xFFFFFFFF; + + const auto firstChunk = f.Read(); + ci.samplesPerChunk = f.Read(); + Expect(f, 1, "sample description ID"); + + // duplicate last chunk + for (size_t j = chunkInfos.size(); j < firstChunk - 1; j++) + { + auto lastChunk = chunkInfos[chunkInfos.size() - 1]; + chunkInfos.push_back(lastChunk); + } + + chunkInfos.push_back(ci); + } + + return chunkInfos; +} + +// Sample Sizes +static std::vector Parse_stsz(Pomme::BigEndianIStream& f) +{ + std::vector sampleSizes; + AtomGuard stsz(f, 'stsz'); + Expect(f, 0, "stsz version + flags"); + auto globalSampleSize = f.Read(); + auto numberOfEntries = f.Read(); + if (globalSampleSize == 0) + { + for (UInt32 i = 0; i < numberOfEntries; i++) + { + sampleSizes.push_back(f.Read()); + } + } + else + { + sampleSizes.push_back(globalSampleSize); + } + return sampleSizes; +} + +// Chunk Offsets +static void Parse_stco(Pomme::BigEndianIStream& f, std::vector& chunkList) +{ + AtomGuard stco(f, 'stco'); + Expect(f, 0, "stco version + flags"); + auto numberOfEntries = f.Read(); + for (UInt32 i = 0; i < numberOfEntries; i++) + { + auto chunkOffset = f.Read(); + chunkList.at(i).offset = chunkOffset; + } +} + +static void Parse_mdia_vide(Pomme::BigEndianIStream& f, Movie& movie, UInt32 timeScale) +{ + std::vector chunkList; + std::vector frameSizes; + + { + AtomGuard minf(f, 'minf'); + RequireAtomAndSkip(f, 'vmhd'); + RequireAtomAndSkip(f, 'hdlr'); + RequireAtomAndSkip(f, 'dinf'); + { + AtomGuard stbl(f, 'stbl'); + { + AtomGuard stsd(f, 'stsd'); + Expect(f, 0, "vide stsd version + flags"); + Expect(f, 1, "vide stsd number of entries"); + f.Skip(4); // UInt32 sampleDescriptionSize + movie.videoFormat = f.Read(); + f.Skip(6); // reserved + f.Skip(2); // data reference index + Expect(f, 1, "vide stsd version"); + Expect(f, 1, "vide stsd revision level"); // docs say it should be 0, but in practice it's 1 + f.Skip(4); // vendor + f.Skip(4); // temporal quality + f.Skip(4); // spatial quality + movie.width = f.Read(); + movie.height = f.Read(); + f.Skip(4); // horizontal resolution (ppi) + f.Skip(4); // vertical resolution (ppi) + Expect(f, 0, "vide stsd data size"); + Expect(f, 1, "vide stsd frame count per sample"); + f.Skip(32); // compressor name + Expect(f, 24, "pixel depth"); + f.Skip(2); // color table ID + } + { + AtomGuard stts(f, 'stts'); + Expect(f, 0, "stts version + flags"); + Expect(f, 1, "stts number of entries"); + f.Skip(4); // UInt32 sampleCount + auto sampleDuration = f.Read(); + movie.videoFrameRate = (float) timeScale / sampleDuration; + } + SkipAtomIfPresent(f, 'stss'); + chunkList = Parse_stsc(f); + frameSizes = Parse_stsz(f); + Parse_stco(f, chunkList); + SkipAtomIfPresent(f, 'stsh'); + } + } + +// std::cout << "vide: " << Pomme::FourCCString(movie.videoFormat) << ", " << movie.width << "x" << movie.height << ", " << movie.videoFrameRate << "fps\n"; + + // ------------------------------------ + // EXTRACT VIDEO FRAMES + + // Set up guard + auto guard = f.GuardPos(); + + size_t frameCounter = 0; + for (const auto& chunk : chunkList) + { + f.Goto(chunk.offset); + for (UInt32 s = 0; s < chunk.samplesPerChunk; s++, frameCounter++) + { + const size_t frameSize = frameSizes[frameCounter]; + movie.videoFrames.push(f.ReadBytes(frameSize)); + } + } +} + +static void Parse_mdia_soun(Pomme::BigEndianIStream& f, Movie& movie) +{ + std::vector chunkList; + + { + AtomGuard minf(f, 'minf'); + SkipAtomIfPresent(f, 'smhd'); + SkipAtomIfPresent(f, 'hdlr'); + SkipAtomIfPresent(f, 'dinf'); + { + AtomGuard stbl(f, 'stbl'); + { + AtomGuard stsd(f, 'stsd'); + Expect(f, 0, "soun stsd version + flags"); + Expect(f, 1, "soun stsd number of entries"); + f.Skip(4); // UInt32 sampleDescriptionSize + movie.audioFormat = f.Read(); + f.Skip(6); // reserved + f.Skip(2); // data reference index + Expect(f, 0, "soun stsd version"); + Expect(f, 0, "soun stsd revision level"); + f.Skip(4); // vendor + movie.audioNChannels = f.Read(); + movie.audioBitDepth = f.Read(); + Expect(f, 0, "soun stsd compression ID"); + Expect(f, 0, "soun stsd packet size"); + Fixed fixedSampleRate = f.Read(); + movie.audioSampleRate = (static_cast(fixedSampleRate) >> 16) & 0xFFFF; + } + { + AtomGuard stts(f, 'stts'); + Expect(f, 0, "stts version + flags"); + Expect(f, 1, "stts number of entries"); + auto sampleCount = f.Read(); + Expect(f, 1, "soun stts: sample duration"); + movie.audioSampleCount = sampleCount; + } + SkipAtomIfPresent(f, 'stss'); + chunkList = Parse_stsc(f); //SkipAtomIfPresent(f, 'stsc'); + auto sampleSize = Parse_stsz(f); //SkipAtomIfPresent(f, 'stsz'); + MoovAssert(1 == sampleSize.size(), "in the sound track, all samples are expected to be of size 1"); + MoovAssert(1 == sampleSize[0], "in the sound track, all samples are expected to be of size 1"); + Parse_stco(f, chunkList); + SkipAtomIfPresent(f, 'stsh'); + } + } + +// std::cout << "soun: " << Pomme::FourCCString(movie.audioFormat) << ", " << movie.audioNChannels << "ch, " << movie.audioBitDepth << "bit, " << movie.audioSampleRate << "Hz\n"; + + // ------------------------------------ + // EXTRACT AUDIO + + bool isRawPCM = movie.audioFormat == 'twos' || movie.audioFormat == 'swot'; + std::unique_ptr codec = nullptr; + if (!isRawPCM) + { + codec = Pomme::Sound::GetCodec(movie.audioFormat); + } + + // Set up position guard for rest of function + auto guard = f.GuardPos(); + + // Unfortunately, Nanosaur's movies use version 0 of the 'stsd' atom for sound tracks. + // This means that the "bytes per packet" count is not encoded into the file. (QTFF-2001, pp. 100-101) + // We have to deduce it from the audio format. + std::vector chunkLengths; + UInt32 compressedLength = 0; + UInt32 totalSamples = 0; + + for (const auto& chunk : chunkList) + { + totalSamples += chunk.samplesPerChunk; + int chunkBytes = isRawPCM + ? movie.audioNChannels * chunk.samplesPerChunk * (movie.audioBitDepth / 8) + : movie.audioNChannels * chunk.samplesPerChunk * codec->BytesPerPacket() / codec->SamplesPerPacket(); + chunkLengths.push_back(chunkBytes); + compressedLength += chunkBytes; + } + + std::vector compressedSoundData; + compressedSoundData.reserve(compressedLength); + char* out = compressedSoundData.data(); + + for (size_t i = 0; i < chunkList.size(); i++) + { + f.Goto(chunkList[i].offset); + f.Read(out, chunkLengths[i]); + out += chunkLengths[i]; + } + + MoovAssert(out == compressedSoundData.data() + compressedLength, "csd length != total length"); + + if (isRawPCM) + { + movie.audioStream.SetBuffer(std::move(compressedSoundData)); + movie.audioStream.Init( + movie.audioSampleRate, + movie.audioBitDepth, + movie.audioNChannels, + movie.audioFormat == 'twos', + movie.audioStream.GetBuffer(compressedLength)); + } + else + { + auto outBytes = 2 * totalSamples * movie.audioNChannels; + auto outSpan = movie.audioStream.GetBuffer(outBytes); + auto inSpan = std::span(compressedSoundData.data(), compressedLength); + codec->Decode(movie.audioNChannels, inSpan, outSpan); + movie.audioStream.Init(movie.audioSampleRate, 16, movie.audioNChannels, false, outSpan); + } +} + +static void Parse_mdia(Pomme::BigEndianIStream& f, Movie& movie) +{ + AtomGuard mdia(f, 'mdia'); + + FourCharCode componentType; + UInt32 timeScale; + + { + AtomGuard mdhd(f, 'mdhd'); + Expect(f, 0, "mdhd version + flags"); + f.Skip(4); // ctime + f.Skip(4); // mtime + timeScale = f.Read(); + f.Skip(4); // UInt32 duration + f.Skip(2); // language + f.Skip(2); // quality + //std::cout << "mdhd: timeScale: " << timeScale << " units per second; duration: " << duration << " units\n"; + } + + { + AtomGuard hdlr(f, 'hdlr'); + f.Skip(4); + Expect(f, 'mhlr', "mhlr required here"); + componentType = f.Read(); + f.Goto(hdlr.end); + } + + if ('vide' == componentType) + { + Parse_mdia_vide(f, movie, timeScale); + } + else if ('soun' == componentType) + { + Parse_mdia_soun(f, movie); + } + else + { + throw MoovException("hdlr component type should be either vide or soun"); + } +} + +static void Parse_trak(Pomme::BigEndianIStream& f, Movie& movie) +{ + AtomGuard trak(f, 'trak'); + + RequireAtomAndSkip(f, 'tkhd'); + + while (f.Tell() < trak.end) + { + auto start = f.Tell(); + auto atomSize = f.Read(); + auto atomType = f.Read(); + f.Goto(start); + if (atomType != 'mdia') + { + f.Skip(atomSize); + } + else + { + Parse_mdia(f, movie); + } + } +} + +//----------------------------------------------------------------------------- +// Moov parser + +Movie Pomme::Video::ReadMoov(std::istream& theF) +{ + Pomme::BigEndianIStream f(theF); + Movie movie; + AtomGuard moov(f, 'moov'); + RequireAtomAndSkip(f, 'mvhd'); + Parse_trak(f, movie); // Parse first track(video) + Parse_trak(f, movie); // Parse second track (audio) + SkipAtomIfPresent(f, 'udta'); + return movie; +}