mirror of
https://github.com/mikage-emu/mikage-dev.git
synced 2025-01-09 06:50:59 +01:00
224 lines
7.1 KiB
C++
224 lines
7.1 KiB
C++
#pragma once
|
|
|
|
#include <boost/endian/arithmetic.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <iosfwd>
|
|
#include <functional>
|
|
|
|
namespace FileFormat {
|
|
|
|
enum class Endianness {
|
|
Big,
|
|
Little,
|
|
Undefined
|
|
};
|
|
|
|
template<Endianness E>
|
|
struct endianness_tag {
|
|
static constexpr auto endianness = E;
|
|
};
|
|
|
|
using big_endian_tag = endianness_tag<Endianness::Big>;
|
|
using little_endian_tag = endianness_tag<Endianness::Little>;
|
|
|
|
struct expected_size_tag_base { };
|
|
|
|
/// Used to annotate serializeable structures with the expected total size of the serialized data (used as a static assertion)
|
|
template<size_t Size>
|
|
struct expected_size_tag : expected_size_tag_base { static constexpr auto expected_serialized_size = Size; };
|
|
|
|
template<typename T>
|
|
struct StreamIn;
|
|
|
|
template<typename T>
|
|
struct StreamOut;
|
|
|
|
/*template<>
|
|
struct StreamIn<std::istream> {
|
|
static constexpr bool IsStreamInInstance = true;
|
|
|
|
// TODO: Consider changing this to a template for size? Or better yet, the storage type?
|
|
void Read(char* dest, size_t size);
|
|
|
|
private:
|
|
std::istream stream;
|
|
};*/
|
|
|
|
template<typename ForwardIt, typename EndIt = ForwardIt>
|
|
struct StreamInFromContainer {
|
|
StreamInFromContainer(ForwardIt begin, EndIt end) : cursor(begin), end(end) {
|
|
}
|
|
|
|
void Read(char* dest, size_t size) {
|
|
// static_assert(sizeof(typename std::iterator_traits<ForwardIt>::value_type) == sizeof(char), "");
|
|
// TODO: Assert we don't copy past the end!
|
|
for (auto remaining = size; remaining != 0; --remaining) {
|
|
assert(cursor != end);
|
|
*dest++ = *cursor++;
|
|
}
|
|
}
|
|
|
|
operator std::function<void(char*, size_t)>() {
|
|
return [this](char* dest, size_t size) {
|
|
Read(dest, size);
|
|
};
|
|
}
|
|
|
|
static constexpr bool IsStreamInInstance = true;
|
|
|
|
private:
|
|
ForwardIt cursor;
|
|
EndIt end;
|
|
};
|
|
|
|
template<typename ForwardIt, typename EndIt = ForwardIt>
|
|
StreamInFromContainer<ForwardIt, EndIt> MakeStreamInFromContainer(ForwardIt begin, EndIt end) {
|
|
return StreamInFromContainer<ForwardIt, EndIt>{begin, end};
|
|
}
|
|
|
|
template<typename Rng>
|
|
auto MakeStreamInFromContainer(Rng&& rng) -> StreamInFromContainer<decltype(std::begin(rng)), decltype(std::end(rng))> {
|
|
return StreamInFromContainer { std::begin(rng), std::end(rng) };
|
|
}
|
|
|
|
template<typename ForwardIt>
|
|
struct StreamOutFromContainer {
|
|
StreamOutFromContainer(ForwardIt begin) : cursor(begin) {
|
|
}
|
|
|
|
void Write(char* data, size_t size) {
|
|
for (auto remaining = size; remaining != 0; --remaining)
|
|
*cursor++ = *data++;
|
|
}
|
|
|
|
static constexpr bool IsStreamOutInstance = true;
|
|
|
|
private:
|
|
ForwardIt cursor;
|
|
};
|
|
|
|
template<typename ForwardIt>
|
|
StreamOutFromContainer<ForwardIt> MakeStreamOutFromContainer(ForwardIt begin) {
|
|
return StreamOutFromContainer<ForwardIt>{begin};
|
|
}
|
|
|
|
/**
|
|
* Maps given type to a buffer type. The buffer type must be convertible to the type.
|
|
* For instance, the type uint32_t would map to little_uint32_t.
|
|
*/
|
|
template<typename T>
|
|
struct TypeMapper3DS;
|
|
|
|
template<>
|
|
struct TypeMapper3DS<uint64_t> {
|
|
using type = boost::endian::little_uint64_at;
|
|
};
|
|
|
|
template<>
|
|
struct TypeMapper3DS<uint32_t> {
|
|
using type = boost::endian::little_uint32_at;
|
|
};
|
|
|
|
template<>
|
|
struct TypeMapper3DS<uint16_t> {
|
|
using type = boost::endian::little_uint16_at;
|
|
};
|
|
|
|
template<>
|
|
struct TypeMapper3DS<uint8_t> {
|
|
using type = boost::endian::little_uint8_at;
|
|
};
|
|
|
|
template<typename T, size_t size>
|
|
struct TypeMapper3DS<std::array<T, size>> {
|
|
using type = std::array<typename TypeMapper3DS<T>::type, size>;
|
|
};
|
|
|
|
/**
|
|
* Maps given type to a buffer type. The buffer type must be convertible to the type.
|
|
* For instance, the type uint32_t would map to little_uint32_t.
|
|
*/
|
|
template<typename T>
|
|
struct TypeMapperBigEndian;
|
|
|
|
template<>
|
|
struct TypeMapperBigEndian<uint64_t> {
|
|
using type = boost::endian::big_uint64_at;
|
|
};
|
|
|
|
template<>
|
|
struct TypeMapperBigEndian<uint32_t> {
|
|
using type = boost::endian::big_uint32_at;
|
|
};
|
|
|
|
template<>
|
|
struct TypeMapperBigEndian<uint16_t> {
|
|
using type = boost::endian::big_uint16_at;
|
|
};
|
|
|
|
template<>
|
|
struct TypeMapperBigEndian<uint8_t> {
|
|
using type = boost::endian::big_uint8_at;
|
|
};
|
|
|
|
template<typename T, size_t size>
|
|
struct TypeMapperBigEndian<std::array<T, size>> {
|
|
using type = std::array<typename TypeMapperBigEndian<T>::type, size>;
|
|
};
|
|
|
|
// TODO: TypeMapper for enum -> TypeMapper<underlyingtype>
|
|
// TODO: TypeMapper for union -> TypeMapper<uintN_t<sizeof(T)>>, iff the union has a tag "TypeMap_to_storage"
|
|
|
|
/**
|
|
* Helper class to offload compilation-time intensive part of the implementation in a standalone
|
|
* translation unit. The implementation is quite heavy on metaprogramming and hence would make
|
|
* compile time explode if we were to recompile it in each unit. Unfortunately, this means we
|
|
* cannot have a proper generic implementation in terms of the Stream parameter, but the overhead
|
|
* of std::function is probably small compared to the actual file reading operation anyway.
|
|
* NOTE: This is a template struct rather than a template function because that eases explicit instantiation in the main translation unit.
|
|
*/
|
|
template<typename Data>
|
|
struct SerializationInterface {
|
|
static Data Load(std::function<void(char*, size_t)> reader);
|
|
static void Save(const Data& data, std::function<void(char*, size_t)> writer);
|
|
};
|
|
|
|
template<typename Data, typename Stream>
|
|
Data Load(Stream& stream_in) {
|
|
static_assert(Stream::IsStreamInInstance, "Given stream must have an IsStreamInInstance member");
|
|
|
|
return SerializationInterface<Data>::Load([&stream_in](char* dest, size_t size) {stream_in.Read(dest, size); });
|
|
}
|
|
|
|
template<typename Data>
|
|
Data Load(std::istream& istream) {
|
|
// Wrap in a dummy enable_if_t so that this header doesn't need to pull in <iostream> (only users of this function do)
|
|
auto& istream2 = static_cast<std::enable_if_t<(sizeof(Data) > 0), std::istream&>>(istream);
|
|
return SerializationInterface<Data>::Load([&istream2](char* dest, size_t size) {istream2.read(dest, size); });
|
|
}
|
|
|
|
template<typename Data, boost::endian::order Order>
|
|
Data LoadValue(std::function<void(char*, size_t)> reader) {
|
|
boost::endian::endian_buffer<Order, Data, sizeof(Data) * 8> buffer;
|
|
reader(reinterpret_cast<char*>(buffer.data()), sizeof(Data));
|
|
return buffer.value();
|
|
}
|
|
|
|
template<typename Data, typename Stream>
|
|
auto Save(const Data& data, Stream& stream_out) -> std::enable_if_t<Stream::IsStreamOutInstance> {
|
|
static_assert(Stream::IsStreamOutInstance, "Given stream must have an IsStreamOutInstance member");
|
|
|
|
SerializationInterface<Data>::Save(data, [&stream_out](char* data, size_t size) {stream_out.Write(data, size); });
|
|
}
|
|
|
|
// Wrap in a dummy enable_if_t so that this header doesn't need to pull in <iostream> (only users of this function do)
|
|
template<typename Data>
|
|
void Save(const Data& data, std::ostream& ostream) {
|
|
// Wrap in a dummy enable_if_t so that this header doesn't need to pull in <iostream> (only users of this function do)
|
|
auto& ostream2 = static_cast<std::enable_if_t<(sizeof(Data) > 0), std::ostream&>>(ostream);
|
|
SerializationInterface<Data>::Save(data, [&ostream2](char* data, size_t size) {ostream2.write(data, size); });
|
|
}
|
|
|
|
} // namespace FileFormat
|