cmake_minimum_required(VERSION 3.20)
project(ig5_ghidra_native VERSION 12.1.4 LANGUAGES C CXX)

# IG5's portable build overlay. The upstream source tree is immutable. Source
# membership is derived from the exact upstream Gradle definitions, rather than
# using all .cc files (which would mix console/test/production implementations).
set(GHIDRA_SOURCE_ROOT "" CACHE PATH "Pinned Ghidra 12.1.4 source tree")
if(NOT EXISTS "${GHIDRA_SOURCE_ROOT}/Ghidra/Features/Decompiler/buildNatives.gradle")
  message(FATAL_ERROR "GHIDRA_SOURCE_ROOT must identify the fixed Ghidra source tree")
endif()
set(_module "${GHIDRA_SOURCE_ROOT}/Ghidra/Features/Decompiler")
set(_cpp "${_module}/src/decompile/cpp")
file(READ "${_module}/buildNatives.gradle" _native)
string(REGEX REPLACE "//[^\n]*" "" _native "${_native}")
string(FIND "${_native}" "decompile(NativeExecutableSpec)" _decompile_start)
string(FIND "${_native}" "sleigh(NativeExecutableSpec)" _sleigh_start)
if(_decompile_start LESS 0 OR _sleigh_start LESS 0)
  message(FATAL_ERROR "Upstream native build declaration changed")
endif()
math(EXPR _decompile_length "${_sleigh_start}-${_decompile_start}")
string(SUBSTRING "${_native}" ${_decompile_start} ${_decompile_length} _decompile_block)
string(SUBSTRING "${_native}" ${_sleigh_start} -1 _sleigh_block)

function(ig5_sources declaration output)
  string(REGEX MATCHALL "include \"[^\"]+\\.cc\"" _includes "${declaration}")
  set(_paths "")
  foreach(_include IN LISTS _includes)
    string(REGEX REPLACE "include \"([^\"]+)\"" "\\1" _file "${_include}")
    if(NOT EXISTS "${_cpp}/${_file}")
      message(FATAL_ERROR "Missing upstream native source: ${_file}")
    endif()
    list(APPEND _paths "${_cpp}/${_file}")
  endforeach()
  list(REMOVE_DUPLICATES _paths)
  set(${output} "${_paths}" PARENT_SCOPE)
endfunction()
ig5_sources("${_decompile_block}" _decompile_sources)
ig5_sources("${_sleigh_block}" _sleigh_sources)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# Keep Windows artifacts self-contained: no dependency on a user-installed
# Visual C++ redistributable that happens to be present on the build machine.
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
add_executable(decompile ${_decompile_sources})
file(GLOB _zlib_sources CONFIGURE_DEPENDS "${_module}/src/decompile/zlib/*.c")
add_library(ig5_zlib STATIC ${_zlib_sources})
target_compile_definitions(ig5_zlib PRIVATE NO_GZIP)
target_include_directories(ig5_zlib PUBLIC "${_module}/src/decompile/zlib")
add_executable(sleigh ${_sleigh_sources})
target_compile_definitions(sleigh PRIVATE LOCAL_ZLIB)
target_link_libraries(sleigh PRIVATE ig5_zlib)

# A linkable Sleigh decoder/CPU-emulation foundation for Android/iOS native
# hosts. This is not the complete Java analysis framework or a mobile runtime.
set(_sleigh_library_sources ${_sleigh_sources})
list(FILTER _sleigh_library_sources EXCLUDE REGEX "/(slgh_compile|slghparse|slghscan)\\.cc$")
list(APPEND _sleigh_library_sources "${_cpp}/loadimage.cc" "${_cpp}/memstate.cc" "${_cpp}/emulate.cc" "${_cpp}/opbehavior.cc")
add_library(ig5_sleigh_core STATIC ${_sleigh_library_sources})
target_compile_definitions(ig5_sleigh_core PRIVATE LOCAL_ZLIB)
target_include_directories(ig5_sleigh_core PUBLIC "${_cpp}")
target_link_libraries(ig5_sleigh_core PUBLIC ig5_zlib)
option(IG5_SLEIGH_SHARED "Build a C ABI shared library; disable for app-linked iOS/static hosts" ON)
if(IG5_SLEIGH_SHARED)
  add_library(ig5_sleigh SHARED ig5_sleigh.cpp)
  target_compile_definitions(ig5_sleigh PRIVATE IG5_SLEIGH_BUILD)
else()
  add_library(ig5_sleigh STATIC ig5_sleigh.cpp)
  target_compile_definitions(ig5_sleigh PUBLIC IG5_SLEIGH_STATIC)
endif()
target_link_libraries(ig5_sleigh PRIVATE ig5_sleigh_core)

foreach(_target decompile sleigh ig5_sleigh_core ig5_zlib)
  if(WIN32)
    target_compile_definitions(${_target} PRIVATE WINDOWS _WINDOWS WIN32 _WIN32 WIN64 _WIN64 _CRT_SECURE_NO_DEPRECATE _CRT_NONSTDC_NO_DEPRECATE)
    if(MSVC)
      target_compile_options(${_target} PRIVATE /W3 /utf-8)
      target_compile_options(${_target} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:/EHsc>)
      if(_target STREQUAL "ig5_zlib")
        target_compile_definitions(${_target} PRIVATE ZLIB_WINAPI)
      endif()
    endif()
  elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR ANDROID)
    target_compile_definitions(${_target} PRIVATE LINUX _LINUX)
  endif()
endforeach()
install(TARGETS decompile sleigh ig5_sleigh_core ig5_sleigh ig5_zlib RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
install(FILES ig5_sleigh.h DESTINATION include)
