diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a32d29f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+/.internal
+/build
+.externalToolBuilders
+.DS_Store
+Thumbs.db
+.lock-wscript
+*.pyc
+.project
+.cproject
+builtins
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..3e328f1
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Defold
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/assets/main.font b/assets/main.font
new file mode 100644
index 0000000..95da1d7
--- /dev/null
+++ b/assets/main.font
@@ -0,0 +1,17 @@
+font: "/builtins/fonts/vera_mo_bd.ttf"
+material: "/builtins/fonts/font.material"
+size: 60
+antialias: 1
+alpha: 1.0
+outline_alpha: 0.0
+outline_width: 0.0
+shadow_alpha: 0.0
+shadow_blur: 0
+shadow_x: 0.0
+shadow_y: 0.0
+extra_characters: ""
+output_format: TYPE_BITMAP
+all_chars: false
+cache_width: 0
+cache_height: 0
+render_mode: MODE_SINGLE_LAYER
diff --git a/example/example.collection b/example/example.collection
new file mode 100644
index 0000000..55b8ec2
--- /dev/null
+++ b/example/example.collection
@@ -0,0 +1,52 @@
+name: "main"
+scale_along_z: 0
+embedded_instances {
+ id: "go"
+ data: "components {\n"
+ " id: \"gui\"\n"
+ " component: \"/example/example.gui\"\n"
+ " position {\n"
+ " x: 0.0\n"
+ " y: 0.0\n"
+ " z: 0.0\n"
+ " }\n"
+ " rotation {\n"
+ " x: 0.0\n"
+ " y: 0.0\n"
+ " z: 0.0\n"
+ " w: 1.0\n"
+ " }\n"
+ "}\n"
+ "components {\n"
+ " id: \"example\"\n"
+ " component: \"/example/example.script\"\n"
+ " position {\n"
+ " x: 0.0\n"
+ " y: 0.0\n"
+ " z: 0.0\n"
+ " }\n"
+ " rotation {\n"
+ " x: 0.0\n"
+ " y: 0.0\n"
+ " z: 0.0\n"
+ " w: 1.0\n"
+ " }\n"
+ "}\n"
+ ""
+ position {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ }
+ rotation {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ w: 1.0
+ }
+ scale3 {
+ x: 1.0
+ y: 1.0
+ z: 1.0
+ }
+}
diff --git a/example/example.gui b/example/example.gui
new file mode 100644
index 0000000..2abf299
--- /dev/null
+++ b/example/example.gui
@@ -0,0 +1,249 @@
+script: "/example/example.gui_script"
+fonts {
+ name: "main"
+ font: "/assets/main.font"
+}
+background_color {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ w: 0.0
+}
+nodes {
+ position {
+ x: 540.0
+ y: 170.0
+ z: 0.0
+ w: 1.0
+ }
+ rotation {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ w: 1.0
+ }
+ scale {
+ x: 1.0
+ y: 1.0
+ z: 1.0
+ w: 1.0
+ }
+ size {
+ x: 900.0
+ y: 160.0
+ z: 0.0
+ w: 1.0
+ }
+ color {
+ x: 1.0
+ y: 1.0
+ z: 1.0
+ w: 1.0
+ }
+ type: TYPE_BOX
+ blend_mode: BLEND_MODE_ALPHA
+ texture: ""
+ id: "login_btn"
+ xanchor: XANCHOR_NONE
+ yanchor: YANCHOR_NONE
+ pivot: PIVOT_CENTER
+ adjust_mode: ADJUST_MODE_FIT
+ layer: ""
+ inherit_alpha: true
+ slice9 {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ w: 0.0
+ }
+ clipping_mode: CLIPPING_MODE_NONE
+ clipping_visible: true
+ clipping_inverted: false
+ alpha: 1.0
+ template_node_child: false
+ size_mode: SIZE_MODE_MANUAL
+}
+nodes {
+ position {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ w: 1.0
+ }
+ rotation {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ w: 1.0
+ }
+ scale {
+ x: 1.0
+ y: 1.0
+ z: 1.0
+ w: 1.0
+ }
+ size {
+ x: 200.0
+ y: 100.0
+ z: 0.0
+ w: 1.0
+ }
+ color {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ w: 1.0
+ }
+ type: TYPE_TEXT
+ blend_mode: BLEND_MODE_ALPHA
+ text: "Manual Login\n"
+ ""
+ font: "main"
+ id: "text"
+ xanchor: XANCHOR_NONE
+ yanchor: YANCHOR_NONE
+ pivot: PIVOT_CENTER
+ outline {
+ x: 1.0
+ y: 1.0
+ z: 1.0
+ w: 1.0
+ }
+ shadow {
+ x: 1.0
+ y: 1.0
+ z: 1.0
+ w: 1.0
+ }
+ adjust_mode: ADJUST_MODE_FIT
+ line_break: false
+ parent: "login_btn"
+ layer: ""
+ inherit_alpha: true
+ alpha: 1.0
+ outline_alpha: 1.0
+ shadow_alpha: 1.0
+ template_node_child: false
+ text_leading: 1.0
+ text_tracking: 0.0
+}
+nodes {
+ position {
+ x: 540.0
+ y: 427.0
+ z: 0.0
+ w: 1.0
+ }
+ rotation {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ w: 1.0
+ }
+ scale {
+ x: 1.0
+ y: 1.0
+ z: 1.0
+ w: 1.0
+ }
+ size {
+ x: 900.0
+ y: 160.0
+ z: 0.0
+ w: 1.0
+ }
+ color {
+ x: 1.0
+ y: 1.0
+ z: 1.0
+ w: 1.0
+ }
+ type: TYPE_BOX
+ blend_mode: BLEND_MODE_ALPHA
+ texture: ""
+ id: "silent_login_btn"
+ xanchor: XANCHOR_NONE
+ yanchor: YANCHOR_NONE
+ pivot: PIVOT_CENTER
+ adjust_mode: ADJUST_MODE_FIT
+ layer: ""
+ inherit_alpha: true
+ slice9 {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ w: 0.0
+ }
+ clipping_mode: CLIPPING_MODE_NONE
+ clipping_visible: true
+ clipping_inverted: false
+ alpha: 1.0
+ template_node_child: false
+ size_mode: SIZE_MODE_MANUAL
+}
+nodes {
+ position {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ w: 1.0
+ }
+ rotation {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ w: 1.0
+ }
+ scale {
+ x: 1.0
+ y: 1.0
+ z: 1.0
+ w: 1.0
+ }
+ size {
+ x: 200.0
+ y: 100.0
+ z: 0.0
+ w: 1.0
+ }
+ color {
+ x: 0.0
+ y: 0.0
+ z: 0.0
+ w: 1.0
+ }
+ type: TYPE_TEXT
+ blend_mode: BLEND_MODE_ALPHA
+ text: "Silent Login Btn"
+ font: "main"
+ id: "text1"
+ xanchor: XANCHOR_NONE
+ yanchor: YANCHOR_NONE
+ pivot: PIVOT_CENTER
+ outline {
+ x: 1.0
+ y: 1.0
+ z: 1.0
+ w: 1.0
+ }
+ shadow {
+ x: 1.0
+ y: 1.0
+ z: 1.0
+ w: 1.0
+ }
+ adjust_mode: ADJUST_MODE_FIT
+ line_break: false
+ parent: "silent_login_btn"
+ layer: ""
+ inherit_alpha: true
+ alpha: 1.0
+ outline_alpha: 1.0
+ shadow_alpha: 1.0
+ template_node_child: false
+ text_leading: 1.0
+ text_tracking: 0.0
+}
+material: "/builtins/materials/gui.material"
+adjust_reference: ADJUST_REFERENCE_PARENT
+max_nodes: 512
diff --git a/example/example.gui_script b/example/example.gui_script
new file mode 100644
index 0000000..b4a4f51
--- /dev/null
+++ b/example/example.gui_script
@@ -0,0 +1,52 @@
+function init(self)
+ msg.post(".", "acquire_input_focus")
+
+ if siwg then
+ siwg.set_callback(gpgs_callback)
+ end
+end
+
+function on_message(self, message_id, message, sender)
+
+end
+
+function on_input(self, action_id, action)
+ if action_id == hash("touch") and action.pressed then
+ if gui.pick_node(gui.get_node("login_btn"), action.x, action.y) then
+ if siwg then
+ print("Login Pressed!")
+ siwg.login()
+ end
+ end
+
+ if gui.pick_node(gui.get_node("silent_login_btn"), action.x, action.y) then
+ if siwg then
+ print("Login Pressed!")
+ siwg.silent_login()
+ end
+ end
+ end
+end
+
+function gpgs_callback(self, message_id, message)
+ if message_id == siwg.MSG_SIGN_IN or message_id == siwg.MSG_SILENT_SIGN_IN then
+ if message.status == siwg.STATUS_SUCCESS then
+ print("SIWG ID: " .. siwg.get_id())
+ print("SIWG DisplayName: " .. siwg.get_display_name())
+
+ if sys.get_config("siwg.client_id") then
+ print("SIWG id_token: ", siwg.get_id_token())
+ print("SIWG auth_code: ", siwg.get_server_auth_code())
+ end
+
+ elseif message.status == siwg.STATUS_FAILED then
+ print("Status: FAILED")
+ print("Error: " .. message.error)
+ end
+ elseif message_id == siwg.MSG_SIGN_OUT then
+ print("SIWG Logged out!")
+ end
+
+ print("SIWG is_logged_in: " .. tostring(siwg.is_logged_in()))
+end
+
diff --git a/example/example.script b/example/example.script
new file mode 100644
index 0000000..d0f5843
--- /dev/null
+++ b/example/example.script
@@ -0,0 +1,28 @@
+function init(self)
+ msg.post(".", "acquire_input_focus")
+end
+
+function final(self)
+ -- Add finalization code here
+ -- Remove this function if not needed
+end
+
+function update(self, dt)
+ -- Add update code here
+ -- Remove this function if not needed
+end
+
+function on_message(self, message_id, message, sender)
+ -- Add message-handling code here
+ -- Remove this function if not needed
+end
+
+function on_input(self, action_id, action)
+ -- Add input-handling code here
+ -- Remove this function if not needed
+end
+
+function on_reload(self)
+ -- Add reload-handling code here
+ -- Remove this function if not needed
+end
diff --git a/fnal/api/siwg.api b/fnal/api/siwg.api
new file mode 100644
index 0000000..2132a6f
--- /dev/null
+++ b/fnal/api/siwg.api
@@ -0,0 +1,199 @@
+- name: siwg
+ type: table
+ desc: Functions and constants for interacting with Google Services (SIWG) APIs
+
+ members:
+
+#*****************************************************************************************************
+
+ - name: is_supported
+ type: function
+ desc: Check if Google Services are available & ready on the device.
+
+ returns:
+ - name: is_supported
+ type: boolean
+ desc: Status of Google Services on the device.
+
+ examples:
+ - desc: |-
+ ```lua
+ if siwg then
+ local is_supported = siwg.is_supported()
+ end
+ ```
+
+#*****************************************************************************************************
+
+ - name: login
+ type: function
+ desc: Login to SIWG using a button.
+
+ examples:
+ - desc: |-
+ Log in to SIWG using a button:
+ ```lua
+ if siwg then
+ siwg.login()
+ end
+ ```
+
+#*****************************************************************************************************
+
+ - name: silent_login
+ type: function
+ desc: Silent login to SIWG.
+
+ This function is trying to retrieve the currently signed-in player’s account.
+
+ [icon:attention] By default login methods request `GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN`.
+ But if you use Disk, we have to request extra scope `Drive.SCOPE_APPFOLDER`.
+ Or if you use ID token, we have to request ID token with provided client_id.
+ If so it causes the first time silent sign-in to fail, except for users who
+ have already signed in successfully on a different device. Turn off SIWG
+ features you don't want to use in `game.project`.
+
+ examples:
+ - desc: |-
+ ```lua
+ function init(self)
+ if siwg then
+ siwg.silent_login()
+ end
+ end
+ ```
+
+#*****************************************************************************************************
+
+ - name: logout
+ type: function
+ desc: Logout from SIWG
+
+ examples:
+ - desc: |-
+ ```lua
+ if siwg then
+ siwg.logout()
+ end
+ ```
+
+#*****************************************************************************************************
+
+ - name: get_display_name
+ type: function
+ desc: Get the current SIWG player display name.
+
+ returns:
+ - name: name
+ type: string
+ desc: The player's display name.
+
+ examples:
+ - desc: |-
+ ```lua
+ if siwg then
+ local name = siwg.get_display_name()
+ end
+ ```
+
+#*****************************************************************************************************
+
+ - name: get_id
+ type: function
+ desc: Get the current SIWG player id.
+
+ returns:
+ - name: id
+ type: string
+ desc: The player ID.
+
+ examples:
+ - desc: |-
+ ```lua
+ if siwg then
+ local id = siwg.get_id()
+ end
+ ```
+
+#*****************************************************************************************************
+
+ - name: get_id_token
+ type: function
+ desc: Get the current SIWG player id token. Available only if "siwg.client_id" is configured in game.project
+ and "siwg.request_id_token = 1".
+
+ returns:
+ - name: id_token
+ type: string
+ desc: The player ID token.
+
+ examples:
+ - desc: |-
+ ```lua
+ if siwg then
+ local id_token = siwg.get_id_token()
+ end
+ ```
+
+#*****************************************************************************************************
+
+ - name: get_server_auth_code
+ type: function
+ desc: Returns a one-time server auth code to send to your web server which can be exchanged for access token
+
+ returns:
+ - name: server_auth_code
+ type: string
+ desc: The server auth code for logged in account.
+
+ examples:
+ - desc: |-
+ ```lua
+ if siwg then
+ local server_auth_code = siwg.get_server_auth_code()
+ end
+ ```
+
+#*****************************************************************************************************
+
+ - name: is_logged_in
+ type: function
+ desc: Check if a user is logged in currently.
+
+ returns:
+ - name: is_loggedin
+ type: boolean
+ desc: Current login state.
+
+ examples:
+ - desc: |-
+ ```lua
+ if siwg then
+ local is_loggedin = siwg.is_logged_in()
+ end
+ ```
+
+#*****************************************************************************************************
+
+ - name: MSG_SIGN_IN
+ type: number
+ desc: The message type that SIWG sends when finishing the asynchronous operation
+ after calling `siwg.login()`
+
+ - name: MSG_SILENT_SIGN_IN
+ type: number
+ desc: The message type that SIWG sends when finishing the asynchronous operation
+ after calling `siwg.silent_login()`
+
+ - name: MSG_SIGN_OUT
+ type: number
+ desc: The message type that SIWG sends when finishing the asynchronous operation
+ after calling `siwg.logout()`
+
+ - name: STATUS_SUCCESS
+ type: number
+ desc: An operation success.
+
+ - name: STATUS_FAILED
+ type: number
+ desc: An operation failed. Check the error field in the massage table.
\ No newline at end of file
diff --git a/fnal/ext.manifest b/fnal/ext.manifest
new file mode 100644
index 0000000..fea73a1
--- /dev/null
+++ b/fnal/ext.manifest
@@ -0,0 +1 @@
+name: FNAL
\ No newline at end of file
diff --git a/fnal/manifests/android/AndroidManifest.xml b/fnal/manifests/android/AndroidManifest.xml
new file mode 100644
index 0000000..25f2762
--- /dev/null
+++ b/fnal/manifests/android/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fnal/manifests/android/build.gradle b/fnal/manifests/android/build.gradle
new file mode 100644
index 0000000..80c1df7
--- /dev/null
+++ b/fnal/manifests/android/build.gradle
@@ -0,0 +1,6 @@
+dependencies {
+ // https://developers.google.com/android/guides/setup#split
+ implementation 'com.google.android.gms:play-services-base:17.5.0'
+ implementation 'com.google.android.gms:play-services-auth:18.1.0'
+ implementation 'com.google.android.gms:play-services-games:20.0.1'
+}
\ No newline at end of file
diff --git a/fnal/src/fnal.cpp b/fnal/src/fnal.cpp
new file mode 100644
index 0000000..ecc4141
--- /dev/null
+++ b/fnal/src/fnal.cpp
@@ -0,0 +1,80 @@
+#define EXTENSION_NAME FNAL
+#define LIB_NAME "FNAL"
+#define MODULE_NAME "fnal"
+
+// include the Defold SDK
+#include
+#include
+#include
+
+static int FNAL_Log(lua_State* L)
+{
+ // The number of expected items to be on the Lua stack
+ // once this struct goes out of scope
+ DM_LUA_STACK_CHECK(L, 1);
+
+ // Check and get parameter string from stack
+ char* str = (char*)luaL_checkstring(L, 1);
+
+ // Reverse the string
+ int len = strlen(str);
+ for(int i = 0; i < len / 2; i++) {
+ const char a = str[i];
+ const char b = str[len - i - 1];
+ str[i] = b;
+ str[len - i - 1] = a;
+ }
+
+ // Put the reverse string on the stack
+ lua_pushstring(L, str);
+
+ // Return 1 item
+ return 1;
+}
+
+// Functions exposed to Lua
+static const luaL_reg FNAL_Methods[] =
+{
+ {"log", FNAL_Log},
+ {0, 0}
+};
+
+static void LuaInit(lua_State* L)
+{
+ int top = lua_gettop(L);
+ luaL_register(L, MODULE_NAME, FNAL_Methods);
+ lua_pop(L, 1);
+ assert(top == lua_gettop(L));
+}
+
+dmExtension::Result AppInitializeFNAL(dmExtension::AppParams* params)
+{
+ dmLogInfo("[FNAL] Registered Extension!");
+ return dmExtension::RESULT_OK;
+}
+
+dmExtension::Result AppFinalizeFNAL(dmExtension::AppParams* params)
+{
+ return dmExtension::RESULT_OK;
+}
+
+dmExtension::Result InitializeFNAL(dmExtension::Params* params)
+{
+ dmLogInfo("[FNAL] Initializing Extension...");
+ return dmExtension::RESULT_OK;
+}
+
+dmExtension::Result UpdateFNAL(dmExtension::Params* params)
+{
+ return dmExtension::RESULT_OK;
+}
+
+dmExtension::Result FinalizeFNAL(dmExtension::Params* params)
+{
+ dmLogInfo("[FNAL] Finalizing Extension...");
+ return dmExtension::RESULT_OK;
+}
+
+
+
+DM_DECLARE_EXTENSION(EXTENSION_NAME, LIB_NAME, AppInitializeFNAL, AppFinalizeFNAL, InitializeFNAL, UpdateFNAL, 0, FinalizeFNAL)
\ No newline at end of file
diff --git a/game.project b/game.project
new file mode 100644
index 0000000..6e659ca
--- /dev/null
+++ b/game.project
@@ -0,0 +1,28 @@
+[bootstrap]
+main_collection = /example/example.collectionc
+
+[script]
+shared_state = 1
+
+[display]
+width = 1080
+height = 1920
+
+[project]
+title = FNAL-Extension
+
+[library]
+include_dirs = fnal
+
+[native_extension]
+app_manifest = /generated.appmanifest
+
+[android]
+input_method = HiddenInputField
+immersive_mode = 1
+package = com.aterve.extension.fnal
+debuggable = 1
+
+[ios]
+bundle_identifier = com.aterve.extension.fnal
+
diff --git a/generated.appmanifest b/generated.appmanifest
new file mode 100644
index 0000000..df2f660
--- /dev/null
+++ b/generated.appmanifest
@@ -0,0 +1,100 @@
+# App manifest generated Fri Nov 20 2020 09:28:33 GMT+0100 (Central European Standard Time)
+# Settings: OpenGL
+platforms:
+ x86_64-osx:
+ context:
+ excludeLibs: []
+ excludeSymbols: []
+ symbols: []
+ libs: []
+ frameworks: []
+ linkFlags: []
+
+ x86_64-linux:
+ context:
+ excludeLibs: []
+ excludeSymbols: []
+ symbols: []
+ libs: []
+ linkFlags: []
+
+ js-web:
+ context:
+ excludeLibs: []
+ excludeJsLibs: []
+ excludeSymbols: []
+ symbols: []
+ libs: []
+ linkFlags: []
+
+ wasm-web:
+ context:
+ excludeLibs: []
+ excludeJsLibs: []
+ excludeSymbols: []
+ symbols: []
+ libs: []
+ linkFlags: []
+
+ x86-win32:
+ context:
+ excludeLibs: []
+ excludeSymbols: []
+ symbols: []
+ libs: []
+ linkFlags: []
+
+ x86_64-win32:
+ context:
+ excludeLibs: []
+ excludeSymbols: []
+ symbols: []
+ libs: []
+ linkFlags: []
+
+ armv7-android:
+ context:
+ excludeLibs: []
+ excludeJars: []
+ excludeSymbols: []
+ symbols: []
+ libs: []
+ linkFlags: []
+ jetifier: true
+
+ arm64-android:
+ context:
+ excludeLibs: []
+ excludeJars: []
+ excludeSymbols: []
+ symbols: []
+ libs: []
+ linkFlags: []
+ jetifier: true
+
+ armv7-ios:
+ context:
+ excludeLibs: []
+ excludeSymbols: []
+ symbols: []
+ libs: []
+ frameworks: []
+ linkFlags: []
+
+ arm64-ios:
+ context:
+ excludeLibs: []
+ excludeSymbols: []
+ symbols: []
+ libs: []
+ frameworks: []
+ linkFlags: []
+
+ x86_64-ios:
+ context:
+ excludeLibs: []
+ excludeSymbols: []
+ symbols: []
+ libs: []
+ frameworks: []
+ linkFlags: []
\ No newline at end of file
diff --git a/input/game.input_binding b/input/game.input_binding
new file mode 100644
index 0000000..8ed1d4e
--- /dev/null
+++ b/input/game.input_binding
@@ -0,0 +1,4 @@
+mouse_trigger {
+ input: MOUSE_BUTTON_1
+ action: "touch"
+}