# Host unit tests for OnionHEN shared libs + util cheats (no PS5 SDK).
# Usage (from this directory):
#   make
#   make test
#   ONION_TEST_VERBOSE=1 make test

UTIL_ROOT := $(abspath ..)
REPO_ROOT := $(abspath $(UTIL_ROOT)/../..)
THIRD_PARTY_ROOT := $(REPO_ROOT)/third_party
BUILD_DIR := $(UTIL_ROOT)/build/host-tests
BIN := $(BUILD_DIR)/onion-host-tests

HOST_CC ?= cc
HOST_CXX ?= c++
PYTHON ?= python3
KEYSTONE_PREFIX ?= /opt/homebrew
KEYSTONE_CFLAGS := -I$(THIRD_PARTY_ROOT)/keystone/include
KEYSTONE_LDFLAGS := -L$(KEYSTONE_PREFIX)/lib -lkeystone

DEPFLAGS := -MMD -MP
COMMON_FLAGS := -Wall -Wextra -g -O0 $(DEPFLAGS) -DONION_HOST_TEST=1 -DCBC=1 \
	-DONION_TEST_DIR=\"$(CURDIR)\" \
	-I$(UTIL_ROOT)/include \
	-I$(UTIL_ROOT)/tests \
	-I$(REPO_ROOT)/source/include \
	-I$(REPO_ROOT)/source/libonion_settings/include \
	-I$(REPO_ROOT)/source/libonion_platform/include \
	-I$(REPO_ROOT)/source/libonion_payload/include \
	-I$(REPO_ROOT)/source/libonion_proc/include \
	-I$(REPO_ROOT)/source/libonion_ipc/include \
	-I$(REPO_ROOT)/source/libonion_detour/include \
	-I$(REPO_ROOT)/source/libonion_ready \
	-I$(REPO_ROOT)/source/daemon/include \
	-I$(THIRD_PARTY_ROOT)/cjson \
	-I$(THIRD_PARTY_ROOT)/cheat_support \
	-I$(REPO_ROOT)/source/shellui/include \
	-I$(BUILD_DIR)/generated \
	$(KEYSTONE_CFLAGS)

I18N_DIR := $(REPO_ROOT)/source/i18n
I18N_JSON := $(wildcard $(I18N_DIR)/*.json)
I18N_CATALOG := $(BUILD_DIR)/generated/toolbox_i18n_catalog.inc
NOTIFY_I18N_CATALOG := $(BUILD_DIR)/generated/notify_i18n_catalog.inc

# ready.h lives under source/include/onion/
COMMON_FLAGS += -I$(REPO_ROOT)/source/include

CFLAGS := $(COMMON_FLAGS) -std=c11
CXXFLAGS := $(COMMON_FLAGS) -std=c++20
LDFLAGS := $(KEYSTONE_LDFLAGS) -pthread

# Production sources under test
PROD_C_SRCS := \
	$(UTIL_ROOT)/source/cheats/cheat_engine_utils.c \
	$(UTIL_ROOT)/source/cheats/cheat_engine_parser_shnext.c \
	$(UTIL_ROOT)/source/cheats/cheat_flatten.c \
	$(REPO_ROOT)/source/libonion_platform/source/fs.c \
	$(REPO_ROOT)/source/libonion_platform/source/log.c \
	$(REPO_ROOT)/source/libonion_platform/source/net.c \
	$(REPO_ROOT)/source/libonion_platform/source/notify.c \
	$(REPO_ROOT)/source/libonion_platform/source/notify_i18n.c \
	$(REPO_ROOT)/source/libonion_ready/source/ready.c \
	$(REPO_ROOT)/source/libonion_payload/source/payload.c

PROD_CXX_SRCS := \
	$(UTIL_ROOT)/source/cheats/json_cheat_parser.cpp \
	$(UTIL_ROOT)/source/cheats/xml_cheat_parser.cpp \
	$(UTIL_ROOT)/source/cheats/mc4_cheat_parser.cpp \
	$(UTIL_ROOT)/source/cheats/shn_ext_cheat_parser.cpp \
	$(UTIL_ROOT)/source/cheats/cheat_parser_factory.cpp \
	$(UTIL_ROOT)/source/http_github.cpp \
	$(THIRD_PARTY_ROOT)/cjson/cJSON.cpp \
	$(REPO_ROOT)/source/libonion_settings/source/settings.cpp \
	$(REPO_ROOT)/source/libonion_ipc/source/ipc_server.cpp \
	$(REPO_ROOT)/source/shellui/src/ps5_settings_ui.cpp \
	$(REPO_ROOT)/source/shellui/src/toolbox_route.cpp \
	$(REPO_ROOT)/source/shellui/src/toolbox_i18n.cpp

TEST_C_SRCS := \
	test_harness.c \
	test_support.c \
	test_cheat_utils.c \
	test_cheat_flatten.c \
	test_payload.c \
	test_base64.c \
	test_aes_cbc.c \
	test_hde64.c \
	test_reg_entity.c \
	test_account_id_b64.c \
	test_ready.c \
	test_platform_fs.c \
	test_platform_log.c \
	test_platform_net.c \
	test_platform_notify.c \
	test_toolbox_timing.c \
	test_hijack_retry.c \
	stubs/host_stubs.c

TEST_CXX_SRCS := \
	test_cheat_parsers.cpp \
	test_settings.cpp \
	test_msg_protocol.cpp \
	test_app_jailbreak_policy.cpp \
	test_debug_settings_route_policy.cpp \
	test_ipc_harden.cpp \
	test_ps5_settings_ui.cpp \
	test_toolbox_route.cpp \
	test_onpress_policy.cpp \
	test_toolbox_helpers.cpp \
	test_toolbox_i18n.cpp \
	test_toolbox_injection.cpp \
	test_overlay_text_metrics.cpp \
	test_trampoline_arena.cpp \
	test_hook_lifecycle.cpp \
	test_http_github.cpp \
	test_hotpatch.cpp \
	test_x64_relocator.cpp \
	test_main.cpp

PROD_C_OBJS := $(patsubst $(UTIL_ROOT)/%.c,$(BUILD_DIR)/prod/%.o,$(filter $(UTIL_ROOT)/%,$(PROD_C_SRCS)))
PROD_C_OBJS += $(BUILD_DIR)/prod/libonion_platform/fs.o
PROD_C_OBJS += $(BUILD_DIR)/prod/libonion_platform/log.o
PROD_C_OBJS += $(BUILD_DIR)/prod/libonion_platform/net.o
PROD_C_OBJS += $(BUILD_DIR)/prod/libonion_platform/notify.o
PROD_C_OBJS += $(BUILD_DIR)/prod/libonion_platform/notify_i18n.o
PROD_C_OBJS += $(BUILD_DIR)/prod/libonion_ready/ready.o
PROD_C_OBJS += $(BUILD_DIR)/prod/libonion_payload/payload.o
PROD_C_OBJS += $(BUILD_DIR)/prod/libonion_detour/hde64.o

CHEAT_SUPPORT_SRCS := $(wildcard $(THIRD_PARTY_ROOT)/cheat_support/*.c)
CHEAT_SUPPORT_OBJS := $(patsubst $(THIRD_PARTY_ROOT)/cheat_support/%.c,$(BUILD_DIR)/prod/third_party/cheat_support/%.o,$(CHEAT_SUPPORT_SRCS))

PROD_CXX_OBJS := $(patsubst $(UTIL_ROOT)/%.cpp,$(BUILD_DIR)/prod/%.o,$(filter $(UTIL_ROOT)/%,$(PROD_CXX_SRCS)))
PROD_CXX_OBJS += $(BUILD_DIR)/prod/third_party/cjson.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/libonion_settings/settings.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/libonion_ipc/ipc_server.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/libonion_detour/hotpatch.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/libonion_detour/trampoline_arena.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/libonion_detour/x64_relocator.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/shellui/hook_lifecycle.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/shellui/ps5_settings_ui.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/shellui/toolbox_route.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/shellui/toolbox_i18n.o

TEST_C_OBJS := $(patsubst %.c,$(BUILD_DIR)/tests/%.o,$(TEST_C_SRCS))
TEST_CXX_OBJS := $(patsubst %.cpp,$(BUILD_DIR)/tests/%.o,$(TEST_CXX_SRCS))

ALL_OBJS := $(PROD_C_OBJS) $(CHEAT_SUPPORT_OBJS) $(PROD_CXX_OBJS) $(TEST_C_OBJS) $(TEST_CXX_OBJS)

# Soften third_party noise
THIRD_PARTY_FLAGS := -Wno-error -Wno-unused-function -Wno-sign-compare \
	-Wno-unused-parameter -Wno-missing-field-initializers

.PHONY: all test clean

all: $(BIN)

test: $(BIN)
	cd $(CURDIR) && ONION_TEST_ROOT=$(CURDIR) $(BIN)

$(BIN): $(ALL_OBJS) | $(BUILD_DIR)
	$(HOST_CXX) -o $@ $^ $(LDFLAGS)

$(BUILD_DIR)/prod/third_party/cheat_support/%.o: $(THIRD_PARTY_ROOT)/cheat_support/%.c | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CC) $(CFLAGS) $(THIRD_PARTY_FLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/source/cheats/cheat_engine_parser_shnext.o: $(UTIL_ROOT)/source/cheats/cheat_engine_parser_shnext.c | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CC) $(CFLAGS) -Wno-unused-function -c -o $@ $<

$(BUILD_DIR)/prod/%.o: $(UTIL_ROOT)/%.c | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CC) $(CFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/%.o: $(UTIL_ROOT)/%.cpp | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/third_party/cjson.o: $(THIRD_PARTY_ROOT)/cjson/cJSON.cpp | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) $(THIRD_PARTY_FLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_settings/settings.o: $(REPO_ROOT)/source/libonion_settings/source/settings.cpp | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_ipc/ipc_server.o: $(REPO_ROOT)/source/libonion_ipc/source/ipc_server.cpp | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_detour/x64_relocator.o: $(REPO_ROOT)/source/libonion_detour/source/x64_relocator.cpp | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_detour/hotpatch.o: $(REPO_ROOT)/source/libonion_detour/source/hotpatch.cpp | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_detour/trampoline_arena.o: $(REPO_ROOT)/source/libonion_detour/source/trampoline_arena.cpp | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/shellui/hook_lifecycle.o: $(REPO_ROOT)/source/shellui/src/hook_lifecycle.cpp | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/shellui/ps5_settings_ui.o: $(REPO_ROOT)/source/shellui/src/ps5_settings_ui.cpp | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/shellui/toolbox_route.o: $(REPO_ROOT)/source/shellui/src/toolbox_route.cpp | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<

$(I18N_CATALOG): $(I18N_DIR)/generate_catalog.py $(I18N_JSON) | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(PYTHON) $(I18N_DIR)/generate_catalog.py \
		--locales-dir $(I18N_DIR) \
		--catalog toolbox \
		--output $@

$(NOTIFY_I18N_CATALOG): $(I18N_DIR)/generate_catalog.py $(I18N_JSON) | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(PYTHON) $(I18N_DIR)/generate_catalog.py \
		--locales-dir $(I18N_DIR) \
		--catalog notifications \
		--output $@

$(BUILD_DIR)/prod/shellui/toolbox_i18n.o: $(REPO_ROOT)/source/shellui/src/toolbox_i18n.cpp $(I18N_CATALOG) | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_ready/ready.o: $(REPO_ROOT)/source/libonion_ready/source/ready.c | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CC) $(CFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_platform/fs.o: $(REPO_ROOT)/source/libonion_platform/source/fs.c | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CC) $(CFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_platform/log.o: $(REPO_ROOT)/source/libonion_platform/source/log.c | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CC) $(CFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_platform/net.o: $(REPO_ROOT)/source/libonion_platform/source/net.c | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CC) $(CFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_platform/notify.o: $(REPO_ROOT)/source/libonion_platform/source/notify.c | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CC) $(CFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_platform/notify_i18n.o: $(REPO_ROOT)/source/libonion_platform/source/notify_i18n.c $(NOTIFY_I18N_CATALOG) | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CC) $(CFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_payload/payload.o: $(REPO_ROOT)/source/libonion_payload/source/payload.c | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CC) $(CFLAGS) -c -o $@ $<

$(BUILD_DIR)/prod/libonion_detour/hde64.o: $(REPO_ROOT)/source/libonion_detour/source/hde64.cpp | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<

$(BUILD_DIR)/tests/%.o: %.c | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CC) $(CFLAGS) -c -o $@ $<

$(BUILD_DIR)/tests/%.o: %.cpp | $(BUILD_DIR)
	@mkdir -p $(dir $@)
	$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<

$(BUILD_DIR):
	mkdir -p $(BUILD_DIR)

clean:
	rm -rf $(BUILD_DIR)

# Header dependencies generated by -MMD; keeps rebuilds correct.
-include $(shell find $(BUILD_DIR) -name '*.d' 2>/dev/null)
