﻿/*
Copyright © 2006 - 2026, Benilda Key

Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following
conditions are met:

1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/

// GetStartTime.cpp : Defines the entry point for the console application.
//

#include "StdAfx.h"
#include <boost/predef.h>

#if defined(_MSC_VER)
#  pragma warning(push)
#  pragma warning(disable : 4996 6255 6388 26439 26444 26451 26495 26498 28251 26812)
#endif
#include <string>
#include <memory>
#include <iostream>
#include <iomanip>
#include <array>

#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <boost/numeric/conversion/cast.hpp>
#if defined(_MSC_VER)
#  pragma warning(pop)
#endif

#include "mingw_wil_compat.h"

using namespace std::string_literals; 

namespace {

constexpr size_t BufferSize = SIZE_T_C(512);

constexpr int tm_year_offset = INT32_C(1900);
constexpr int tm_mon_offset = INT32_C(1);
constexpr int tm_isdst_unknown = INT32_C(-1);

constexpr LONGLONG OneHundredNanosecondIntervalsPerSecond = INT64_C(10000000);
constexpr LONGLONG FileTimeAtUnixTimeStart = INT64_C(116444736000000000);
constexpr LONGLONG BitsToShiftForHighDateTime = INT64_C(32);

constexpr time_t invalid_time_t = INT64_C(-1);

enum class GSBTError: unsigned int
{
    none = 0U,
    invalidParams,
    openEventLog,
    getNumRecords,
    allocBuffer,
    readEventLog,
    eventLogFileCorrupt,
    systemTimeToUnixTimeFailed
};

void UnixTimeToFileTime(const time_t t, LPFILETIME pft)
{
    // Note that LONGLONG is a 64-bit value
    const LONGLONG ll{(t * OneHundredNanosecondIntervalsPerSecond) + FileTimeAtUnixTimeStart};
    pft->dwLowDateTime = static_cast<DWORD>(ll);
    // NOLINTNEXTLINE(hicpp-signed-bitwise)
    pft->dwHighDateTime = static_cast<DWORD>(ll >> BitsToShiftForHighDateTime);
}

BOOL UnixTimeToSystemTime(const time_t t, LPSYSTEMTIME pst)
{
    ZeroMemory(pst, sizeof(SYSTEMTIME));
    FILETIME ft{};
    UnixTimeToFileTime(t, &ft);
    BOOL ret_val{FALSE};
    FILETIME lft{};
    ret_val = FileTimeToLocalFileTime(&ft, &lft);
    if (ret_val != FALSE)
    {
        ret_val = FileTimeToSystemTime(&lft, pst);
    }
    return ret_val;
}

bool IsValidSystemTime(const SYSTEMTIME* const sys_time)
{
	constexpr WORD SYSTEMTIME_wMonth_minimum = UINT8_C(1);
	constexpr WORD SYSTEMTIME_wMonth_maximum = UINT8_C(12);
	constexpr WORD SYSTEMTIME_wDay_minimum = UINT8_C(1);
	constexpr WORD SYSTEMTIME_wDay_maximum = UINT8_C(31);
	constexpr WORD SYSTEMTIME_wHour_maximum = UINT8_C(23);
	constexpr WORD SYSTEMTIME_wMinute_maximum = UINT8_C(59);
	constexpr WORD SYSTEMTIME_wSecond_maximum = UINT8_C(59);
    if (sys_time == nullptr)
    {
        return false;
    }
	return (sys_time->wYear >= tm_year_offset
        && (sys_time->wMonth >= SYSTEMTIME_wMonth_minimum && sys_time->wMonth <= SYSTEMTIME_wMonth_maximum)
        && (sys_time->wDay >= SYSTEMTIME_wDay_minimum && sys_time->wDay <= SYSTEMTIME_wDay_maximum)
        && sys_time->wHour <= SYSTEMTIME_wHour_maximum && sys_time->wMinute <= SYSTEMTIME_wMinute_maximum
        && sys_time->wSecond <= SYSTEMTIME_wSecond_maximum);
}

time_t SystemTimeToUnixTime(const SYSTEMTIME* const sys_time)
{
    if (!IsValidSystemTime(sys_time))
    {
        return invalid_time_t;
    }
    tm atm{};
    atm.tm_sec = sys_time->wSecond;
    atm.tm_min = sys_time->wMinute;
    atm.tm_hour = sys_time->wHour;
    atm.tm_mday = sys_time->wDay;
    atm.tm_mon = sys_time->wMonth - tm_mon_offset;
    atm.tm_year = sys_time->wYear - tm_year_offset;
    atm.tm_isdst = tm_isdst_unknown;
    const auto ret_val = mktime(&atm);
    return ret_val;
}

bool IsEventLogServiceStartedOrWindowsStartedEvent(PEVENTLOGRECORD eventLogRecord)
{
    constexpr size_t SourceNameSize = SIZE_T_C(1024);
    constexpr WORD EventLogServiceStartedEventId = UINT8_C(6005);
    constexpr WORD WindowsStartedEventId = UINT8_C(6009);
    if (eventLogRecord == nullptr)
    {
        return false;
    }
    if (eventLogRecord->EventType != EVENTLOG_INFORMATION_TYPE)
    {
        return false;
    }
	std::array<WCHAR, SourceNameSize> sourceName{};
	wcscpy_s(sourceName.data(), sourceName.size(),
		reinterpret_cast<LPCTSTR>(reinterpret_cast<LPBYTE>(eventLogRecord) + sizeof(EVENTLOGRECORD)));
    if (_wcsicmp(sourceName.data(), L"eventlog") != 0)
    {
        return false;
    }
	// NOLINTNEXTLINE(hicpp-signed-bitwise)
	WORD eventCode{LOWORD(eventLogRecord->EventID)};
    return (eventCode == EventLogServiceStartedEventId || eventCode == WindowsStartedEventId);
}

time_t GetStartTimeFromEventLogRecords(PEVENTLOGRECORD eventLogRecords, DWORD bytesRead)
{
    if (eventLogRecords == nullptr)
    {
        return invalid_time_t;
    }
    time_t startTime{0};
    while (bytesRead > 0)
    {
        if (IsEventLogServiceStartedOrWindowsStartedEvent(eventLogRecords))
		{
			startTime = eventLogRecords->TimeGenerated;
			break;
        }
        bytesRead -= eventLogRecords->Length;
        eventLogRecords = reinterpret_cast<PEVENTLOGRECORD>(
            reinterpret_cast<LPBYTE>(eventLogRecords)+ eventLogRecords->Length);
    }
    return startTime;
}

GSBTError GetSysBootTime(LPSYSTEMTIME lpstStartTime)
{
    constexpr DWORD EventLogBufferMaximumSize = UINT32_C(0x7ffff);
    constexpr size_t pad = SIZE_T_C(32);
    if (lpstStartTime == nullptr)
    {
        return GSBTError::invalidParams;
    }
    SecureZeroMemory(lpstStartTime, sizeof(SYSTEMTIME));
    auto lastError = GSBTError::none;
    try
    {
        wil_compat::unique_event_log eventLogPtr(OpenEventLogW(nullptr, L"System"));
        if (!eventLogPtr)
        {
            return GSBTError::openEventLog;
        }
        DWORD NumberOfRecords{0};
        const auto getNumRecordsResult = GetNumberOfEventLogRecords(eventLogPtr.get(), &NumberOfRecords);
        if (getNumRecordsResult == FALSE || NumberOfRecords == 0)
        {
            return GSBTError::getNumRecords;
        }
        auto bufferSize = static_cast<DWORD>(sizeof(EVENTLOGRECORD) * NumberOfRecords * pad);
        if (bufferSize > EventLogBufferMaximumSize)
        {
            /* As of Windows Server 2003 and Windows XP SP2, the maximum size of
            the event log record buffer is 0x7ffff bytes. */
            bufferSize = EventLogBufferMaximumSize;
        }
        // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
        auto buffer = std::make_unique<BYTE[]>(bufferSize);
        if (!buffer)
        {
            return GSBTError::allocBuffer;
        }
        SecureZeroMemory(buffer.get(), bufferSize);
        auto* pEventLogRecords = reinterpret_cast<PEVENTLOGRECORD>(buffer.get());
        lastError = GSBTError::readEventLog;
        DWORD bytesRead{0};
        DWORD minNumberOfBytesNeeded{0};
        time_t startTime{0};
        while (startTime == 0
            // NOLINTNEXTLINE(hicpp-signed-bitwise)
			&& ReadEventLogW(eventLogPtr.get(), EVENTLOG_BACKWARDS_READ|EVENTLOG_SEQUENTIAL_READ,
                0, pEventLogRecords, bufferSize, &bytesRead, &minNumberOfBytesNeeded) != FALSE)
        {
            startTime = GetStartTimeFromEventLogRecords(pEventLogRecords, bytesRead);
            if (startTime != invalid_time_t)
			{
				lastError = GSBTError::none;
				break;
			}
            SecureZeroMemory(buffer.get(), bufferSize);
            pEventLogRecords = reinterpret_cast<PEVENTLOGRECORD>(buffer.get());
        }
        const auto winError = GetLastError();
        if (winError == ERROR_EVENTLOG_FILE_CORRUPT)
        {
            lastError = GSBTError::eventLogFileCorrupt;
        }
        if (startTime != 0)
        {
            UnixTimeToSystemTime(startTime, lpstStartTime);
        }
    }
    catch(...)
    {
    }
    return lastError;
}

bool IsUptime()
{
	using boost::algorithm::ifind_first;
    std::wstring module_name;
    const auto result = wil_compat::GetModuleFileNameW(nullptr, module_name);
    if (FAILED(result))
    {
        return false;
    }
    const auto found = ifind_first(module_name, L"uptime"s);
    return static_cast<bool>(found);
}

} // anonymous namespace

// NOLINTNEXTLINE(clang-diagnostic-missing-prototypes)
int __cdecl wmain()
{
    using boost::wformat;
    using boost::io::group;
	constexpr time_t seconds_per_hour = INT64_C(3600);
	constexpr time_t minutes_per_hour = INT64_C(60);
	constexpr time_t seconds_per_minute = INT64_C(60);
	constexpr time_t hours_per_day = INT64_C(24);
    const auto isUptime = IsUptime();
    SYSTEMTIME stStartTime{};
    const auto errVal = GetSysBootTime(&stStartTime);
    if (errVal == GSBTError::none)
    {
        if (isUptime)
        {
            SYSTEMTIME local_time{};
            GetLocalTime(&local_time);
            const auto u_local_time = SystemTimeToUnixTime(&local_time);
            if (u_local_time == invalid_time_t)
            {
                std::wcout << L"Unable to convert the current system time to a Unix time!\r\n";
                return static_cast<int>(GSBTError::systemTimeToUnixTimeFailed);
            }
            const auto u_start_time = SystemTimeToUnixTime(&stStartTime);
            if (u_start_time == invalid_time_t)
            {
                std::wcout << L"Unable to convert the start time to a Unix time!\r\n";
                return static_cast<int>(GSBTError::systemTimeToUnixTimeFailed);
            }
            const auto elapsed_time = u_local_time - u_start_time;
            const auto total_hours = (elapsed_time / seconds_per_hour);
            const auto total_minutes = (elapsed_time / minutes_per_hour);
            const auto total_seconds = (elapsed_time);
            const auto days = (elapsed_time / (hours_per_day * seconds_per_hour));
            const auto hours = (total_hours - (days * hours_per_day));
            const auto minutes = (total_minutes - (total_hours * minutes_per_hour));
            const auto seconds = (total_seconds - (total_minutes * seconds_per_minute));
            const auto formatString = L"The system has been up for: %1% day(s), %2% hour(s), %3% minute(s), %4% second(s).\r\n"s;
            wformat formatObject(formatString);
            std::wcout << str(formatObject % days % hours % minutes % seconds);
        }
        else
        {
            std::array<wchar_t, BufferSize> szDate{};
            // NOLINTNEXTLINE(hicpp-signed-bitwise)
            GetDateFormatW(LOCALE_USER_DEFAULT, DATE_LONGDATE, &stStartTime, nullptr, szDate.data(),
				boost::numeric_cast<int>(szDate.size() - 1));
            std::array<wchar_t, BufferSize> szTime{};
            // NOLINTNEXTLINE(hicpp-signed-bitwise)
            GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &stStartTime, nullptr, szTime.data(),
				boost::numeric_cast<int>(szTime.size() - 1));
            const auto formatString = L"The system was last started on %1% at %2%.\r\n"s;
            wformat formatObject(formatString);
            std::wcout << str(formatObject % szDate.data() % szTime.data());
        }
    }
    else
    {
        std::wcout << L"Unable to get the time the system was last started!\r\n";
    }
    return static_cast<int>(errVal);
}
