Browse Source

More CLI work, getting license response off activation server

Josh Engebretson 10 years ago
parent
commit
3aad5a023b

+ 23 - 1
Source/AtomicCLI/CLI.cpp

@@ -1,11 +1,17 @@
 
 #include <Atomic/Core/ProcessUtils.h>
+#include <Atomic/IO/Log.h>
 #include <Atomic/Engine/Engine.h>
 
+#include <ToolCore/ToolSystem.h>
+
+#include <ToolCore/License/LicenseSystem.h>
+
 #include "CLI.h"
 
-DEFINE_APPLICATION_MAIN(AtomicCLI::CLI);
+using namespace ToolCore;
 
+DEFINE_APPLICATION_MAIN(AtomicCLI::CLI);
 
 namespace AtomicCLI
 {
@@ -23,6 +29,18 @@ CLI::~CLI()
 
 void CLI::Setup()
 {
+    const Vector<String>& arguments = GetArguments();
+
+    for (unsigned i = 0; i < arguments.Size(); ++i)
+    {
+        if (arguments[i].Length() > 1 /* && arguments[i][0] == '-'*/)
+        {
+            String argument = arguments[i].Substring(1).ToLower();
+            String value = i + 1 < arguments.Size() ? arguments[i + 1] : String::EMPTY;
+
+            LOGINFOF("%s", argument.CString());
+        }
+    }
 
     engineParameters_["Headless"] = true;
     engineParameters_["ResourcePaths"] = "";
@@ -30,7 +48,11 @@ void CLI::Setup()
 
 void CLI::Start()
 {
+    context_->RegisterSubsystem(new ToolSystem(context_));
 
+    // BEGIN LICENSE MANAGEMENT
+    GetSubsystem<LicenseSystem>()->Initialize();
+    // END LICENSE MANAGEMENT
 }
 
 void CLI::Stop()

+ 1 - 1
Source/AtomicCLI/CMakeLists.txt

@@ -2,6 +2,6 @@ set (ATOMIC_CLI_SOURCES CLI.cpp CLI.h)
 
 add_executable(atomic-cli ${ATOMIC_CLI_SOURCES})
 
-target_link_libraries(atomic-cli ${ATOMIC_LINK_LIBRARIES})
+target_link_libraries(atomic-cli ${ATOMIC_LINK_LIBRARIES} ToolCore Poco curl)
 
 

+ 1 - 1
Source/CMakeLists.txt

@@ -5,7 +5,7 @@ add_subdirectory(AtomicJS)
 add_subdirectory(AtomicPlayer)
 
 if (NOT IOS AND NOT ANDROID AND NOT EMSCRIPTEN)    
-	add_subdirectory(AtomicToolbox)
+	add_subdirectory(ToolCore)
 	add_subdirectory(AtomicCLI)
     add_subdirectory(AtomicEditor)
     add_subdirectory(Tools)

+ 13 - 0
Source/ToolCore/CMakeLists.txt

@@ -0,0 +1,13 @@
+include_directories (${CMAKE_SOURCE_DIR}/Source/ThirdParty/rapidjson/include
+                     ${CMAKE_SOURCE_DIR}/Source/ThirdParty
+                     ${CMAKE_SOURCE_DIR}/Source/ThirdParty/Poco/Foundation/include
+                     ${CMAKE_SOURCE_DIR}/Source/ThirdParty/nativefiledialog)
+
+
+file (GLOB_RECURSE SOURCE_FILES Source/*.cpp Source/*.h)
+
+add_definitions(-DPOCO_NO_AUTOMATIC_LIBS)
+
+file (GLOB_RECURSE SOURCE_FILES *.cpp *.h)
+
+add_library(ToolCore ${SOURCE_FILES})

+ 505 - 0
Source/ToolCore/License/LicenseSystem.cpp

@@ -0,0 +1,505 @@
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+// BEGIN LICENSE MANAGEMENT
+
+#ifdef ATOMIC_PLATFORM_WINDOWS
+#ifndef _MSC_VER
+#define _WIN32_IE 0x501
+#endif
+#include <windows.h>
+#include <shellapi.h>
+#include <direct.h>
+#include <shlobj.h>
+#include <sys/types.h>
+#include <sys/utime.h>
+#endif
+
+#include <Atomic/Core/CoreEvents.h>
+#include <Atomic/Core/Context.h>
+#include <Atomic/IO/FileSystem.h>
+#include <Atomic/IO/File.h>
+#include <Atomic/IO/Log.h>
+
+#include "LicenseSystem.h"
+
+#include <Poco/MD5Engine.h>
+#include <Poco/File.h>
+
+namespace ToolCore
+{
+
+LicenseSystem::LicenseSystem(Context* context) :
+    Object(context)
+    , eulaAgreementConfirmed_(false)
+
+{
+    ResetLicense();
+
+}
+
+LicenseSystem::~LicenseSystem()
+{
+
+}
+
+void LicenseSystem::Initialize()
+{
+
+    FileSystem* filesystem = GetSubsystem<FileSystem>();
+    String eulaConfirmedFilePath = filesystem->GetAppPreferencesDir("AtomicEditor", "License");
+    eulaConfirmedFilePath = AddTrailingSlash(eulaConfirmedFilePath);
+    eulaConfirmedFilePath += "EulaConfirmed";
+
+    eulaAgreementConfirmed_ = filesystem->FileExists(eulaConfirmedFilePath);
+
+    if (!LoadLicense() || !key_.Length() || !eulaAgreementConfirmed_)
+    {
+        ResetLicense();
+        /*
+        UIModalOps* ops = GetSubsystem<UIModalOps>();
+
+        if (eulaAgreementConfirmed_)
+            ops->ShowActivation();
+        else
+            ops->ShowEulaAgreement();
+        */
+    }
+    else
+    {
+        RequestServerVerification(key_);
+    }
+}
+
+void LicenseSystem::LicenseAgreementConfirmed()
+{
+    eulaAgreementConfirmed_ = true;
+
+    FileSystem* filesystem = GetSubsystem<FileSystem>();
+    String eulaConfirmedFilePath = filesystem->GetAppPreferencesDir("AtomicEditor", "License");
+    eulaConfirmedFilePath = AddTrailingSlash(eulaConfirmedFilePath);
+    eulaConfirmedFilePath += "EulaConfirmed";
+
+    SharedPtr<File> file(new File(context_, eulaConfirmedFilePath, FILE_WRITE));
+    file->WriteInt(1);
+    file->Close();
+
+    /*
+    UIModalOps* ops = GetSubsystem<UIModalOps>();
+    ops->ShowActivation();
+    */
+}
+
+String LicenseSystem::GenerateMachineID()
+{
+#ifdef ATOMIC_PLATFORM_OSX
+    String path = getenv("HOME");
+#else
+    wchar_t pathName[MAX_PATH];
+    pathName[0] = 0;
+    SHGetSpecialFolderPathW(0, pathName, CSIDL_PERSONAL, 0);
+    String path(pathName);
+#endif
+
+    Poco::MD5Engine md5;
+    md5.update(path.CString(), path.Length());
+    String id = Poco::MD5Engine::digestToHex(md5.digest()).c_str();
+    return id;
+}
+
+void LicenseSystem::ResetLicense()
+{
+    key_ = "";
+    licenseWindows_ = false;
+    licenseMac_ = false;
+    licenseAndroid_ = false;
+    licenseIOS_ = false;
+    licenseHTML5_ = false;
+    licenseModule3D_ = false;
+
+}
+
+bool LicenseSystem::LoadLicense()
+{
+
+    ResetLicense();
+
+    FileSystem* filesystem = GetSubsystem<FileSystem>();
+    String licenseFilePath = filesystem->GetAppPreferencesDir("AtomicEditor", "License");
+    licenseFilePath = AddTrailingSlash(licenseFilePath);
+    licenseFilePath += "AtomicLicense";
+
+    if (!filesystem->FileExists(licenseFilePath))
+        return false;
+
+    SharedPtr<File> file(new File(context_, licenseFilePath, FILE_READ));
+
+    file->ReadInt(); // version
+
+    String key = file->ReadString();
+    if (!ValidateKey(key))
+        return false;
+
+    key_ = key;
+
+    licenseWindows_ = file->ReadBool();
+    licenseMac_ = file->ReadBool();
+    licenseAndroid_ = file->ReadBool();
+    licenseIOS_ = file->ReadBool();
+    licenseHTML5_ = file->ReadBool();
+    licenseModule3D_ = file->ReadBool();
+
+    return true;
+}
+
+bool LicenseSystem::ValidateKey(const String& key)
+{
+    if (!key.StartsWith("ATOMIC-"))
+        return false;
+
+    Vector<String> elements = key.Split('-');
+    if (elements.Size() != 5)
+        return false;
+
+    for (unsigned i = 1; i < elements.Size(); i++)
+    {
+        String element = elements[i];
+        if (element.Length() != 4)
+            return false;
+
+        for (unsigned j = 0; j < 4; j++)
+        {
+            char c = element[j];
+            if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
+                continue;
+            return false;
+        }
+    }
+
+    return true;
+}
+
+
+void LicenseSystem::SaveLicense()
+{
+    FileSystem* filesystem = GetSubsystem<FileSystem>();
+    String licenseFilePath = filesystem->GetAppPreferencesDir("AtomicEditor", "License");
+    licenseFilePath = AddTrailingSlash(licenseFilePath);
+
+    if (!filesystem->DirExists(licenseFilePath))
+    {
+        Poco::File dirs(licenseFilePath.CString());
+        dirs.createDirectories();
+    }
+
+    licenseFilePath += "AtomicLicense";
+
+    SharedPtr<File> file(new File(context_, licenseFilePath, FILE_WRITE));
+    file->WriteInt(1); // version
+    file->WriteString(key_);
+
+    file->WriteBool(licenseWindows_);
+    file->WriteBool(licenseMac_);
+    file->WriteBool(licenseAndroid_);
+    file->WriteBool(licenseIOS_);
+    file->WriteBool(licenseHTML5_);
+    file->WriteBool(licenseModule3D_);
+
+    file->Close();
+
+}
+
+void LicenseSystem::RemoveLicense()
+{
+    FileSystem* filesystem = GetSubsystem<FileSystem>();
+
+    String licenseFilePath = filesystem->GetAppPreferencesDir("AtomicEditor", "License");
+    licenseFilePath = AddTrailingSlash(licenseFilePath);
+    licenseFilePath += "AtomicLicense";
+
+    if (filesystem->FileExists(licenseFilePath))
+    {
+        filesystem->Delete(licenseFilePath);
+    }
+}
+
+bool LicenseSystem::IsStandardLicense()
+{
+    return !licenseAndroid_;
+}
+
+
+void LicenseSystem::RequestServerVerification(const String& key)
+{
+    if (serverVerification_.NotNull())
+    {
+        LOGERROR("LicenseSystem::RequestServerLicense - request already exists");
+        return;
+    }
+
+    key_ = key;
+    CurlManager* cm = GetSubsystem<CurlManager>();
+    String post;
+    String id = GenerateMachineID();
+    post.AppendWithFormat("key=%s&id=%s", key.CString(), id.CString());
+
+    serverVerification_ = cm->MakeRequest("https://store.atomicgameengine.com/licenses/license_verify.php", post);
+
+    SubscribeToEvent(serverVerification_, E_CURLCOMPLETE, HANDLER(LicenseSystem, HandleVerification));
+}
+
+int LicenseSystem::ParseResponse(const String& response, LicenseParse& parse)
+{
+
+    LOGINFOF("%s", response.CString());
+
+    if (response.StartsWith("AC_ACTIVATIONSEXCEEDED"))
+    {
+        return 1;
+    }
+
+    if (response.StartsWith("AC_IDNOTACTIVATED"))
+    {
+        return 4;
+    }
+
+    if (response.StartsWith("AC_FAILED"))
+    {
+        return 2;
+    }
+
+    if (!response.StartsWith("WINDOWS"))
+    {
+        LOGERRORF("Error Parsing Server Response %s", response.CString());
+        return 3;
+    }
+
+    String codes = response;
+    codes.Replace("\n", "");
+    codes.Replace("\r", "");
+
+    Vector<String> cvector = codes.Split(' ');
+
+    for (unsigned i = 0; i < cvector.Size(); i++)
+    {
+        Vector<String> feature = cvector[i].Split('=');
+        if (feature.Size() != 2)
+            continue;
+
+        if (feature[0] == "WINDOWS")
+            parse.licenseWindows_ = !feature[1].StartsWith("0");
+        else if (feature[0] == "MAC")
+            parse.licenseMac_ = !feature[1].StartsWith("0");
+        else if (feature[0] == "ANDROID")
+            parse.licenseAndroid_ = !feature[1].StartsWith("0");
+        else if (feature[0] == "IOS")
+            parse.licenseIOS_ = !feature[1].StartsWith("0");
+        else if (feature[0] == "HTML5")
+            parse.licenseHTML5_ = !feature[1].StartsWith("0");
+        else if (feature[0] == "THREED")
+            parse.licenseModule3D_ = !feature[1].StartsWith("0");
+
+    }
+
+    return 0;
+
+}
+
+void LicenseSystem::Activate(const String& key, const LicenseParse& parse)
+{
+    key_ = key;
+
+    licenseWindows_ = parse.licenseWindows_;
+    licenseMac_ = parse.licenseMac_;
+    licenseAndroid_ = parse.licenseAndroid_;
+    licenseIOS_= parse.licenseIOS_;
+    licenseHTML5_= parse.licenseHTML5_;
+    licenseModule3D_= parse.licenseModule3D_;
+
+    SaveLicense();
+}
+
+SharedPtr<CurlRequest>& LicenseSystem::Deactivate()
+{
+    if (deactivate_.NotNull())
+    {
+        LOGERROR("LicenseSystem::Deactivate - request already exists");
+        return deactivate_;
+    }
+
+    if (!key_.Length())
+    {
+        LOGERROR("LicenseSystem::Deactivate - zero length key");
+        return deactivate_;
+    }
+
+    CurlManager* cm = GetSubsystem<CurlManager>();
+    String post;
+    String id = GenerateMachineID();
+    post.AppendWithFormat("key=%s&id=%s", key_.CString(), id.CString());
+
+    deactivate_ = cm->MakeRequest("https://store.atomicgameengine.com/licenses/license_deactivate.php", post);
+
+    SubscribeToEvent(deactivate_, E_CURLCOMPLETE, HANDLER(LicenseSystem, HandleDeactivate));
+
+    return deactivate_;
+
+}
+
+void LicenseSystem::HandleVerification(StringHash eventType, VariantMap& eventData)
+{
+
+    CurlRequest* request = (CurlRequest*) (eventData[CurlComplete::P_CURLREQUEST].GetPtr());
+
+    if (serverVerification_.NotNull())
+    {
+        assert(request == serverVerification_);
+
+        if (serverVerification_->GetError().Length())
+        {
+            LOGERRORF("Unable to verify with server: %s", serverVerification_->GetError().CString());
+        }
+        else
+        {
+            LicenseParse parse;
+            int code = ParseResponse(serverVerification_->GetResponse(), parse);
+
+            // Not activated
+            if (code == 4)
+            {
+
+            }
+            else if (code == 2)
+            {
+                // something is wrong with the key
+                LOGERRORF("Error with product key");
+
+                RemoveLicense();
+                ResetLicense();
+                /*
+                UIModalOps* ops = GetSubsystem<UIModalOps>();
+                ops->Hide();
+                ops->ShowActivation();
+                */
+
+            }
+            else if (code == 3)
+            {
+                // something is wrong on the activation server
+                key_ = "";
+            }
+            else if (code == 1)
+            {
+                // exceeded code, should not happen here as we aren't activating
+                key_ = "";
+            }
+            else if (code == 0)
+            {
+                // we should raise an error if there is a mismatch between local and server keys
+                // when the local says there are more enabled than server?
+                // otherwise, they could be being added
+
+                bool mismatch = false;
+
+                if (parse.licenseWindows_ != licenseWindows_)
+                    mismatch = true;
+
+                if (parse.licenseMac_ != licenseMac_)
+                    mismatch = true;
+
+                if (parse.licenseWindows_ != licenseWindows_)
+                    mismatch = true;
+
+                if (parse.licenseAndroid_ != licenseAndroid_)
+                    mismatch = true;
+
+                if (parse.licenseIOS_ != licenseIOS_)
+                    mismatch = true;
+
+                if (parse.licenseHTML5_ != licenseHTML5_)
+                    mismatch = true;
+
+                if (parse.licenseModule3D_ != licenseModule3D_)
+                    mismatch = true;
+
+                if (mismatch)
+                {
+                    LOGERROR("License Mismatch, reseting");
+                    licenseWindows_ = parse.licenseWindows_;
+                    licenseMac_ = parse.licenseMac_;
+                    licenseAndroid_ = parse.licenseAndroid_;
+                    licenseIOS_= parse.licenseIOS_;
+                    licenseHTML5_= parse.licenseHTML5_;
+                    licenseModule3D_= parse.licenseModule3D_;
+
+                    SaveLicense();
+                }
+
+                //if (!HasPlatformLicense())
+                //{
+                //    UIModalOps* ops = GetSubsystem<UIModalOps>();
+                //    if (!ops->ModalActive())
+                //        ops->ShowPlatformsInfo();
+
+               // }
+
+            }
+
+        }
+
+        UnsubscribeFromEvents(serverVerification_);
+        serverVerification_ = 0;
+    }
+
+}
+
+void LicenseSystem::HandleDeactivate(StringHash eventType, VariantMap& eventData)
+{
+    CurlRequest* request = (CurlRequest*) (eventData[CurlComplete::P_CURLREQUEST].GetPtr());
+
+    if (deactivate_.NotNull())
+    {
+        assert(request == deactivate_);
+
+        if (deactivate_->GetError().Length())
+        {
+            String msg;
+            msg.AppendWithFormat("Unable to deactivate with server: %s", deactivate_->GetError().CString());
+            //editor->PostModalError("Deactivation Error", msg);
+            LOGERROR(msg);
+        }
+        else
+        {
+            String response = request->GetResponse();
+            if (response.StartsWith("AC_FAILED"))
+            {
+                String msg;
+                msg.AppendWithFormat("Unable to deactivate with server: %s", response.CString());
+                //editor->PostModalError("Deactivation Error", msg);
+                LOGERROR(msg);
+            }
+            else if (response.StartsWith("AC_NOTACTIVATED") || response.StartsWith("AC_SUCCESS"))
+            {
+                ResetLicense();
+                RemoveLicense();
+
+                /*
+                UIModalOps* ops = GetSubsystem<UIModalOps>();
+                ops->Hide();
+                ops->ShowActivation();
+                */
+            }
+
+        }
+
+        UnsubscribeFromEvents(deactivate_);
+        deactivate_ = 0;
+    }
+
+}
+
+}
+
+// END LICENSE MANAGEMENT
+

+ 108 - 0
Source/ToolCore/License/LicenseSystem.h

@@ -0,0 +1,108 @@
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+// BEGIN LICENSE MANAGEMENT
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+#include "../Net/CurlManager.h"
+
+using namespace Atomic;
+
+namespace ToolCore
+{
+
+class LicenseSystem : public Object
+{
+    OBJECT(LicenseSystem);
+
+public:
+
+    struct LicenseParse
+    {
+        bool licenseWindows_;
+        bool licenseMac_;
+        bool licenseAndroid_;
+        bool licenseIOS_;
+        bool licenseHTML5_;
+        bool licenseModule3D_;
+
+        LicenseParse()
+        {
+            licenseWindows_ = false;
+            licenseMac_ = false;
+            licenseAndroid_ = false;
+            licenseIOS_ = false;
+            licenseHTML5_ = false;
+            licenseModule3D_ = false;
+        }
+    };
+
+
+    /// Construct.
+    LicenseSystem(Context* context);
+    /// Destruct.
+    virtual ~LicenseSystem();
+
+    void Initialize();
+
+    bool LicenseWindows() { return licenseWindows_; }
+    bool LicenseMac() { return licenseMac_; }
+    bool LicenseAndroid() { return licenseAndroid_; }
+    bool LicenseIOS() { return licenseIOS_; }
+    bool LicenseHTML5() { return licenseHTML5_; }
+    bool LicenseModule3D() { return licenseModule3D_; }
+
+    /// Returns whether there are any platform licenses available
+    bool IsStandardLicense();
+
+    void Activate(const String& key, const LicenseParse& parse);
+    SharedPtr<CurlRequest>& Deactivate();
+
+    void ResetLicense();
+
+    /// Basic key validation
+    bool ValidateKey(const String &key);
+
+    const String& GetKey() { return key_; }
+    String GenerateMachineID();
+    String GetEmail() { return email_;}
+
+    void LicenseAgreementConfirmed();
+
+    int ParseResponse(const String& response, LicenseParse &parse);
+
+private:
+
+    void RequestServerVerification(const String& key);
+
+    bool LoadLicense();
+    void SaveLicense();
+    void RemoveLicense();
+
+    void HandleVerification(StringHash eventType, VariantMap& eventData);
+    void HandleDeactivate(StringHash eventType, VariantMap& eventData);
+    void HandleEditorShutdown(StringHash eventType, VariantMap& eventData);
+
+    bool eulaAgreementConfirmed_;
+
+    String key_;
+    String email_;
+    bool licenseWindows_;
+    bool licenseMac_;
+    bool licenseAndroid_;
+    bool licenseIOS_;
+    bool licenseHTML5_;
+    bool licenseModule3D_;
+
+    SharedPtr<CurlRequest> serverVerification_;
+    SharedPtr<CurlRequest> deactivate_;
+
+
+};
+
+}
+
+// END LICENSE MANAGEMENT

+ 117 - 0
Source/ToolCore/Net/CurlManager.cpp

@@ -0,0 +1,117 @@
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+#include "AtomicEditor.h"
+#include <Atomic/Core/CoreEvents.h>
+#include <Atomic/Core/Context.h>
+#include "CurlManager.h"
+#include <curl/curl.h>
+
+#include "AEEvents.h"
+
+namespace ToolCore
+{
+
+CurlRequest::CurlRequest(Context* context, const String& url, const String& postData) :
+    Object(context)
+{
+    curl_ = curl_easy_init();
+
+    // take care, curl doesn't make copies of all data
+    url_ = url;
+    postData_ = postData;
+
+    curl_easy_setopt(curl_, CURLOPT_URL, url_.CString());
+    curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, Writer);
+
+    if (postData.Length())
+    {
+        curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, postData_.CString());
+    }
+
+    curl_easy_setopt(curl_, CURLOPT_WRITEDATA, (void *) this);
+
+}
+
+CurlRequest::~CurlRequest()
+{
+    curl_easy_cleanup(curl_);
+}
+
+void CurlRequest::ThreadFunction()
+{
+    CURLcode res;
+    res = curl_easy_perform(curl_);
+
+    if(res != CURLE_OK)
+    {
+        error_ = curl_easy_strerror(res);
+    }
+
+    shouldRun_ = false;
+}
+
+size_t CurlRequest::Writer(void *ptr, size_t size, size_t nmemb, void *crequest)
+{
+    CurlRequest* request = (CurlRequest*) crequest;
+
+    size_t realsize = size * nmemb;
+    const char* text = (const char*) ptr;
+
+    for (size_t i = 0; i < realsize; i++)
+    {
+        request->response_ += text[i];
+    }
+
+    return realsize;
+
+}
+
+
+CurlManager::CurlManager(Context* context) :
+    Object(context)
+{
+    curl_global_init(CURL_GLOBAL_DEFAULT);
+    SubscribeToEvent(E_UPDATE, HANDLER(CurlManager, HandleUpdate));
+}
+
+CurlManager::~CurlManager()
+{
+    curl_global_cleanup();
+}
+
+SharedPtr<CurlRequest> CurlManager::MakeRequest(const String& url, const String& postData)
+{
+    SharedPtr<CurlRequest> request(new CurlRequest(context_, url, postData));
+    request->Run();
+    requests_.Push(request);
+    return request;
+}
+
+void CurlManager::HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    Vector<CurlRequest*> remove;
+
+    for (unsigned i = 0; i < requests_.Size(); i++)
+    {
+        CurlRequest* request = requests_[i];
+
+        if (!request->shouldRun_)
+        {
+            remove.Push(request);
+            VariantMap eventData;
+            eventData[CurlComplete::P_CURLREQUEST] = request;
+            request->SendEvent(E_CURLCOMPLETE, eventData);
+
+        }
+    }
+
+    for (unsigned i = 0; i < remove.Size(); i++)
+    {
+        requests_.Remove(SharedPtr<CurlRequest>(remove[i]));
+    }
+
+}
+
+}

+ 77 - 0
Source/ToolCore/Net/CurlManager.h

@@ -0,0 +1,77 @@
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+#pragma once
+
+typedef void CURL;
+
+#include <Atomic/Core/Object.h>
+#include <Atomic/Container/Str.h>
+#include <Atomic/Core/Mutex.h>
+#include <Atomic/Core/Thread.h>
+
+using namespace Atomic;
+
+namespace ToolCore
+{
+
+EVENT(E_CURLCOMPLETE, CurlComplete)
+{
+    PARAM(P_CURLREQUEST, Request);      // CurlRequest*
+}
+
+class CurlRequest : public Object, public Thread
+{
+    friend class CurlManager;
+
+    OBJECT(CurlRequest);
+
+public:
+
+    CurlRequest(Context* context, const String& url, const String& postData = String::EMPTY);
+    virtual ~CurlRequest();
+
+    const String& GetError() { return error_; }
+    const String& GetResponse() { return response_; }
+
+private:
+
+    void ThreadFunction();
+    mutable Mutex mutex_;
+
+    static size_t Writer(void *ptr, size_t size, size_t nmemb, void *userp);
+
+    CURL* curl_;
+    String response_;
+    String error_;
+
+    String postData_;
+    String url_;
+};
+
+class CurlManager : public Object
+{
+    OBJECT(CurlManager);
+
+public:
+    /// Construct.
+    CurlManager(Context* context);
+    /// Destruct.
+    virtual ~CurlManager();
+
+    SharedPtr<CurlRequest> MakeRequest(const String& url, const String& postData = String::EMPTY);
+
+private:
+
+    void HandleUpdate(StringHash eventType, VariantMap& eventData);
+
+    Vector< SharedPtr<CurlRequest> > requests_;
+
+};
+
+}
+
+
+
+

+ 18 - 0
Source/ToolCore/Platform/Platform.cpp

@@ -0,0 +1,18 @@
+
+#include "Platform.h"
+
+namespace ToolCore
+{
+
+Platform::Platform(Context* context) : Object(context),
+    validLicense_(false)
+{
+
+}
+
+Platform::~Platform()
+{
+
+}
+
+}

+ 39 - 0
Source/ToolCore/Platform/Platform.h

@@ -0,0 +1,39 @@
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+
+using namespace Atomic;
+
+namespace ToolCore
+{
+
+enum PlatformID
+{
+    PLATFORMID_UNDEFINED,
+    PLATFORMID_WINDOWS,
+    PLATFORMID_MAC,
+    PLATFORMID_ANDROID,
+    PLATFORMID_IOS,
+    PLATFORMID_WEB
+};
+
+class Platform : public Object
+{
+    OBJECT(Platform);
+
+public:
+
+    Platform(Context* context);
+    virtual ~Platform();
+
+    virtual String GetName() = 0;
+    virtual PlatformID GetPlatformID() = 0;
+
+private:
+
+    bool validLicense_;
+
+};
+
+}

+ 17 - 0
Source/ToolCore/Platform/PlatformWeb.cpp

@@ -0,0 +1,17 @@
+
+#include "PlatformWeb.h"
+
+namespace ToolCore
+{
+
+PlatformWeb::PlatformWeb(Context* context) : Platform(context)
+{
+
+}
+
+PlatformWeb::~PlatformWeb()
+{
+
+}
+
+}

+ 23 - 0
Source/ToolCore/Platform/PlatformWeb.h

@@ -0,0 +1,23 @@
+
+#pragma once
+
+#include "Platform.h"
+
+namespace ToolCore
+{
+
+class PlatformWeb : public Platform
+{
+    OBJECT(PlatformWeb);
+
+public:
+
+    PlatformWeb(Context* context);
+    virtual ~PlatformWeb();
+
+    String GetName() { return "WEB"; }
+    PlatformID GetPlatformID() { return PLATFORMID_WEB; }
+
+};
+
+}

+ 270 - 0
Source/ToolCore/Project/Project.cpp

@@ -0,0 +1,270 @@
+
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Please see LICENSE.md in repository root for license information
+// https://github.com/AtomicGameEngine/AtomicGameEngine
+
+#include <rapidjson/document.h>
+#include <rapidjson/filestream.h>
+#include <rapidjson/prettywriter.h>
+
+#include <Atomic/IO/Log.h>
+#include <Atomic/IO/File.h>
+
+#include "Project.h"
+
+using namespace rapidjson;
+
+namespace ToolCore
+{
+
+Project::Project(Context* context) :
+    Object(context)
+{
+
+}
+
+Project::~Project()
+{
+
+}
+
+void Project::LoadUserPrefs(const String& fullpath)
+{
+    rapidjson::Document document;
+
+    File jsonFile(context_, fullpath);
+
+    if (!jsonFile.IsOpen())
+        return;
+
+    String json;
+    jsonFile.ReadText(json);
+
+    if (!json.Length())
+        return;
+
+    if (document.Parse<0>(json.CString()).HasParseError())
+    {
+        LOGERRORF("Could not parse Project JSON data from %s", fullpath.CString());
+        return;
+    }
+
+    const Value::Member* current_platform = document.FindMember("current_platform");
+
+    /*
+    AEEditorPlatform platform = AE_PLATFORM_UNDEFINED;
+    if (current_platform && current_platform->value.IsString())
+    {
+        String splatform = current_platform->value.GetString();
+        if (splatform == "Windows")
+            platform = AE_PLATFORM_WINDOWS;
+        else if (splatform == "Mac")
+            platform = AE_PLATFORM_MAC;
+        else if (splatform == "HTML5")
+            platform = AE_PLATFORM_HTML5;
+        else if (splatform == "iOS")
+            platform = AE_PLATFORM_IOS;
+        else if (splatform == "Android")
+            platform = AE_PLATFORM_ANDROID;
+    }
+    if (platform == AE_PLATFORM_UNDEFINED)
+    {
+#ifdef ATOMIC_PLATFORM_OSX
+        platform = AE_PLATFORM_MAC;
+#else
+        platform = AE_PLATFORM_WINDOWS;
+#endif
+    }
+    */
+
+    const Value::Member* last_build_path = document.FindMember("last_build_path");
+    if (last_build_path && last_build_path->value.IsString())
+    {
+        lastBuildPath_ = last_build_path->value.GetString();
+    }
+
+    // probably will want to move this, it will trigger a save (which is guarded with load_)
+    /*
+    Editor* editor = GetSubsystem<Editor>();
+    editor->RequestPlatformChange(platform);
+    */
+
+}
+
+void Project::SaveUserPrefs(const String& fullpath)
+{
+    //Editor* editor = GetSubsystem<Editor>();
+
+    FILE* file = fopen(fullpath.CString(), "w");
+
+    if (!file)
+        return;
+
+    rapidjson::FileStream s(file);
+    rapidjson::PrettyWriter<rapidjson::FileStream> writer(s);
+
+    writer.StartObject();
+    writer.String("version");
+    writer.Int(1);
+
+/*
+    writer.String("current_platform");
+
+    AEEditorPlatform platform = editor->GetCurrentPlatform();
+    if (platform == AE_PLATFORM_WINDOWS)
+        writer.String("Windows");
+    else if (platform == AE_PLATFORM_MAC)
+        writer.String("Mac");
+    else if (platform == AE_PLATFORM_HTML5)
+        writer.String("HTML5");
+    else if (platform == AE_PLATFORM_IOS)
+        writer.String("iOS");
+    else if (platform == AE_PLATFORM_ANDROID)
+        writer.String("Android");
+*/
+
+    writer.String("last_build_path");
+    writer.String(lastBuildPath_.CString());
+
+    writer.EndObject();
+
+    fclose(file);
+}
+
+void Project::Load(const String& fullpath)
+{
+    projectFilePath_ = fullpath;
+
+    LoadUserPrefs(GetUserPrefsFullPath(fullpath));
+
+    rapidjson::Document document;
+
+    File jsonFile(context_, fullpath);
+
+    if (!jsonFile.IsOpen())
+    {
+        return;
+    }
+
+    String json;
+    jsonFile.ReadText(json);
+
+    if (!json.Length())
+    {
+        return;
+    }
+
+    if (document.Parse<0>(json.CString()).HasParseError())
+    {
+        LOGERRORF("Could not parse Project JSON data from %s", fullpath.CString());
+        return;
+    }
+
+    const Value::Member* version = document.FindMember("version");
+    if (version && version->value.IsInt())
+    {
+
+    }
+
+    Value::Member* build_settings = document.FindMember("build_settings");
+    if (build_settings && build_settings->value.IsObject())
+    {
+        //BuildSystem* buildSystem = GetSubsystem<BuildSystem>();
+        //buildSystem->LoadBuildSettings(build_settings);
+    }
+}
+
+String Project::GetUserPrefsFullPath(const String& projectPath)
+{
+    String path = GetPath(projectPath);
+    String filename = GetFileName(projectPath);
+    String prefsPath = path + "/" + filename + ".atomic.userprefs";
+    return prefsPath;
+}
+
+void Project::Save(const String& fullpath)
+{
+    if (fullpath.Length())
+        projectFilePath_ = fullpath;
+
+    String path = projectFilePath_;
+
+    SaveUserPrefs(GetUserPrefsFullPath(path));
+
+    FILE* file = fopen(path.CString(), "w");
+
+    if (!file)
+        return;
+
+    rapidjson::FileStream s(file);
+    rapidjson::PrettyWriter<rapidjson::FileStream> writer(s);
+
+    writer.StartObject();
+    writer.String("version");
+    writer.Int(1);
+
+    //BuildSystem* buildSystem = GetSubsystem<BuildSystem>();
+    //buildSystem->SaveBuildSettings(writer);
+
+    writer.EndObject();
+
+    fclose(file);
+
+}
+
+bool Project::IsComponentsDirOrFile(const String& fullPath)
+{
+    String pathName;
+    String fileName;
+    String extension;
+
+    SplitPath(fullPath, pathName, fileName, extension);
+
+    if (extension.Length() && extension != ".js")
+        return false;
+
+    if (IsAbsoluteParentPath(componentsPath_, pathName))
+        return true;
+
+    return false;
+
+}
+
+bool Project::IsScriptsDirOrFile(const String& fullPath)
+{
+    String pathName;
+    String fileName;
+    String extension;
+
+    SplitPath(fullPath, pathName, fileName, extension);
+
+    if (extension.Length() && extension != ".js")
+        return false;
+
+    if (IsAbsoluteParentPath(scriptsPath_, pathName))
+        return true;
+
+    return false;
+
+}
+
+bool Project::IsModulesDirOrFile(const String& fullPath)
+{
+    String pathName;
+    String fileName;
+    String extension;
+
+    SplitPath(fullPath, pathName, fileName, extension);
+
+    if (extension.Length() && extension != ".js")
+        return false;
+
+    if (IsAbsoluteParentPath(modulesPath_, pathName))
+        return true;
+
+    return false;
+
+}
+
+
+}

+ 60 - 0
Source/ToolCore/Project/Project.h

@@ -0,0 +1,60 @@
+#include <Atomic/Core/Object.h>
+#include <Atomic/IO/FileSystem.h>
+
+using namespace Atomic;
+
+namespace ToolCore
+{
+
+class Project : public Object
+{
+    OBJECT(Project);
+
+public:
+    /// Construct.
+    Project(Context* context);
+    /// Destruct.
+    virtual ~Project();
+
+    const String GetResourcePath() { return resourcePath_; }
+    void SetResourcePath(const String& resourcePath)
+    {
+        resourcePath_ = AddTrailingSlash(resourcePath);
+        componentsPath_ = resourcePath_ + "Components";
+        scriptsPath_ = resourcePath_ + "Scripts";
+        modulesPath_ = resourcePath_ + "Modules";
+    }
+    void Load(const String& fullpath);
+    void Save(const String& fullpath = "");
+
+    const String& GetComponentsPath() { return componentsPath_; }
+    const String& GetScriptsPath() { return scriptsPath_; }
+    const String& GetModulesPath() { return modulesPath_; }
+
+    const String& GetProjectFilePath() { return projectFilePath_; }
+
+    bool IsComponentsDirOrFile(const String& fullPath);
+    bool IsScriptsDirOrFile(const String& fullPath);
+    bool IsModulesDirOrFile(const String& fullPath);
+
+    const String& GetLastBuildPath() { return lastBuildPath_; }
+    void SetLastBuildPath(const String& path) { lastBuildPath_ = path; }
+
+private:
+
+    void LoadUserPrefs(const String& fullpath);
+    void SaveUserPrefs(const String& fullpath);
+    String GetUserPrefsFullPath(const String& projectPath);
+
+    String projectFilePath_;
+    String resourcePath_;
+
+    String componentsPath_;
+    String scriptsPath_;
+    String modulesPath_;
+
+    String lastBuildPath_;
+
+};
+
+}

+ 0 - 0
Source/AtomicToolbox/CMakeLists.txt → Source/ToolCore/ToolEvents.h


+ 51 - 0
Source/ToolCore/ToolSystem.cpp

@@ -0,0 +1,51 @@
+
+#include <Atomic/Core/Context.h>
+
+#include "Platform/PlatformWeb.h"
+#include "Net/CurlManager.h"
+#include "License/LicenseSystem.h"
+#include "ToolSystem.h"
+
+
+namespace ToolCore
+{
+
+ToolSystem::ToolSystem(Context* context) : Object(context)
+{
+
+    context_->RegisterSubsystem(new CurlManager(context_));
+    context_->RegisterSubsystem(new LicenseSystem(context_));
+
+
+    // platform registration
+    RegisterPlatform(new PlatformWeb(context));
+}
+
+ToolSystem::~ToolSystem()
+{
+
+}
+
+void ToolSystem::SetCurrentPlatform(PlatformID platform)
+{
+    if (!platforms_.Contains((unsigned) platform))
+        return;
+
+    currentPlatform_ = platforms_[(unsigned)platform];
+
+}
+
+PlatformID ToolSystem::GetCurrentPlatform()
+{
+    if (currentPlatform_.Null())
+        return PLATFORMID_UNDEFINED;
+
+    return currentPlatform_->GetPlatformID();
+}
+
+void ToolSystem::RegisterPlatform(Platform* platform)
+{
+    platforms_.Insert(MakePair((unsigned)platform->GetPlatformID(), SharedPtr<Platform>(platform)));
+}
+
+}

+ 38 - 0
Source/ToolCore/ToolSystem.h

@@ -0,0 +1,38 @@
+
+#pragma once
+
+#include <Atomic/Core/Object.h>
+
+#include "Platform/Platform.h"
+
+using namespace Atomic;
+
+namespace ToolCore
+{
+
+class Platform;
+
+class ToolSystem : public Object
+{
+    OBJECT(ToolSystem);
+
+public:
+
+    ToolSystem(Context* context);
+    virtual ~ToolSystem();
+
+    void RegisterPlatform(Platform* platform);
+
+    void SetCurrentPlatform(PlatformID platform);
+    PlatformID GetCurrentPlatform();
+
+private:
+
+    SharedPtr<Platform> currentPlatform_;
+
+    // PlatformID -> platform
+    HashMap<unsigned, SharedPtr<Platform> > platforms_;
+
+};
+
+}