#include <Windows.h>
#include <shellapi.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include "ewfapi.h"

typedef enum {
    MxError_Unknown = -1,
    MxError_None = 0,

    MxError_Ewf = 501,
    MxError_Version = 502,
    MxError_NoKeyFile = 503,
    MxError_DiskAccessPrivilege = 504,
    MxError_SystemMount = 505,

    MxError_MxMaster = 507,

    MxError_SBR = 511,
    MxError_SysKeyDelete = 512,
} MxError_t;
typedef enum {
    Init = 0,
    CheckEwf = 1,
    EwfDisabled = 2,
    GetSBR = 3,
    NormalBoot = 4,
    SystemUpdate2 = 5,
    SystemUpdate3 = 6,
    Done = 7,
} StartupStep_t;

#define VERSION_BUFFER 256

#define VERSION_FILE "C:\\System\\SystemVersion.txt"
#define DEFAULT_VERSION_FILE "C:\\System\\DefaultSystemVersion.txt"

#define SYSTEM_KEY_FILE "Z:\\SystemKeyFile"
#define OS_UPDATE_KEY_FILE "Z:\\UpdateKeyFile"
#define SYSTEM_VOLUME "C:\\System\\Execute\\System"
#define SYSTEM_MOUNT "S:"

#define NEXT_HOP_CWD SYSTEM_MOUNT "\\"
#define NEXT_HOP_MXMASTER NEXT_HOP_CWD "mxmaster.exe"

#define CACLS_REMPERM_EXECUTE "cacls c:\\system\\execute /t /e /p AppUser:N"
#define CACLS_REMPERM_Z "cacls z:\\ /t /e /p appuser:N"
#define CACLS_REMPERM_S "cacls s:\\ /t /e /p appuser:N"

// Rather than implement these sums in code, we're just going to use the
// presummed versions, because we have nothing to gain by obfuscating this.
// These two sum to TC_PASSWORD
#define TC_PASSWORD_1 "3d550a335d2557055d2b3d1444153348"
#define TC_PASSWORD_2 "36105d2e0b3c1b5f1336365f335a3f1c"
#define TC_PASSWORD "segahardpassword"
// These two sum to SYSTEM_KEYFILE_ADS
#define SYSTEM_KEYFILE_1 "041443255b204a3f054d1b4c352a22292f211b29440e1237591f2e49375d093967614d"
#define SYSTEM_KEYFILE_2 "3f26192e1e532a26680f2a2c3039534b363b2923082c41421a5537241408700d020b18"
#define SYSTEM_KEYFILE_ADS "C:\\System\\Execute\\DLL:SystemKeyFile"
// These two sum to OS_UPDATE_ADS
#define OS_UPDATE_KEYFILE_1 "4019582c56283006300b40042110143d603b3d3b1d012b5c0d5423152235163f624a43"
#define OS_UPDATE_KEYFILE_2 "03210427234b445f3d51057444536137052107112f392a14570d515029306307072222"
#define OS_UPDATE_KEYFILE_ADS "C:\\System\\Execute\\DLL:UpdateKeyFile"

int LOG_EN_PLATFORM = 0;

#define MX_LOG(...) log(__FUNCTION__, __LINE__, __VA_ARGS__)

void log(char* function, unsigned int line_number, char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    printf("%s: Line%d  ", function, line_number);
    vprintf(fmt, args);
    va_end(args);
}

// TODO: These two
bool InitGraphics() { return true; }
bool PopulateEventInfo() { return true; }

// TODO: These three
void AppendAsterisk(void){};
void setStrForStep2(void){};
void setStrForStep3(void){};

int osuExecProcess(char* command) {
    PROCESS_INFORMATION processInformation = { 0 };
    STARTUPINFOA startupInfo = {
        .cb = 68,
        .dwFlags = STARTF_USESHOWWINDOW,
        .wShowWindow = SW_HIDE,
    };

    if (command == NULL) return -150;

    BOOL spanwed = CreateProcessA(NULL, command, NULL, NULL, 0, 0, NULL, NULL, &startupInfo, &processInformation);
    DWORD wait = WaitForSingleObject(processInformation.hProcess, INFINITE);

    int ret;
    if (!spanwed) {
        ret = -140;
    } else if (wait == WAIT_OBJECT_0) {
        DWORD exitCode;
        GetExitCodeProcess(processInformation.hProcess, &exitCode);
        ret = exitCode == 0 ? 0 : -141;
    } else {
        ret = -141;
    }

    CloseHandle(processInformation.hProcess);
    CloseHandle(processInformation.hThread);

    return ret;
}

/* Read a version from a text file */
bool GetVersionText(char* version, char* path) {
    char buffer[VERSION_BUFFER];
    FILE* file;

    memset(buffer, 0, sizeof buffer);
    if (fopen_s(&file, path, "r") || file == NULL) {
        MX_LOG("Error : GetVersionText error path = %s\n", path);
        return false;
    }

    fgets(buffer, sizeof buffer, file);
    fclose(file);
    strncpy_s(version, VERSION_BUFFER, buffer, VERSION_BUFFER);
    return true;
}
/* Get the currently running version of the system */
bool GetCurrentVersion(char* version) { return GetVersionText(version, VERSION_FILE); }
/* Get the default version of the system */
bool GetDefaultVersion(char* version) { return GetVersionText(version, DEFAULT_VERSION_FILE); }

/* Mount the system volume */
bool MountSystemVolume(void) {
    char command[256];
    snprintf(command, sizeof command, "TrueCrypt /p %s /k %s /v %s /l %s /w /s /q", TC_PASSWORD, SYSTEM_KEY_FILE,
             SYSTEM_VOLUME, SYSTEM_MOUNT);

    int ret = osuExecProcess(command);
    if (ret == 0) return true;
    MX_LOG("Error : osuExecProcess error. ret = %d\n", ret);
    return false;
};
/* Try to mount the system volume with up to 100 retries */
bool TryMountSystemVolume(void) {
    for (int i = 0; i < 100; i++) {
        if (MountSystemVolume()) return true;
        Sleep(200);
    }
    return false;
}

/* Copies the file at src to dst */
bool GetKeyFile(LPCSTR src, LPCSTR dst) {
    HANDLE file_1 = CreateFileA(src, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (file_1 == INVALID_HANDLE_VALUE) {
        MX_LOG("CreaterFile Src error\n");
        return false;
    }

    HANDLE file_2 = CreateFileA(dst, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file_2 == INVALID_HANDLE_VALUE) {
        CloseHandle(file_1);
        MX_LOG("Error : CreateFile Dst error\n");
        return false;
    }

    CHAR readBuffer[260];
    DWORD bytesRead;

    if (!ReadFile(file_1, readBuffer, 255, &bytesRead, NULL)) {
        CloseHandle(file_1);
        CloseHandle(file_2);
        MX_LOG("Error : ReadFile error\n");
        return false;
    }
    readBuffer[bytesRead] = '\0';

    DWORD written;
    bool ret = WriteFile(file_2, readBuffer, strlen(readBuffer), &written, NULL) != 0;
    if (!ret) MX_LOG("Error : WriteFile error\n");
    CloseHandle(file_1);
    CloseHandle(file_2);
    return ret;
}

bool GetSystemKeyFile() { return GetKeyFile(SYSTEM_KEYFILE_ADS, SYSTEM_KEY_FILE); };
bool GetOSUpdateKeyFile() { return GetKeyFile(OS_UPDATE_KEYFILE_ADS, OS_UPDATE_KEY_FILE); };
bool SetDiskAccessPrivilege() {
    int i;
    for (i = 0; i < 100; i++) {
        if (osuExecProcess(CACLS_REMPERM_EXECUTE) != -140) break;
        Sleep(200);
    }
    if (i == 100) {
        MX_LOG("Error : osuExecProcess error\n");
        return false;
    }

    for (i = 0; i < 100; i++) {
        if (osuExecProcess(CACLS_REMPERM_Z) != -140) break;
        Sleep(200);
    }
    if (i == 100) {
        MX_LOG("Error : osuExecProcess error\n");
        return false;
    }

    for (i = 0; i < 100; i++) {
        if (osuExecProcess(CACLS_REMPERM_S) != -140) break;
        Sleep(200);
    }
    if (i == 100) {
        MX_LOG("Error : osuExecProcess error\n");
        return false;
    }

    return true;
};
bool ExecuteMxMaster() {
    HINSTANCE ret = ShellExecuteA(NULL, NULL, NEXT_HOP_MXMASTER, NULL, NEXT_HOP_CWD, 0);
    if ((int)ret < 33) {
        MX_LOG("Error : ShellExecute ret = %d\n", ret);
        return false;
    }
    return true;
};
bool MxInitStartup(int* diskNumber) { return false; }
bool CheckEwfState(EWF_STATE* state) {
    // TODO: Consider a better implementation of this!

    *state = EWF_ENABLED;  // Nothing to do, boot the system
    // *state = EWF_DISABLED;  // We're in the middle of an update, so finish

    return true;
}
bool GetSBRSlotOSState(int diskNumber, int* updateState) { return false; };
bool SystemUpdateStep2(int diskNumber) { return false; }
bool SystemUpdateStep3(int diskNumber) { return false; }

MxError_t NormalBootStep(void) {
    AppendAsterisk();
    if (!GetSystemKeyFile()) return MxError_NoKeyFile;
    AppendAsterisk();
    if (!TryMountSystemVolume()) return MxError_SystemMount;
    AppendAsterisk();
    if (!DeleteFileA(SYSTEM_KEY_FILE)) return MxError_SysKeyDelete;
    AppendAsterisk();
    if (!SetDiskAccessPrivilege()) return MxError_DiskAccessPrivilege;
    AppendAsterisk();
    if (!ExecuteMxMaster()) return MxError_MxMaster;
    return MxError_None;
}

int DoStartup() {
    StartupStep_t step = Init;
    MxError_t error = MxError_None;
    int diskNumber = 0;

    while (step != Done) {
        printf("Step: %d (%d)\n", step, error);

        switch (step) {
            case Init:
                if (!MxInitStartup(&diskNumber))
                    step = CheckEwf;
                else
                    step = Done;
                break;
            case CheckEwf: {
                AppendAsterisk();
                EWF_STATE state;
                if (!CheckEwfState(&state)) {
                    error = MxError_Ewf;
                    step = Done;
                    break;
                }

                if (state == EWF_ENABLED)
                    step = NormalBoot;
                else
                    step = EwfDisabled;

                break;
            }
            case EwfDisabled: {
                char versionString[VERSION_BUFFER];
                char defaultVersionString[VERSION_BUFFER];

                if (!GetCurrentVersion(versionString) || !GetDefaultVersion(defaultVersionString)) {
                    error = MxError_Version;
                    step = Done;
                    break;
                }

                if (strcmp(versionString, defaultVersionString) == 0)
                    step = GetSBR;
                else
                    step = SystemUpdate3;
                break;
            }
            case GetSBR: {
                int updateState;
                AppendAsterisk();
                if (!GetSBRSlotOSState(diskNumber, &updateState)) {
                    error = MxError_SBR;
                    step = Done;
                    break;
                }

                if (updateState == 2)
                    step = SystemUpdate2;
                else
                    step = SystemUpdate3;
                break;
            }
            case NormalBoot:
                error = NormalBootStep();
                step = Done;
                break;
            case SystemUpdate2:
                setStrForStep2();
                error = SystemUpdateStep2(diskNumber);
                step = Done;
                break;
            case SystemUpdate3:
                setStrForStep3();
                error = SystemUpdateStep3(diskNumber);
                step = Done;
                break;
            default:
                error = MxError_Unknown;
                step = Done;
                break;
        }
    }
    return error;
}

int setEventSource(char** source) {
    // if (WIN_EVENT->ready == 0) {
    //     return -3;
    // }
    // if (source == (char**)0x0) {
    //     return -2;
    // }
    // _memcpy_s(&WIN_EVENT->eventSource, 16, source, 16);
    return 0;
}
void setLogFn(void* log_fn) {
    // LOG_FUN = log_fn;
    return;
}

int __cdecl logToEventlog(LPCSTR msg) {
    //
    return 0;
}

bool ExitGraphics(void) {
    //   int iVar1;
    //   iVar1 = FUN_0040b9e0((int)&hInstance);
    //   return iVar1 != 0;
    return true;
}

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
    PVOID old = NULL;
    Wow64DisableWow64FsRedirection(&old);

    if (!InitGraphics(hInstance, nShowCmd)) {
        MX_LOG("Error : InitGraphics failed.\n");
    }

    if (!PopulateEventInfo()) {
        LOG_EN_PLATFORM = 1;
        char* mxstartup = "mxstartup";
        setEventSource(&mxstartup);
        setLogFn(logToEventlog);
    }

    MxError_t err = DoStartup();
    if (!err) {
        if (!ExitGraphics()) {
            MX_LOG("Error : ExitGraphics failed.\n");
        }
        // FUN_00408810();#

        Sleep(1000000);
        return 0;
    }

    printf("Fatal: %04d\n", err);

    // while (1) {
    //  FUN_00424640();
    //    Sleep(100000);
    //}

    return 0;
}

int main() { return WinMain(NULL, NULL, NULL, 0); }
