diff --git a/CMakeLists.txt b/CMakeLists.txt index 12bdcf5..d990e11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,4 +2,6 @@ cmake_minimum_required(VERSION 3.10) project(tzdb2nx VERSION 1.0) +set(CMAKE_CXX_STANDARD 20) + add_subdirectory(src) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0e41555..426fd84 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,11 @@ add_executable(tzdb2nx - main.cpp) + main.cpp + tzif.cpp + tzif.h) + +add_compile_options( + -Werror=all + -Werror=extra +) include_directories(.) diff --git a/src/main.cpp b/src/main.cpp index 3eda31a..fb6c9cc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,48 @@ +#include "tzif.h" +#include #include +#include +#include +#include +#include -int main() { return 0; } +constexpr std::size_t ten_megabytes{(1 << 20) * 10}; + +int main(int argc, char *argv[]) { + int f{STDIN_FILENO}; + char *filename{}; + std::size_t filesize{ten_megabytes}; + + if (argc > 1) { + char *filename = argv[1]; + f = open(filename, O_RDONLY); + + struct stat statbuf; + stat(filename, &statbuf); + + filesize = statbuf.st_size; + } + + u_int8_t *buf = new u_int8_t[filesize]; + + filesize = read(f, buf, filesize); + close(f); + + const std::unique_ptr tzif_data = + Tzif::ReadData(buf, filesize); + + std::vector output_buffer; + tzif_data->ReformatNintendo(output_buffer); + + f = STDOUT_FILENO; + if (argc > 2) { + char *filename = argv[2]; + f = open(filename, O_WRONLY | O_CREAT | O_TRUNC); + } + + write(f, output_buffer.data(), output_buffer.size()); + + close(f); + + return 0; +} diff --git a/src/tzif.cpp b/src/tzif.cpp new file mode 100644 index 0000000..e4c1429 --- /dev/null +++ b/src/tzif.cpp @@ -0,0 +1,137 @@ +#include "tzif.h" +#include +#include +#include +#include + +namespace Tzif { + +static std::size_t SkipToVersion2(const u_int8_t *data) { + char magic[5]; + const u_int8_t *p{data}; + + std::memcpy(magic, data, 4); + magic[4] = '\0'; + + if (std::strcmp(magic, "TZif") != 0) { + return 0; + } + + do { + p++; + } while (std::strncmp(reinterpret_cast(p), "TZif", 4) != 0); + + return p - data; +} + +template constexpr static void SwapEndianess(Type *value) { + u_int8_t *data = reinterpret_cast(value); + + union { + u_int8_t data[sizeof(Type)]; + Type value; + } temp; + + for (int i = 0; i < sizeof(Type); i++) { + int alt_index = sizeof(Type) - i - 1; + temp.data[alt_index] = data[i]; + } + + *value = temp.value; +} + +static void FlipHeader(Header &header) { + SwapEndianess(&header.isutcnt); + SwapEndianess(&header.isstdcnt); + SwapEndianess(&header.leapcnt); + SwapEndianess(&header.timecnt); + SwapEndianess(&header.typecnt); + SwapEndianess(&header.charcnt); +} + +std::unique_ptr ReadData(const u_int8_t *data, std::size_t size) { + const u_int8_t *p = data + SkipToVersion2(data); + + Header header; + std::memcpy(&header, p, sizeof(header)); + p += sizeof(header); + + FlipHeader(header); + + std::unique_ptr impl = std::make_unique(); + impl->header = header; + + impl->transition_times = std::make_unique(header.timecnt); + impl->transition_types = std::make_unique(header.timecnt); + impl->local_time_type_records = + std::make_unique(header.typecnt); + impl->time_zone_designations = std::make_unique(header.charcnt); + impl->standard_indicators = std::make_unique(header.isstdcnt); + impl->ut_indicators = std::make_unique(header.isutcnt); + + std::memcpy(impl->transition_times.get(), p, + header.timecnt * sizeof(int64_t)); + p += header.timecnt * sizeof(int64_t); + + std::memcpy(impl->transition_types.get(), p, + header.timecnt * sizeof(u_int8_t)); + p += header.timecnt * sizeof(u_int8_t); + + std::memcpy(impl->local_time_type_records.get(), p, + header.typecnt * sizeof(TimeTypeRecord)); + p += header.typecnt * sizeof(TimeTypeRecord); + + std::memcpy(impl->time_zone_designations.get(), p, header.charcnt); + p += header.charcnt * sizeof(int8_t); + + std::memcpy(impl->standard_indicators.get(), p, + header.isstdcnt * sizeof(u_int8_t)); + p += header.isstdcnt * sizeof(u_int8_t); + + std::memcpy(impl->ut_indicators.get(), p, header.isutcnt * sizeof(u_int8_t)); + p += header.isutcnt * sizeof(u_int8_t); + + const std::size_t footer_string_length = data + size - p - 2; + p++; + + impl->footer.tz_string = std::make_unique(footer_string_length); + std::memcpy(impl->footer.tz_string.get(), p, footer_string_length); + impl->footer.footer_string_length = footer_string_length; + + return impl; +} + +static void PushToBuffer(std::vector &buffer, const void *data, + std::size_t size) { + const u_int8_t *p{reinterpret_cast(data)}; + for (std::size_t i = 0; i < size; i++) { + buffer.push_back(*p); + p++; + } +} + +void DataImpl::ReformatNintendo(std::vector &buffer) const { + buffer.clear(); + + Header header_copy{header}; + header_copy.isstdcnt = 0; + header_copy.isutcnt = 0; + FlipHeader(header_copy); + + PushToBuffer(buffer, &header_copy, sizeof(Header)); + PushToBuffer(buffer, transition_times.get(), + header.timecnt * sizeof(int64_t)); + PushToBuffer(buffer, transition_types.get(), + header.timecnt * sizeof(u_int8_t)); + PushToBuffer(buffer, local_time_type_records.get(), + header.typecnt * sizeof(TimeTypeRecord)); + PushToBuffer(buffer, time_zone_designations.get(), + header.charcnt * sizeof(int8_t)); + // omit standard_indicators + // omit ut_indicators + PushToBuffer(buffer, &footer.nl_a, 1); + PushToBuffer(buffer, footer.tz_string.get(), footer.footer_string_length); + PushToBuffer(buffer, &footer.nl_b, 1); +} + +} // namespace Tzif diff --git a/src/tzif.h b/src/tzif.h new file mode 100644 index 0000000..db56c86 --- /dev/null +++ b/src/tzif.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include + +namespace Tzif { + +typedef struct { + char magic[4]; + u_int8_t version; + u_int8_t reserved[15]; + u_int32_t isutcnt; + u_int32_t isstdcnt; + u_int32_t leapcnt; + u_int32_t timecnt; + u_int32_t typecnt; + u_int32_t charcnt; +} Header; +static_assert(sizeof(Header) == 0x2c); + +class Footer { +public: + explicit Footer() = default; + ~Footer() = default; + + const char nl_a{'\n'}; + std::unique_ptr tz_string; + const char nl_b{'\n'}; + + std::size_t footer_string_length; +}; + +#pragma pack(push, 1) +typedef struct { + u_int32_t utoff; + u_int8_t dst; + u_int8_t idx; +} TimeTypeRecord; +#pragma pack(pop) +static_assert(sizeof(TimeTypeRecord) == 0x6); + +class Data { +public: + explicit Data() = default; + ~Data() = default; + + virtual void ReformatNintendo(std::vector &buffer) const = 0; +}; + +class DataImpl : public Data { +public: + explicit DataImpl() = default; + ~DataImpl() = default; + + void ReformatNintendo(std::vector &buffer) const override; + + Header header; + Footer footer; + + std::unique_ptr transition_times; + std::unique_ptr transition_types; + std::unique_ptr local_time_type_records; + std::unique_ptr time_zone_designations; + std::unique_ptr standard_indicators; + std::unique_ptr ut_indicators; +}; + +std::unique_ptr ReadData(const u_int8_t *data, std::size_t size); + +} // namespace Tzif