2025-07-23 10:00:29 +02:00
|
|
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
2025-05-31 02:33:02 -04:00
|
|
|
#include "tzif.h"
|
|
|
|
#include <cstdint>
|
|
|
|
#include <cstring>
|
|
|
|
#include <memory>
|
2025-07-23 10:00:29 +02:00
|
|
|
#include <cstdint>
|
2025-05-31 02:33:02 -04:00
|
|
|
|
|
|
|
namespace Tzif {
|
|
|
|
|
2025-07-23 10:00:29 +02:00
|
|
|
static std::size_t SkipToVersion2(const std::uint8_t *data, std::size_t size) {
|
2025-05-31 02:33:02 -04:00
|
|
|
char magic[5];
|
2025-07-23 10:00:29 +02:00
|
|
|
const std::uint8_t *p{data};
|
2025-05-31 02:33:02 -04:00
|
|
|
|
|
|
|
std::memcpy(magic, data, 4);
|
|
|
|
magic[4] = '\0';
|
|
|
|
|
|
|
|
if (std::strcmp(magic, "TZif") != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
p++;
|
|
|
|
if (p >= data + size) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} while (std::strncmp(reinterpret_cast<const char *>(p), "TZif", 4) != 0);
|
|
|
|
|
|
|
|
return p - data;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename Type> constexpr static void SwapEndianess(Type *value) {
|
2025-07-23 10:00:29 +02:00
|
|
|
std::uint8_t *data = reinterpret_cast<std::uint8_t *>(value);
|
2025-05-31 02:33:02 -04:00
|
|
|
|
|
|
|
union {
|
2025-07-23 10:00:29 +02:00
|
|
|
std::uint8_t data[sizeof(Type)];
|
2025-05-31 02:33:02 -04:00
|
|
|
Type value;
|
|
|
|
} temp;
|
|
|
|
|
2025-07-23 10:00:29 +02:00
|
|
|
for (std::uint32_t i = 0; i < sizeof(Type); i++) {
|
|
|
|
std::uint32_t alt_index = sizeof(Type) - i - 1;
|
2025-05-31 02:33:02 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2025-07-23 10:00:29 +02:00
|
|
|
std::unique_ptr<DataImpl> ReadData(const std::uint8_t *data, std::size_t size) {
|
2025-05-31 02:33:02 -04:00
|
|
|
const std::size_t v2_offset = SkipToVersion2(data, size);
|
|
|
|
if (v2_offset == static_cast<std::size_t>(-1)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2025-07-23 10:00:29 +02:00
|
|
|
const std::uint8_t *p = data + v2_offset;
|
2025-05-31 02:33:02 -04:00
|
|
|
|
|
|
|
Header header;
|
|
|
|
std::memcpy(&header, p, sizeof(header));
|
|
|
|
p += sizeof(header);
|
|
|
|
|
|
|
|
FlipHeader(header);
|
|
|
|
|
|
|
|
const std::size_t data_block_length =
|
2025-07-23 10:00:29 +02:00
|
|
|
header.timecnt * sizeof(int64_t) + header.timecnt * sizeof(std::uint8_t) +
|
2025-05-31 02:33:02 -04:00
|
|
|
header.typecnt * sizeof(TimeTypeRecord) +
|
2025-07-23 10:00:29 +02:00
|
|
|
header.charcnt * sizeof(int8_t) + header.isstdcnt * sizeof(std::uint8_t) +
|
|
|
|
header.isutcnt * sizeof(std::uint8_t);
|
2025-05-31 02:33:02 -04:00
|
|
|
|
|
|
|
if (v2_offset + data_block_length + sizeof(Header) > size) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<DataImpl> impl = std::make_unique<DataImpl>();
|
|
|
|
impl->header = header;
|
|
|
|
|
|
|
|
const auto copy =
|
|
|
|
[]<typename Type>(std::unique_ptr<Type[]> &array, int length,
|
2025-07-23 10:00:29 +02:00
|
|
|
const std::uint8_t *const &ptr) -> const std::uint8_t * {
|
2025-05-31 02:33:02 -04:00
|
|
|
const std::size_t region_length = length * sizeof(Type);
|
|
|
|
array = std::make_unique<Type[]>(length);
|
|
|
|
std::memcpy(array.get(), ptr, region_length);
|
|
|
|
return ptr + region_length;
|
|
|
|
};
|
|
|
|
|
|
|
|
p = copy(impl->transition_times, header.timecnt, p);
|
|
|
|
p = copy(impl->transition_types, header.timecnt, p);
|
|
|
|
p = copy(impl->local_time_type_records, header.typecnt, p);
|
|
|
|
p = copy(impl->time_zone_designations, header.charcnt, p);
|
|
|
|
p = copy(impl->standard_indicators, header.isstdcnt, p);
|
|
|
|
p = copy(impl->ut_indicators, header.isutcnt, p);
|
|
|
|
|
|
|
|
const std::size_t footer_string_length = data + size - p - 2;
|
|
|
|
p++;
|
|
|
|
|
|
|
|
if (p + footer_string_length > data + size ||
|
|
|
|
p + footer_string_length < data) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl->footer.tz_string = std::make_unique<char[]>(footer_string_length);
|
|
|
|
std::memcpy(impl->footer.tz_string.get(), p, footer_string_length);
|
|
|
|
impl->footer.footer_string_length = footer_string_length;
|
|
|
|
|
|
|
|
return impl;
|
|
|
|
}
|
|
|
|
|
2025-07-23 10:00:29 +02:00
|
|
|
static void PushToBuffer(std::vector<std::uint8_t> &buffer, const void *data,
|
2025-05-31 02:33:02 -04:00
|
|
|
std::size_t size) {
|
2025-07-23 10:00:29 +02:00
|
|
|
const std::uint8_t *p{reinterpret_cast<const std::uint8_t *>(data)};
|
2025-05-31 02:33:02 -04:00
|
|
|
for (std::size_t i = 0; i < size; i++) {
|
|
|
|
buffer.push_back(*p);
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-23 10:00:29 +02:00
|
|
|
void DataImpl::ReformatNintendo(std::vector<std::uint8_t> &buffer) const {
|
2025-05-31 02:33:02 -04:00
|
|
|
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(),
|
2025-07-23 10:00:29 +02:00
|
|
|
header.timecnt * sizeof(std::uint8_t));
|
2025-05-31 02:33:02 -04:00
|
|
|
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
|