﻿cmake_minimum_required(VERSION 3.22)

project(LatiNova)

set(LATITE_BUILD_TIMESTAMP "" CACHE STRING
        "Optional fixed Latite build timestamp in YYYY-MM-DD_HH-mm-ssZ UTC format")
set(LATINOVA_NOVA_SHOP_API_BASE "http://127.0.0.1:8794" CACHE STRING
        "Nova Shop API base URL; use the deployed HTTPS Workers URL for distribution builds")
if (LATITE_BUILD_TIMESTAMP AND NOT LATITE_BUILD_TIMESTAMP MATCHES
        "^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_[0-9][0-9]-[0-9][0-9]-[0-9][0-9]Z$")
    message(FATAL_ERROR
            "LATITE_BUILD_TIMESTAMP must use YYYY-MM-DD_HH-mm-ssZ UTC+0 format; got '${LATITE_BUILD_TIMESTAMP}'")
endif ()

set(CMAKE_CONFIGURATION_TYPES "Debug;Release;RelWithDebInfo;MinSizeRel;Nightly" CACHE STRING "" FORCE)

# Nightly keeps RelWithDebInfo-style compilation so we get usable symbols, but
# links like a shipped binary so the DLL doesn't become a My 600-Lb. Life contestant
set(CMAKE_C_FLAGS_NIGHTLY "${CMAKE_C_FLAGS_RELWITHDEBINFO}" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS_NIGHTLY "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}" CACHE STRING "" FORCE)
set(CMAKE_EXE_LINKER_FLAGS_NIGHTLY "${CMAKE_EXE_LINKER_FLAGS_RELEASE}" CACHE STRING "" FORCE)
set(CMAKE_SHARED_LINKER_FLAGS_NIGHTLY "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}" CACHE STRING "" FORCE)

if (NOT MSVC)
    message(FATAL_ERROR "Compiler unsupported, must use MSVC")
endif()

# Enable Hot Reload / debug info format for supported MSVC generators
if (POLICY CMP0141)
    cmake_policy(SET CMP0141 NEW)
    set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug>:EditAndContinue>$<$<CONFIG:RelWithDebInfo,Nightly>:ProgramDatabase>")
endif ()

# MSVC runtime
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded")

# Add all sources in src/* and deps/*
file(GLOB_RECURSE Latite_SRC CONFIGURE_DEPENDS
        "src/*.h"
        "src/*.cpp"
        "deps/*.h"
        "deps/*.cpp"
)

# Assets
function(ADD_RESOURCES out_var)
    set(result)
    foreach(in_f ${ARGN})
        file(RELATIVE_PATH src_f ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${in_f})
        set(out_f "${PROJECT_BINARY_DIR}/${in_f}.o")

        add_custom_command(
                OUTPUT ${out_f}
                COMMAND ld.exe -r -b binary -o ${out_f} ${src_f}
                DEPENDS ${in_f}
                WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
                COMMENT "Building object ${out_f}"
                VERBATIM
        )

        list(APPEND result ${out_f})
    endforeach()

    set(${out_var} "${result}" PARENT_SCOPE)
endfunction()

# Add all files in assets/ as resources
file(GLOB_RECURSE RESOURCE_FILES
        RELATIVE ${CMAKE_CURRENT_LIST_DIR}
        CONFIGURE_DEPENDS
        "assets/*"
)

# 3D Skin is native-only as of dev.6. Keep the historical generated pack in
# source for license/audit purposes, but do not embed or deploy it.
list(FILTER RESOURCE_FILES EXCLUDE REGEX "^assets/3d_skin/")
list(FILTER RESOURCE_FILES EXCLUDE REGEX "^assets/nova_shop/concepts/")
list(FILTER RESOURCE_FILES EXCLUDE REGEX "^assets/nova_shop/README\\.md$")
list(FILTER RESOURCE_FILES EXCLUDE REGEX "^assets/nova_shop/minecraft/.*_atlas\\.png$")

add_resources(EMBEDDED_RESOURCES ${RESOURCE_FILES})

set(LATITE_BUILD_TIMESTAMP_SOURCE
        "${PROJECT_BINARY_DIR}/generated/LatiteBuildTimestamp.cpp")
set_source_files_properties("${LATITE_BUILD_TIMESTAMP_SOURCE}" PROPERTIES GENERATED TRUE)

set(LATITE_EFFECTIVE_BUILD_TIMESTAMP "${LATITE_BUILD_TIMESTAMP}")
if (NOT LATITE_EFFECTIVE_BUILD_TIMESTAMP AND
        NOT CMAKE_BUILD_TYPE MATCHES "^(Debug|Nightly)$")
    # Shipped configurations return the redacted timestamp below, so keep the
    # generated source stable and avoid rebuilding the entire DLL every time.
    set(LATITE_EFFECTIVE_BUILD_TIMESTAMP "1970-01-01_00-00-00Z")
endif ()
execute_process(
        COMMAND ${CMAKE_COMMAND}
        "-DLATITE_BUILD_TIMESTAMP=${LATITE_EFFECTIVE_BUILD_TIMESTAMP}"
        "-DOUTPUT_FILE=${LATITE_BUILD_TIMESTAMP_SOURCE}"
        -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/GenerateBuildTimestamp.cmake"
        RESULT_VARIABLE LATITE_BUILD_TIMESTAMP_RESULT
)
if (NOT LATITE_BUILD_TIMESTAMP_RESULT EQUAL 0)
    message(FATAL_ERROR "Unable to generate Latite build timestamp source")
endif ()

add_library(Latite SHARED
        ${Latite_SRC}
        ${EMBEDDED_RESOURCES}
        "${LATITE_BUILD_TIMESTAMP_SOURCE}"
)

# Final artifact names. Keep these in CMake so the DLL CodeView record and
# linker-generated PDB name agree, **do not rename the DLL/PDB after linking!!**
set_target_properties(Latite PROPERTIES
        OUTPUT_NAME_RELEASE "LatiNova1.26.42-development"
        PDB_NAME_RELEASE "LatiNova1.26.42-development"
        OUTPUT_NAME_RELWITHDEBINFO "LatiNova"
        PDB_NAME_RELWITHDEBINFO "LatiNova"
        OUTPUT_NAME_DEBUG "LatiNovaDebug"
        PDB_NAME_DEBUG "LatiNovaDebug"
        OUTPUT_NAME_NIGHTLY "LatiNovaNightly"
        PDB_NAME_NIGHTLY "LatiNovaNightly"
)

# C++ standard
set_property(TARGET Latite PROPERTY CXX_STANDARD 23)
set_property(TARGET Latite PROPERTY CXX_STANDARD_REQUIRED ON)

# Compile definitions
target_compile_definitions(Latite PRIVATE
        _UNICODE
        UNICODE
        _HAS_STATIC_RTTI=0
        LATINOVA_NOVA_SHOP_API_BASE=L\"${LATINOVA_NOVA_SHOP_API_BASE}\"

        $<$<CONFIG:Debug>:LATITE_DEBUG>
        $<$<CONFIG:Debug>:LATITE_CRASH_REPORTING>
        $<$<CONFIG:Release>:LATITE_RELEASE>
        $<$<CONFIG:Release>:LATITE_CRASH_REPORTING>
        $<$<CONFIG:RelWithDebInfo>:LATITE_RELEASE>
        $<$<CONFIG:RelWithDebInfo>:LATITE_CRASH_REPORTING>
        $<$<CONFIG:MinSizeRel>:LATITE_RELEASE>
        $<$<CONFIG:Nightly>:LATITE_NIGHTLY>
        $<$<CONFIG:Nightly>:LATITE_CRASH_REPORTING>
)

# Compile options
target_compile_options(Latite PRIVATE
        # CMake emits the absolute PCH include path as UTF-8. Tell MSVC to
        # decode it as UTF-8 as well so clean builds also work from Japanese
        # (or other non-ASCII) source directories.
        /utf-8
        /EHsc
        /GR-
        /W4
        /GS
        /sdl
        /guard:cf
        /ZH:SHA_256
        $<$<CONFIG:Release,RelWithDebInfo,Nightly>:/Qspectre>
)

target_link_options(Latite PRIVATE
        /DYNAMICBASE
        /NXCOMPAT
        /guard:cf
        /CETCOMPAT
        /MAP:${CMAKE_CURRENT_BINARY_DIR}/LatiNova.map
        $<$<CONFIG:Release>:/INCREMENTAL:NO>
        $<$<CONFIG:Release>:/OPT:REF>
        $<$<CONFIG:Release>:/OPT:ICF>

        $<$<CONFIG:Nightly>:/DEBUG:FULL>
        $<$<CONFIG:Nightly>:/INCREMENTAL:NO>
        $<$<CONFIG:Nightly>:/OPT:REF>
        $<$<CONFIG:Nightly>:/OPT:NOICF>
)

target_include_directories(Latite PRIVATE
        src
        deps
)

# Link libraries
target_link_libraries(Latite PRIVATE
        # WinRT
        WindowsApp.lib
        Shlwapi.lib

        # Needed for getting app version
        Version.lib

        # Needed for DirectX hooking and Direct2D rendering
        d3d11
        d3d12
        dxguid
        dxgi

        # UI rendering
        d2d1

        # Text rendering
        dwrite

        # ImageNtHeader for mnemosyne
        Dbghelp

        # PlaySound for scripting
        winmm

        # SHA-256 verification for the optional scripting runtime
        bcrypt

        # Per-Windows-user protection for Nova Shop device credentials
        crypt32
)

add_subdirectory(src)
add_subdirectory(deps)

include(CTest)
if (BUILD_TESTING)
    add_executable(LatiNovaSecurityTests
            tests/SafePathTests.cpp
            src/util/SafePath.cpp
    )
    target_include_directories(LatiNovaSecurityTests PRIVATE src)
    target_compile_features(LatiNovaSecurityTests PRIVATE cxx_std_23)
    target_compile_options(LatiNovaSecurityTests PRIVATE /utf-8 /W4 /GS /sdl /guard:cf)
    target_link_options(LatiNovaSecurityTests PRIVATE /DYNAMICBASE /NXCOMPAT /guard:cf /CETCOMPAT)
    add_test(NAME SafePathSecurity COMMAND LatiNovaSecurityTests)

    add_executable(LatiNovaNovaShopAuthTests
            tests/NovaShopAuthTests.cpp
            src/client/misc/NovaShopAuth.cpp
    )
    target_include_directories(LatiNovaNovaShopAuthTests PRIVATE src)
    target_compile_features(LatiNovaNovaShopAuthTests PRIVATE cxx_std_23)
    target_compile_options(LatiNovaNovaShopAuthTests PRIVATE /utf-8 /W4 /GS /sdl /guard:cf)
    target_link_options(LatiNovaNovaShopAuthTests PRIVATE /DYNAMICBASE /NXCOMPAT /guard:cf /CETCOMPAT)
    target_link_libraries(LatiNovaNovaShopAuthTests PRIVATE bcrypt crypt32 nlohmann_json)
    add_test(NAME NovaShopDeviceAuth COMMAND LatiNovaNovaShopAuthTests)
endif()
