///
/// Created by Anonymous275 on 7/18/2020
///
#include "Security/Enc.h"
#include <filesystem>
#include <Windows.h>
#include "Logger.h"
#include <fstream>
#include <sstream>
#include <string>
#include <thread>

#define MAX_KEY_LENGTH 255
#define MAX_VALUE_NAME 16383

int TraceBack = 0;
std::string GameDir;

void lowExit(int code){
    TraceBack = 0;
    std::string msg =
    Sec("Failed to find the game please launch it. Report this if the issue persists code ");
    error(msg+std::to_string(code));
    std::this_thread::sleep_for(std::chrono::seconds(10));
    exit(2);
}
void Exit(int code){
    TraceBack = 0;
    std::string msg =
    Sec("Sorry. We do not support cracked copies report this if you believe this is a mistake code ");
    error(msg+std::to_string(code));
    std::this_thread::sleep_for(std::chrono::seconds(10));
    exit(3);
}
void SteamExit(int code){
    TraceBack = 0;
    std::string msg =
    Sec("Illegal steam modifications detected report this if you believe this is a mistake code ");
    error(msg+std::to_string(code));
    std::this_thread::sleep_for(std::chrono::seconds(10));
    exit(4);
}
std::string GetGameDir(){
    if(TraceBack != 4)Exit(0);
    return GameDir.substr(0,GameDir.find_last_of('\\'));
}
LONG OpenKey(HKEY root,const char* path,PHKEY hKey){
    return RegOpenKeyEx(root, reinterpret_cast<LPCSTR>(path), 0, KEY_READ, hKey);
}
std::string QueryKey(HKEY hKey,int ID){
    TCHAR    achKey[MAX_KEY_LENGTH];   // buffer for subkey name
    DWORD    cbName;                   // size of name string
    TCHAR    achClass[MAX_PATH] = TEXT("");  // buffer for class name
    DWORD    cchClassName = MAX_PATH;  // size of class string
    DWORD    cSubKeys=0;               // number of subkeys
    DWORD    cbMaxSubKey;              // longest subkey size
    DWORD    cchMaxClass;              // longest class string
    DWORD    cValues;              // number of values for key
    DWORD    cchMaxValue;          // longest value name
    DWORD    cbMaxValueData;       // longest value data
    DWORD    cbSecurityDescriptor; // size of security descriptor
    FILETIME ftLastWriteTime;      // last write time

    DWORD i, retCode;

    TCHAR  achValue[MAX_VALUE_NAME];
    DWORD cchValue = MAX_VALUE_NAME;

    retCode = RegQueryInfoKey(
            hKey,                    // key handle
            achClass,                // buffer for class name
            &cchClassName,           // size of class string
            nullptr,                    // reserved
            &cSubKeys,               // number of subkeys
            &cbMaxSubKey,            // longest subkey size
            &cchMaxClass,            // longest class string
            &cValues,                // number of values for this key
            &cchMaxValue,            // longest value name
            &cbMaxValueData,         // longest value data
            &cbSecurityDescriptor,   // security descriptor
            &ftLastWriteTime);       // last write time

    BYTE* buffer = new BYTE[cbMaxValueData];
    ZeroMemory(buffer, cbMaxValueData);
    if (cSubKeys){
        for (i=0; i<cSubKeys; i++){
            cbName = MAX_KEY_LENGTH;
            retCode = RegEnumKeyEx(hKey, i,achKey,&cbName,nullptr,nullptr,nullptr,&ftLastWriteTime);
            if (retCode == ERROR_SUCCESS){
                if(strcmp(achKey,Sec("Steam App 284160")) == 0){
                    return achKey;
                }
            }
        }
    }
    if (cValues){
        for (i=0, retCode = ERROR_SUCCESS; i<cValues; i++){
            cchValue = MAX_VALUE_NAME;
            achValue[0] = '\0';
            retCode = RegEnumValue(hKey, i,achValue,&cchValue,nullptr,nullptr,nullptr,nullptr);
            if (retCode == ERROR_SUCCESS ){
                DWORD lpData = cbMaxValueData;
                buffer[0] = '\0';
                LONG dwRes = RegQueryValueEx(hKey, achValue, nullptr, nullptr, buffer, &lpData);
                std::string data = reinterpret_cast<const char *const>(buffer);
                std::string key = achValue;
                switch (ID){
                    case 1: if(key == Sec("SteamExe")){
                            auto p = data.find_last_of('/');
                            if(p != std::string::npos)return data.substr(0,p);
                        }break;
                    case 2: if(key == Sec("Name") && data == Sec("BeamNG.drive"))return data;break;
                    case 3: if(key == Sec("rootpath"))return data;break;
                    case 4: if(key == Sec("userpath_override"))return data;
                    default: break;
                }
            }
        }
    }
    delete [] buffer;
    return "";
}
namespace fs = std::experimental::filesystem;
void FileList(std::vector<std::string>&a,const std::string& Path){
    for (const auto &entry : fs::directory_iterator(Path)) {
        auto pos = entry.path().filename().string().find('.');
        if (pos != std::string::npos) {
            a.emplace_back(entry.path().string());
        }else FileList(a,entry.path().string());
    }
}
bool Find(const std::string& FName,const std::string& Path){
    std::vector<std::string> FS;
    FileList(FS,Path+Sec("/userdata"));
    for(std::string&a : FS){
        if(a.find(FName) != std::string::npos){
            FS.clear();
            return true;
        }
    }
    FS.clear();
    return false;
}
bool FindHack(const std::string& Path){
    bool s = true;
    for (const auto &entry : fs::directory_iterator(Path)) {
        std::string Name = entry.path().filename().string();
        for(char&c : Name)c = char(tolower(c));
        if(Name == Sec("steam.exe"))s = false;
        if(Name.find(Sec("greenluma")) != -1)return true;
        Name.clear();
    }
    return s;
}
std::vector<std::string> GetID(const std::string& log){
    std::string vec,t,r;
    std::vector<std::string> Ret;
    std::ifstream f(log.c_str(), std::ios::binary);
    f.seekg(0, std::ios_base::end);
    std::streampos fileSize = f.tellg();
    vec.resize(size_t(fileSize) + 1);
    f.seekg(0, std::ios_base::beg);
    f.read(&vec[0], fileSize);
    f.close();
    std::stringstream ss(vec);
    bool S = false;
    while (std::getline(ss, t, '{')) {
        if(!S)S = true;
        else{
            for(char& c : t){
                if(isdigit(c))r += c;
            }
            break;
        }
    }
    Ret.emplace_back(r);
    r.clear();
    S = false;
    bool L = true;
    while (std::getline(ss, t, '}')) {
        if(L){
            L = false;
            continue;
        }
        for(char& c : t){
            if(c == '"'){
                if(!S)S = true;
                else{
                    if(r.length() > 10) {
                        Ret.emplace_back(r);
                    }
                    r.clear();
                    S = false;
                    continue;
                }
            }
            if(isdigit(c))r += c;
        }
    }
    vec.clear();
    return Ret;
}
std::string GetManifest(const std::string& Man){
    std::string vec;
    std::ifstream f(Man.c_str(), std::ios::binary);
    f.seekg(0, std::ios_base::end);
    std::streampos fileSize = f.tellg();
    vec.resize(size_t(fileSize) + 1);
    f.seekg(0, std::ios_base::beg);
    f.read(&vec[0], fileSize);
    f.close();
    std::string ToFind = Sec("\"LastOwner\"\t\t\"");
    int pos = int(vec.find(ToFind));
    if(pos != -1){
        pos += int(ToFind.length());
        vec = vec.substr(pos);
        return vec.substr(0,vec.find('\"'));
    }else return "";
}
bool IDCheck(std::string Man, std::string steam){
    bool a = false,b = true;
    int pos = int(Man.rfind(Sec("steamapps")));
    if(pos == -1)Exit(5);
    Man = Man.substr(0,pos+9) + Sec("/appmanifest_284160.acf");
    steam += Sec("/config/loginusers.vdf");
    if(fs::exists(Man) && fs::exists(steam)){
        for(const std::string&ID : GetID(steam)){
            if(ID == GetManifest(Man))b = false;
        }
        if(b)Exit(6);
    }else a = true;
    return a;
}
void LegitimacyCheck(){
    std::string Result,T;
    std::string K1 = Sec("Software\\Valve\\Steam");
    std::string K2 = Sec("Software\\Valve\\Steam\\Apps\\284160");
    std::string K3 = Sec("Software\\BeamNG\\BeamNG.drive");
    HKEY hKey;
    LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K1.c_str(), &hKey);
    if(dwRegOPenKey == ERROR_SUCCESS) {
        Result = QueryKey(hKey, 1);
        if(Result.empty())Exit(1);
        if(fs::exists(Result)){
            if(!Find(Sec("284160.json"),Result))Exit(2);
            if(FindHack(Result))SteamExit(1);
        }else Exit(3);
        T = Result;
        Result.clear();
        TraceBack++;
    }else Exit(4);
    K1.clear();
    RegCloseKey(hKey);
    dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K2.c_str(), &hKey);
    if(dwRegOPenKey == ERROR_SUCCESS) {
        Result = QueryKey(hKey, 2);
        if(Result.empty())lowExit(1);
        TraceBack++;
    }else lowExit(2);
    K2.clear();
    RegCloseKey(hKey);
    dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey);
    if(dwRegOPenKey == ERROR_SUCCESS) {
        Result = QueryKey(hKey, 3);
        if(Result.empty())lowExit(3);
        if(IDCheck(Result,T))lowExit(5);
        GameDir = Result;
        TraceBack++;
    }else lowExit(4);
    K3.clear();
    Result.clear();
    RegCloseKey(hKey);
    if(TraceBack < 3)exit(-1);
}
std::string CheckVer(const std::string &dir){
    std::string vec,temp,Path = dir + Sec("\\integrity.json");
    std::ifstream f(Path.c_str(), std::ios::binary);
    f.seekg(0, std::ios_base::end);
    std::streampos fileSize = f.tellg();
    vec.resize(size_t(fileSize) + 1);
    f.seekg(0, std::ios_base::beg);
    f.read(&vec[0], fileSize);
    f.close();
    vec = vec.substr(vec.find_last_of(Sec("version")),vec.find_last_of('"'));
    for(const char &a : vec){
        if(isdigit(a) || a == '.')temp+=a;
    }
    return temp;
}
