#include "core/stdafx.h"
#include "sdklauncher.h"

//-----------------------------------------------------------------------------
// Print the error message to the console if any.
//-----------------------------------------------------------------------------
void PrintLastError()
{
    DWORD errorMessageID = ::GetLastError();
    if (errorMessageID != NULL)
    {
        LPSTR messageBuffer = nullptr;
        size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);

        spdlog::error("{}\n", messageBuffer);
        LocalFree(messageBuffer);
    }
}

//-----------------------------------------------------------------------------
// Purpose case switch:
// * Launch the game in user specified mode and state.
// * Load specified command line arguments from a file on the disk.
// * Format the file paths for the game exe and specified hook dll.
//-----------------------------------------------------------------------------
bool LaunchR5Apex(LAUNCHMODE lMode, LAUNCHSTATE lState)
{
    ///////////////////////////////////////////////////////////////////////////
    // Initialize strings.
    std::string WorkerDll            = std::string();
    std::string GameDirectory        = std::string();
    std::string CommandLineArguments = std::string();
    std::string StartupCommandLine   = std::string();
    std::string currentDirectory     = std::filesystem::current_path().u8string();

    ///////////////////////////////////////////////////////////////////////////
    // Determine launch mode.
    switch (lMode)
    {
        case LAUNCHMODE::LM_DEBUG:
        {
            std::filesystem::path cfgPath = std::filesystem::current_path() /= "platform\\cfg\\startup_debug.cfg"; // Get cfg path for debug startup.
            std::ifstream cfgFile(cfgPath); // Read the cfg file.
            if (cfgFile.good() && cfgFile)  // Does the cfg file exist?
            {
                std::stringstream ss;
                ss << cfgFile.rdbuf();           // Read ifstream buffer into stringstream.
                CommandLineArguments = ss.str(); // Get all the contents of the cfg file.
            }
            else
            {
                spdlog::error("File 'platform\\cfg\\startup_debug.cfg' does not exist.\n");
                cfgFile.close();
                return false;
            }
            cfgFile.close(); // Close cfg file.

            WorkerDll           = currentDirectory + "\\bin\\r5apexvtxd.dll";                // Get path to worker dll.
            GameDirectory       = currentDirectory + "\\r5apex.exe";                         // Get path to game executeable.
            StartupCommandLine  = currentDirectory + "\\r5apex.exe " + CommandLineArguments; // Setup startup command line string.

            spdlog::info("*** LAUNCHING GAME [DEBUG] ***\n");
            break;
        }
        case LAUNCHMODE::LM_RELEASE:
        {
            std::filesystem::path cfgPath = std::filesystem::current_path() /= "platform\\cfg\\startup_retail.cfg"; // Get cfg path for release startup.
            std::ifstream cfgFile(cfgPath); // Read the cfg file.
            if (cfgFile.good() && cfgFile)  // Does the cfg file exist?
            {
                std::stringstream ss;
                ss << cfgFile.rdbuf();           // Read ifstream buffer into stringstream.
                CommandLineArguments = ss.str(); // Get all the contents of the cfg file.
            }
            else
            {
                spdlog::error("File 'platform\\cfg\\startup_retail.cfg' does not exist.\n");
                cfgFile.close();
                return false;
            }
            cfgFile.close(); // Close cfg file.

            WorkerDll          = currentDirectory + "\\r5apexsdkd64.dll";                   // Get path to worker dll.
            GameDirectory      = currentDirectory + "\\r5apex.exe";                         // Get path to game executeable.
            StartupCommandLine = currentDirectory + "\\r5apex.exe " + CommandLineArguments; // Setup startup command line string.

            spdlog::info("*** LAUNCHING GAME [RELEASE] ***\n");
            break;
        }
        case LAUNCHMODE::LM_DEDI:
        {
            std::filesystem::path cfgPath = std::filesystem::current_path() /= "platform\\cfg\\startup_dedi.cfg"; // Get cfg path for dedicated startup.
            std::ifstream cfgFile(cfgPath); // Read the cfg file.
            if (cfgFile.good() && cfgFile)  // Does the cfg file exist?
            {
                std::stringstream ss;
                ss << cfgFile.rdbuf();           // Read ifstream buffer into stringstream.
                CommandLineArguments = ss.str(); // Get all the contents of the cfg file.
            }
            else
            {
                spdlog::error("File 'platform\\cfg\\startup_dedi.cfg' does not exist.\n");
                cfgFile.close();
                return false;
            }
            cfgFile.close(); // Close cfg file.

            WorkerDll          = currentDirectory + "\\dedicated.dll";                      // Get path to worker dll.
            GameDirectory      = currentDirectory + "\\r5apex_ds.exe";                         // Get path to game executeable.
            StartupCommandLine = currentDirectory + "\\r5apex_ds.exe " + CommandLineArguments; // Setup startup command line string.

            spdlog::info("*** LAUNCHING GAME [DEDICATED] ***\n");
            break;
        }
        default:
        {
            spdlog::error("*** NO LAUNCH MODE SPECIFIED ***\n");
            return false;
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // Print the file paths and arguments.
    std::cout << "--------------------------------------------------------------------------------------------------------" << std::endl;
    spdlog::debug("- CWD: {}\n", currentDirectory);
    spdlog::debug("- EXE: {}\n", GameDirectory);
    spdlog::debug("- DLL: {}\n", WorkerDll);
    spdlog::debug("- CLI: {}\n", CommandLineArguments);
    std::cout << "--------------------------------------------------------------------------------------------------------" << std::endl;

    ///////////////////////////////////////////////////////////////////////////
    // Build our list of dlls to inject.
    LPCSTR DllsToInject[1] =
    {
        WorkerDll.c_str()
    };

    STARTUPINFO StartupInfo = { 0 };
    PROCESS_INFORMATION ProcInfo = { 0 };

    // Initialize startup info struct.
    StartupInfo.cb = sizeof(STARTUPINFO);

    ///////////////////////////////////////////////////////////////////////////
    // Create the game process in a suspended state with our dll.
    BOOL result = DetourCreateProcessWithDllsA
    (
        GameDirectory.c_str(),                         // lpApplicationName
        (LPSTR)StartupCommandLine.c_str(),             // lpCommandLine
        NULL,                                          // lpProcessAttributes
        NULL,                                          // lpThreadAttributes
        FALSE,                                         // bInheritHandles
        CREATE_SUSPENDED,                              // dwCreationFlags
        NULL,                                          // lpEnvironment
        currentDirectory.c_str(),                      // lpCurrentDirectory
        &StartupInfo,                                  // lpStartupInfo
        &ProcInfo,                                     // lpProcessInformation
        sizeof(DllsToInject) / sizeof(LPCSTR),         // nDlls
        DllsToInject,                                  // rlpDlls
        NULL                                           // pfCreateProcessA
    );

    ///////////////////////////////////////////////////////////////////////////
    // Failed to create the process.
    if (!result)
    {
        PrintLastError();
        return false;
    }

    ///////////////////////////////////////////////////////////////////////////
    // Resume the process.
    ResumeThread(ProcInfo.hThread);

    ///////////////////////////////////////////////////////////////////////////
    // Close the process and thread handles.
    CloseHandle(ProcInfo.hProcess);
    CloseHandle(ProcInfo.hThread);

    return true;
}

///////////////////////////////////////////////////////////////////////////////
// Entrypoint.
///////////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[], char* envp[])
{
    spdlog::set_pattern("[%^%l%$] %v");
    spdlog::set_level(spdlog::level::trace);

    for (int i = 1; i < argc; ++i)
    {
        std::string arg = argv[i];
        if ((arg == "-debug") || (arg == "-dbg"))
        {
            LaunchR5Apex(LAUNCHMODE::LM_DEBUG, LAUNCHSTATE::LS_CHEATS);
            Sleep(2000);
            return EXIT_SUCCESS;
        }

        if ((arg == "-release") || (arg == "-rel"))
        {
            LaunchR5Apex(LAUNCHMODE::LM_RELEASE, LAUNCHSTATE::LS_CHEATS);
            Sleep(2000);
            return EXIT_SUCCESS;
        }

        if ((arg == "-dedicated") || (arg == "-dedi"))
        {
            LaunchR5Apex(LAUNCHMODE::LM_DEDI, LAUNCHSTATE::LS_CHEATS);
            Sleep(2000);
            return EXIT_SUCCESS;
        }
    }

    std::cout << "--------------------------------------------------------------------------------------------------------" << std::endl;
    spdlog::warn("If DEBUG has been choosen as launch parameter, do not broadcast servers to the Server Browser.\n");
    spdlog::warn("All FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ConVar's/ConCommand's will be enabled.\n");
    spdlog::warn("Connected clients will be able to set and execute anything flagged FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY.\n");
    std::cout << "--------------------------------------------------------------------------------------------------------" << std::endl;
    spdlog::warn("Use DEBUG     [1] for research and development purposes.\n");
    spdlog::warn("Use RELEASE   [2] for playing and server hosting purposes.\n");
    spdlog::warn("Use DEDICATED [3] for running and hosting a dedicated server.\n");
    std::cout << "--------------------------------------------------------------------------------------------------------" << std::endl;
    spdlog::info("Enter 1 for DEBUG. Enter 2 for RELEASE. Enter 3 for DEDICATED: ");

    std::string input = std::string();
    if (std::cin >> input)
    {
        try
        {
            LAUNCHMODE iinput = (LAUNCHMODE)std::stoi(input);
            switch (iinput)
            {
            case LAUNCHMODE::LM_DEBUG:
            {
                LaunchR5Apex(LAUNCHMODE::LM_DEBUG, LAUNCHSTATE::LS_CHEATS);
                Sleep(2000);
                return EXIT_SUCCESS;
            }
            case LAUNCHMODE::LM_RELEASE:
            {
                LaunchR5Apex(LAUNCHMODE::LM_RELEASE, LAUNCHSTATE::LS_CHEATS);
                Sleep(2000);
                return EXIT_SUCCESS;
            }
            case LAUNCHMODE::LM_DEDI:
            {
                LaunchR5Apex(LAUNCHMODE::LM_DEDI, LAUNCHSTATE::LS_CHEATS);
                Sleep(2000);
                return EXIT_SUCCESS;
            }
            default:
            {
                spdlog::error("R5Reloaded requires '1' for DEBUG mode, '2' for RELEASE mode, '3' for DEDICATED mode.\n");
                Sleep(5000);
                return EXIT_FAILURE;
            }
            }
        }
        catch (std::exception& e)
        {
            spdlog::error("R5Reloaded only takes numerical input to launch. Error: {}.\n", e.what());
            Sleep(5000);
            return EXIT_FAILURE;
        }
    }
    spdlog::error("R5Reloaded requires numerical input to launch.\n");

    Sleep(5000);
    return EXIT_FAILURE;
}