diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..323ef34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +.DS_Store + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Gcc Patch +/*.gcno + +*.d +*.o +*.dblite + +bin/* diff --git a/generate_static_library.sh b/generate_static_library.sh new file mode 100755 index 0000000..731d063 --- /dev/null +++ b/generate_static_library.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +SCHEME=${1:-godot_plugin} +PROJECT=${2:-godot_plugin.xcodeproj} +OUT=ios-att +CLASS=ATT + +xcodebuild archive \ + -project "./$PROJECT" \ + -scheme $SCHEME \ + -archivePath "./bin/ios_release.xcarchive" \ + -sdk iphoneos \ + SKIP_INSTALL=NO \ + GCC_PREPROCESSOR_DEFINITIONS="PluginClass=${CLASS}" + +xcodebuild archive \ + -project "./$PROJECT" \ + -scheme $SCHEME \ + -archivePath "./bin/sim_release.xcarchive" \ + -sdk iphonesimulator \ + SKIP_INSTALL=NO \ + GCC_PREPROCESSOR_DEFINITIONS="PluginClass=${CLASS}" + +xcodebuild archive \ + -project "./$PROJECT" \ + -scheme $SCHEME \ + -archivePath "./bin/ios_debug.xcarchive" \ + -sdk iphoneos \ + SKIP_INSTALL=NO \ + GCC_PREPROCESSOR_DEFINITIONS="DEBUG_ENABLED=1 PluginClass=${CLASS}" + +xcodebuild archive \ + -project "./$PROJECT" \ + -scheme $SCHEME \ + -archivePath "./bin/sim_debug.xcarchive" \ + -sdk iphonesimulator \ + SKIP_INSTALL=NO \ + GCC_PREPROCESSOR_DEFINITIONS="DEBUG_ENABLED=1 PluginClass=${CLASS}" + +mv "./bin/ios_release.xcarchive/Products/usr/local/lib/lib${SCHEME}.a" "./bin/ios_release.xcarchive/Products/usr/local/lib/${OUT}.a" +mv "./bin/sim_release.xcarchive/Products/usr/local/lib/lib${SCHEME}.a" "./bin/sim_release.xcarchive/Products/usr/local/lib/${OUT}.a" +mv "./bin/ios_debug.xcarchive/Products/usr/local/lib/lib${SCHEME}.a" "./bin/ios_debug.xcarchive/Products/usr/local/lib/${OUT}.a" +mv "./bin/sim_debug.xcarchive/Products/usr/local/lib/lib${SCHEME}.a" "./bin/sim_debug.xcarchive/Products/usr/local/lib/${OUT}.a" + +rm -rf "./bin/${OUT}.release.xcframework" +rm -rf "./bin/${OUT}.debug.xcframework" + +xcodebuild -create-xcframework \ + -library "./bin/ios_release.xcarchive/Products/usr/local/lib/${OUT}.a" \ + -library "./bin/sim_release.xcarchive/Products/usr/local/lib/${OUT}.a" \ + -output "./bin/${OUT}.release.xcframework" + +xcodebuild -create-xcframework \ + -library "./bin/ios_debug.xcarchive/Products/usr/local/lib/${OUT}.a" \ + -library "./bin/sim_debug.xcarchive/Products/usr/local/lib/${OUT}.a" \ + -output "./bin/${OUT}.debug.xcframework" + + + diff --git a/godot_plugin.xcodeproj/project.pbxproj b/godot_plugin.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b2108dc --- /dev/null +++ b/godot_plugin.xcodeproj/project.pbxproj @@ -0,0 +1,353 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 51CF47CB26492EF0007E0AE8 /* AppTrackingTransparency.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51CF47CA26492EF0007E0AE8 /* AppTrackingTransparency.framework */; }; + 90CAAA9924E71FF10013969F /* godot_plugin.mm in Sources */ = {isa = PBXBuildFile; fileRef = 90CAAA9824E71FF10013969F /* godot_plugin.mm */; }; + 90CAAAA424E724C50013969F /* godot_plugin_class.mm in Sources */ = {isa = PBXBuildFile; fileRef = 90CAAAA324E724C50013969F /* godot_plugin_class.mm */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 90CAAA9224E71FF10013969F /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 51CF47CA26492EF0007E0AE8 /* AppTrackingTransparency.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppTrackingTransparency.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk/System/Library/Frameworks/AppTrackingTransparency.framework; sourceTree = DEVELOPER_DIR; }; + 90CAAA9424E71FF10013969F /* libgodot_plugin.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libgodot_plugin.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 90CAAA9724E71FF10013969F /* godot_plugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = godot_plugin.h; sourceTree = ""; }; + 90CAAA9824E71FF10013969F /* godot_plugin.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = godot_plugin.mm; sourceTree = ""; }; + 90CAAAA324E724C50013969F /* godot_plugin_class.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = godot_plugin_class.mm; sourceTree = ""; }; + 90CAAAA524E724D00013969F /* godot_plugin_class.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = godot_plugin_class.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 90CAAA9124E71FF10013969F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 51CF47CB26492EF0007E0AE8 /* AppTrackingTransparency.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 90CAAA8B24E71FF10013969F = { + isa = PBXGroup; + children = ( + 90CAAA9624E71FF10013969F /* godot_plugin */, + 90CAAA9524E71FF10013969F /* Products */, + 90CAAAA824E72A8A0013969F /* Frameworks */, + ); + sourceTree = ""; + }; + 90CAAA9524E71FF10013969F /* Products */ = { + isa = PBXGroup; + children = ( + 90CAAA9424E71FF10013969F /* libgodot_plugin.a */, + ); + name = Products; + sourceTree = ""; + }; + 90CAAA9624E71FF10013969F /* godot_plugin */ = { + isa = PBXGroup; + children = ( + 90CAAA9724E71FF10013969F /* godot_plugin.h */, + 90CAAA9824E71FF10013969F /* godot_plugin.mm */, + 90CAAAA524E724D00013969F /* godot_plugin_class.h */, + 90CAAAA324E724C50013969F /* godot_plugin_class.mm */, + ); + path = godot_plugin; + sourceTree = ""; + }; + 90CAAAA824E72A8A0013969F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 51CF47CA26492EF0007E0AE8 /* AppTrackingTransparency.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 90CAAA9324E71FF10013969F /* godot_plugin */ = { + isa = PBXNativeTarget; + buildConfigurationList = 90CAAA9D24E71FF10013969F /* Build configuration list for PBXNativeTarget "godot_plugin" */; + buildPhases = ( + 90CAAA9024E71FF10013969F /* Sources */, + 90CAAA9124E71FF10013969F /* Frameworks */, + 90CAAA9224E71FF10013969F /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = godot_plugin; + productName = godot_plugin; + productReference = 90CAAA9424E71FF10013969F /* libgodot_plugin.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 90CAAA8C24E71FF10013969F /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1160; + ORGANIZATIONNAME = Godot; + TargetAttributes = { + 90CAAA9324E71FF10013969F = { + CreatedOnToolsVersion = 11.6; + LastSwiftMigration = 1230; + }; + }; + }; + buildConfigurationList = 90CAAA8F24E71FF10013969F /* Build configuration list for PBXProject "godot_plugin" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 90CAAA8B24E71FF10013969F; + productRefGroup = 90CAAA9524E71FF10013969F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 90CAAA9324E71FF10013969F /* godot_plugin */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 90CAAA9024E71FF10013969F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 90CAAAA424E724C50013969F /* godot_plugin_class.mm in Sources */, + 90CAAA9924E71FF10013969F /* godot_plugin.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 90CAAA9B24E71FF10013969F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 90CAAA9C24E71FF10013969F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 90CAAA9E24E71FF10013969F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/godot", + "$(SRCROOT)/godot/platform/iphone", + ); + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/godot/bin", + ); + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = ( + "-fcxx-modules", + "-g", + "-DDEBUG", + "-DDEBUG_ENABLED", + "-DDEBUG_MEMORY_ALLOC", + "-DDISABLE_FORCED_INLINE", + "-DTYPED_METHOD_BIND", + "-fmodules", + ); + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "godot_plugin/godot_plugin-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 90CAAA9F24E71FF10013969F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/godot", + "$(SRCROOT)/godot/platform/iphone", + ); + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/godot/bin", + ); + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = ( + "-fcxx-modules", + "-fmodules", + ); + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "godot_plugin/godot_plugin-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 90CAAA8F24E71FF10013969F /* Build configuration list for PBXProject "godot_plugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 90CAAA9B24E71FF10013969F /* Debug */, + 90CAAA9C24E71FF10013969F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 90CAAA9D24E71FF10013969F /* Build configuration list for PBXNativeTarget "godot_plugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 90CAAA9E24E71FF10013969F /* Debug */, + 90CAAA9F24E71FF10013969F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 90CAAA8C24E71FF10013969F /* Project object */; +} diff --git a/godot_plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/godot_plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..e4ccc74 --- /dev/null +++ b/godot_plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/godot_plugin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/godot_plugin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/godot_plugin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/godot_plugin.xcodeproj/xcshareddata/xcschemes/godot_plugin.xcscheme b/godot_plugin.xcodeproj/xcshareddata/xcschemes/godot_plugin.xcscheme new file mode 100644 index 0000000..8a0fb64 --- /dev/null +++ b/godot_plugin.xcodeproj/xcshareddata/xcschemes/godot_plugin.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/godot_plugin/godot_plugin.h b/godot_plugin/godot_plugin.h new file mode 100644 index 0000000..08f6398 --- /dev/null +++ b/godot_plugin/godot_plugin.h @@ -0,0 +1,3 @@ + +void ios_att_init(); +void ios_att_deinit(); diff --git a/godot_plugin/godot_plugin.mm b/godot_plugin/godot_plugin.mm new file mode 100644 index 0000000..9b66f12 --- /dev/null +++ b/godot_plugin/godot_plugin.mm @@ -0,0 +1,18 @@ + +#import +#import "godot_plugin.h" +#import "godot_plugin_class.h" +#import "core/engine.h" + +PluginClass *plugin; + +void ios_att_init() { + plugin = memnew(PluginClass); + Engine::get_singleton()->add_singleton(Engine::Singleton("ATT", plugin)); +} + +void ios_att_deinit() { + if (plugin) { + memdelete(plugin); + } +} diff --git a/godot_plugin/godot_plugin_class.h b/godot_plugin/godot_plugin_class.h new file mode 100644 index 0000000..62c6d7e --- /dev/null +++ b/godot_plugin/godot_plugin_class.h @@ -0,0 +1,27 @@ +// +// godot_plugin_implementation.h +// godot_plugin +// +// Created by Sergey Minakov on 14.08.2020. +// Copyright © 2020 Godot. All rights reserved. +// + +#pragma once + +#include "core/object.h" + +class PluginClass : public Object { + GDCLASS(ATT, Object); + + static void _bind_methods(); + +public: + int status (); + void requestTracking (); + + PluginClass(); + ~PluginClass(); +}; + + + diff --git a/godot_plugin/godot_plugin_class.mm b/godot_plugin/godot_plugin_class.mm new file mode 100644 index 0000000..40619f6 --- /dev/null +++ b/godot_plugin/godot_plugin_class.mm @@ -0,0 +1,201 @@ +// +// godot_plugin_implementation.m +// godot_plugin +// +// Created by Sergey Minakov on 14.08.2020. +// Copyright © 2020 Godot. All rights reserved. +// + +#import +#import + +#include "core/project_settings.h" +#include "core/class_db.h" + +#import "godot_plugin_class.h" + +/* + * Types conversion methods CPP<->ObjC + */ + +Variant nsobject_to_variant(NSObject *object); +NSObject *variant_to_nsobject(Variant v); + +NSString* to_nsstring(String str) { + return [[NSString alloc] initWithUTF8String:str.utf8().get_data()]; +} + +String from_nsstring(NSString* str) { + const char *s = [str UTF8String]; + return String::utf8(s != NULL ? s : ""); +} + +NSArray* to_nsarray(Array arr) { + NSMutableArray *result = [[NSMutableArray alloc] init]; + for (int i = 0; i < arr.size(); ++i) { + NSObject *value = variant_to_nsobject(arr[i]); + if (value != NULL) { + [result addObject:value]; + } else { + WARN_PRINT("Trying to add something unsupported to the array."); + } + } + return result; +} + +Array from_nsarray(NSArray* array) { + Array result; + for (NSUInteger i = 0; i < [array count]; ++i) { + NSObject *value = [array objectAtIndex:i]; + result.push_back(nsobject_to_variant(value)); + } + return result; +} + +NSDictionary* to_nsdictionary(Dictionary dic) { + NSMutableDictionary *result = [[NSMutableDictionary alloc] init]; + Array keys = dic.keys(); + for (int i = 0; i < keys.size(); ++i) { + NSString *key = [[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()]; + NSObject *value = variant_to_nsobject(dic[keys[i]]); + + if (key == NULL || value == NULL) { + return NULL; + } + + [result setObject:value forKey:key]; + } + return result; +} + +Dictionary from_nsdictionary(NSDictionary* dic) { + Dictionary result; + + NSArray *keys = [dic allKeys]; + long count = [keys count]; + for (int i = 0; i < count; ++i) { + NSObject *k = [keys objectAtIndex:i]; + NSObject *v = [dic objectForKey:k]; + + result[nsobject_to_variant(k)] = nsobject_to_variant(v); + } + return result; +} + +//convert from apple's abstract type to godot's abstract type.... +Variant nsobject_to_variant(NSObject *object) { + if ([object isKindOfClass:[NSString class]]) { + return from_nsstring((NSString *)object); + } else if ([object isKindOfClass:[NSData class]]) { + PoolByteArray ret; + NSData *data = (NSData *)object; + if ([data length] > 0) { + ret.resize([data length]); + { + // PackedByteArray::Write w = ret.write(); + copymem((void *)ret.read().ptr(), [data bytes], [data length]); + } + } + return ret; + } else if ([object isKindOfClass:[NSArray class]]) { + return from_nsarray((NSArray *)object); + } else if ([object isKindOfClass:[NSDictionary class]]) { + return from_nsdictionary((NSDictionary *)object); + } else if ([object isKindOfClass:[NSNumber class]]) { + //Every type except numbers can reliably identify its type. The following is comparing to the *internal* representation, which isn't guaranteed to match the type that was used to create it, and is not advised, particularly when dealing with potential platform differences (ie, 32/64 bit) + //To avoid errors, we'll cast as broadly as possible, and only return int or float. + //bool, char, int, uint, longlong -> int + //float, double -> float + NSNumber *num = (NSNumber *)object; + if (strcmp([num objCType], @encode(BOOL)) == 0) { + return Variant((int)[num boolValue]); + } else if (strcmp([num objCType], @encode(char)) == 0) { + return Variant((int)[num charValue]); + } else if (strcmp([num objCType], @encode(int)) == 0) { + return Variant([num intValue]); + } else if (strcmp([num objCType], @encode(unsigned int)) == 0) { + return Variant((int)[num unsignedIntValue]); + } else if (strcmp([num objCType], @encode(long long)) == 0) { + return Variant((int)[num longValue]); + } else if (strcmp([num objCType], @encode(float)) == 0) { + return Variant([num floatValue]); + } else if (strcmp([num objCType], @encode(double)) == 0) { + return Variant((float)[num doubleValue]); + } else { + return Variant(); + } + } else if ([object isKindOfClass:[NSDate class]]) { + //this is a type that icloud supports...but how did you submit it in the first place? + //I guess this is a type that *might* show up, if you were, say, trying to make your game + //compatible with existing cloud data written by another engine's version of your game + WARN_PRINT("NSDate unsupported, returning null Variant"); + return Variant(); + } else if ([object isKindOfClass:[NSNull class]] or object == nil) { + return Variant(); + } else { + WARN_PRINT("Trying to convert unknown NSObject type to Variant"); + return Variant(); + } +} + +NSObject *variant_to_nsobject(Variant v) { + if (v.get_type() == Variant::STRING) { + return to_nsstring((String)v); + } else if (v.get_type() == Variant::REAL) { + return [NSNumber numberWithDouble:(double)v]; + } else if (v.get_type() == Variant::INT) { + return [NSNumber numberWithLongLong:(long)(int)v]; + } else if (v.get_type() == Variant::BOOL) { + return [NSNumber numberWithBool:BOOL((bool)v)]; + } else if (v.get_type() == Variant::DICTIONARY) { + return to_nsdictionary(v); + } else if (v.get_type() == Variant::ARRAY) { + return to_nsarray(v); + } else if (v.get_type() == Variant::POOL_BYTE_ARRAY) { + PoolByteArray arr = v; + // PackedByteArray::Read r = arr.read(); + NSData *result = [NSData dataWithBytes:arr.read().ptr() length:arr.size()]; + return result; + } + WARN_PRINT(String("Could not add unsupported type to iCloud: '" + Variant::get_type_name(v.get_type()) + "'").utf8().get_data()); + return NULL; +} + + + +/* + * Bind plugin's public interface + */ +void PluginClass::_bind_methods() { + ClassDB::bind_method(D_METHOD("status"), &PluginClass::status); + ClassDB::bind_method(D_METHOD("requestTracking"), &PluginClass::requestTracking); + + ADD_SIGNAL(MethodInfo("requestCompleted", PropertyInfo(Variant::INT, "status"))); +} + +PluginClass::PluginClass() { + NSLog(@"Initialize ATT"); +} + +PluginClass::~PluginClass() { + NSLog(@"Deinitialize ATT"); +} + +void PluginClass::requestTracking() { + if (@available(iOS 14, *)) { + [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) { + emit_signal("requestCompleted", (int)status); + }]; + } else { + // Fallback on earlier versions + }; +} + +int PluginClass::status() { + if (@available(iOS 14, *)) { + return (int)[ATTrackingManager trackingAuthorizationStatus]; + } else { + // Fallback on earlier versions + return 0; + } +} diff --git a/ios-att.gdip b/ios-att.gdip new file mode 100644 index 0000000..d8f551b --- /dev/null +++ b/ios-att.gdip @@ -0,0 +1,14 @@ + +[config] +name="ATT" +binary="ios-att.xcframework" +initialization="ios_att_init" +deinitialization="ios_att_deinit" + +[dependencies] +linked=[] +embedded=[] +system=["AppTrackingTransparency.framework"] +capabilities=[] +files=[] +[plist] diff --git a/nativelib-export/iOS/app-tracking-transparency.plist b/nativelib-export/iOS/app-tracking-transparency.plist new file mode 100644 index 0000000..e5f6337 --- /dev/null +++ b/nativelib-export/iOS/app-tracking-transparency.plist @@ -0,0 +1,2 @@ +NSUserTrackingUsageDescription +This allows us to provide you with a better experience. \ No newline at end of file diff --git a/nativelib.json b/nativelib.json new file mode 100644 index 0000000..e18fc18 --- /dev/null +++ b/nativelib.json @@ -0,0 +1,32 @@ +{ + "name": "ios-att", + "display_name": "AppTrackingTransparency", + "description": "", + "version": "1.0.0", + "license": "MIT", + "url": "https://github.com/DrMoriarty/godot-ios-att", + "category": "misc", + "godot_version": "3.3.0", + "author": { + "name": "DrMoriarty", + "url": "https://github.com/DrMoriarty", + "donate": "https://ko-fi.com/drmoriarty" + }, + "dependencies": [ + "nativelib-export" + ], + "files": { + "scripts": "scripts", + "nativelib-export": "addons/nativelib-export" + }, + "autoload": { + "ATT": "*res://scripts/att.gd" + }, + "platform_ios": { + "files": { + "bin/ios-att.debug.xcframework": "ios/plugins/ios-att/ios-att.debug.xcframework", + "bin/ios-att.release.xcframework": "ios/plugins/ios-att/ios-att.release.xcframework", + "ios-att.gdip": "ios/plugins/ios-att/" + } + } +} diff --git a/plugin b/plugin new file mode 100755 index 0000000..64a4c06 --- /dev/null +++ b/plugin @@ -0,0 +1,401 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import sys, os, json +if sys.version_info[0] >= 3: + import pathlib +else: + reload(sys) + sys.setdefaultencoding('utf8') + +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + GRAY = '\033[90m' + +################################# +# +# Utilities +# + +def print_error(e): + print('{0}{2}{1}'.format(bcolors.FAIL, bcolors.ENDC, e)) + +def print_warning(w): + print('{0}{2}{1}'.format(bcolors.WARNING, bcolors.ENDC, w)) + +def print_bold(s): + print('{0}{2}{1}'.format(bcolors.BOLD, bcolors.ENDC, s)) + +def print_debug(s): + print('{0}{2}{1}'.format(bcolors.GRAY, bcolors.ENDC, s)) + +def print_green(s): + print('{0}{2}{1}'.format(bcolors.OKGREEN, bcolors.ENDC, s)) + +def print_blue(s): + print('{0}{2}{1}'.format(bcolors.OKBLUE, bcolors.ENDC, s)) + +def cancel(): + print_debug('Operation cancelled') + exit() + +def file_text(filepath): + if sys.version_info[0] >= 3: + return pathlib.Path(filepath).read_text() + else: + with open(filepath, 'r') as f: + return f.read() + +def validate_unacceptable_symbols(m, valids): + if not m is None and type(m) == str: + for l in m: + if valids.find(l) < 0: + print_error('Plugin name should not contain symbol "{}"'.format(l)) + return 1 + return 0 + +def replace_template(filename, template, value): + try: + pr_str = file_text(filename) + pr_str = pr_str.replace(template, value) + with open(filename, 'w') as f: + f.write(pr_str) + except IOError as e: + print_error(e) + +def store_plugin_name(plname, cpp_plname, display_name): + gdip = f""" +[config] +name="{display_name}" +binary="{plname}.xcframework" +initialization="{cpp_plname}_init" +deinitialization="{cpp_plname}_deinit" + +[dependencies] +linked=[] +embedded=[] +system=[] +capabilities=[] +files=[] +[plist] +""" + cpp_header = f""" +void {cpp_plname}_init(); +void {cpp_plname}_deinit(); +""" + + cpp_body = """ +#import +#import "godot_plugin.h" +#import "godot_plugin_class.h" +#import "core/engine.h" + +PluginClass *plugin; + +void """+cpp_plname+"""_init() { + plugin = memnew(PluginClass); + Engine::get_singleton()->add_singleton(Engine::Singleton(\""""+display_name+"""\", plugin)); +} + +void """+cpp_plname+"""_deinit() { + if (plugin) { + memdelete(plugin); + } +} +""" + + meta = { + "name": plname, + "display_name": display_name, + "description": "", + "version": "1.0.0", + "license": "MIT", + "url": "https://github.com/YourGitHubName/your_github_repository", + "category": "misc", + "godot_version": "3.2.4", + "author": { + "name": "Your Name", + "url": "https://your_site.com", + "donate": "Url to your donate page, or remove if not needed" + }, + "platform_ios": { + "files": { + f"bin/{plname}.debug.xcframework": f"ios/plugins/{plname}/{plname}.debug.xcframework", + f"bin/{plname}.release.xcframework": f"ios/plugins/{plname}/{plname}.release.xcframework", + f"{plname}.gdip": f"ios/plugins/{plname}/" + } + } + } + with open('godot_plugin/godot_plugin.h', 'w') as f: + f.write(cpp_header) + with open('godot_plugin/godot_plugin.mm', 'w') as f: + f.write(cpp_body) + with open(f'{plname}.gdip', 'w') as f: + f.write(gdip) + with open('nativelib.json', 'w') as f: + json.dump(meta, f, indent = 2) + try: + pr_str = file_text('generate_static_library.sh') + lines = pr_str.split('\n') + with open('generate_static_library.sh', 'w') as f: + for l in lines: + if l.startswith('OUT='): + f.write(f'OUT={plname}\n') + elif l.startswith('CLASS='): + f.write(f'CLASS={display_name}\n') + else: + f.write(l + '\n') + except IOError as e: + print_error(e) + exit() + print_bold('Plugin name set successfully. You can edit file "nativelib.json" and update plugin description, author information and repository url') + +def store_new_method(ret_type, method_name, args, use_swift): + inputs = [] + n = 1 + converts = [] + swiftargs = [] + for argtype in args: + varname = 'arg'+str(n) + inputs.append(argtype + ' ' + varname) + vname = 'a'+str(n) + if argtype == 'String': + converts.append(' NSString *' + vname + ' = to_nsstring(' + varname + ');') + swiftargs.append(['NSString', vname, vname]) + elif argtype == 'Array': + converts.append(' NSArray *' + vname + ' = to_nsarray(' + varname + ');') + swiftargs.append(['NSArray', vname, vname]) + elif argtype == 'Dictionary': + converts.append(' NSDictionary *' + vname + ' = to_nsdictionary(' + varname + ');') + swiftargs.append(['NSDictionary', vname, vname]) + elif argtype == 'int': + swiftargs.append(['Int', vname, varname]) + elif argtype == 'float': + swiftargs.append(['Float', vname, varname]) + else: + swiftargs.append([argtype, vname, varname]) + n += 1 + swift_call = '' + if use_swift: + if len(swiftargs) > 0: + swift_call = f'[SwiftClass {method_name}With' + for sa in swiftargs: + if sa == swiftargs[0]: + swift_call += sa[1].upper() + ':' + sa[2] + ' '; + else: + swift_call += sa[1] + ':' + sa[2] + ' '; + swift_call += ']' + else: + swift_call = f'[SwiftClass {method_name}]' + swift_ret_type = '' + if ret_type == 'void': + swift_call += ';' + elif ret_type == 'int': + swift_call = ' return ' + swift_call + ';'; + swift_ret_type = ' -> Int' + elif ret_type == 'float': + swift_call = ' return ' + swift_call + ';'; + swift_ret_type = ' -> Float' + elif ret_type == 'String': + swift_call = ' return from_nsstring('+swift_call+');' + swift_ret_type = ' -> NSString' + elif ret_type == 'Array': + swift_call = ' return from_nsarray('+swift_call+');' + swift_ret_type = ' -> NSArray' + elif ret_type == 'Dictionary': + swift_call = ' return from_nsdictionary('+swift_call+');' + swift_ret_type = ' -> NSDictionary' + + sargs = [] + for sa in swiftargs: + sargs.append(sa[1]+': '+sa[0]) + swift_declaration = f""" + static func {method_name}(""" + ', '.join(sargs) + ") "+swift_ret_type+""" { + } +""" + swift_completed = False + try: + pr_str = file_text('godot_plugin/SwiftClass.swift') + lines = pr_str.split('\n') + with open('godot_plugin/SwiftClass.swift', 'w') as f: + for l in lines: + if l.startswith('}'): + f.write(swift_declaration) + swift_completed = True + f.write(l + '\n') + except IOError as e: + print_error(e) + exit() + if not swift_completed: + print_error('Can not process Swift class') + cancel() + + declaration = f""" +{ret_type} PluginClass::{method_name}(""" + ', '.join(inputs) + """) { +""" + completed = False + try: + pr_str = file_text('godot_plugin/godot_plugin_class.h') + lines = pr_str.split('\n') + with open('godot_plugin/godot_plugin_class.h', 'w') as f: + for l in lines: + f.write(l + '\n') + if l == 'public:': + f.write(f' {ret_type} {method_name} (' + ', '.join(inputs) + ');\n') + completed = True + if not completed: + print_error('Can not process header') + cancel() + pr_str = file_text('godot_plugin/godot_plugin_class.mm') + lines = pr_str.split('\n') + with open('godot_plugin/godot_plugin_class.mm', 'w') as f: + for l in lines: + f.write(l + '\n') + if l.startswith('void PluginClass::_bind_methods() {'): + f.write(f' ClassDB::bind_method(D_METHOD("{method_name}"), &PluginClass::{method_name});\n') + f.write(declaration) + f.write('\n'.join(converts)) + if use_swift: + f.write('\n'+swift_call+'\n') + f.write('\n}\n') + except IOError as e: + print_error(e) + exit() + print(f'Generated new method: {ret_type} {method_name} (' + ', '.join(args) + ')') + +def set_plugin_name(): + print_green('Enter new plugin name (use only letters, digits, dots and dash sign):') + print_debug(' example: domain.plugin-name') + print_debug('Or press enter to cancel operation') + plname = '' + while True: + plname = input('Plugin name: ') + if plname == '': + cancel() + errors = validate_unacceptable_symbols(plname, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.') + if plname[0] in '0123456789.-': + errors += 1 + print_error('Plugin name should start with letter') + if errors <= 0: + break + print_bold(f'Using plugin name "{plname}"') + cpp_plname = plname.replace('-', '_') + print_green('Enter singleton name (use only letters and digits):') + print_debug(' example: DomainPluginName') + print_debug('Or press enter to cancel operation') + display_name = '' + while True: + display_name = input('Singleton name: ') + if display_name == '': + cancel() + errors = validate_unacceptable_symbols(display_name, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') + if display_name[0] in '0123456789': + errors += 1 + print_error('Singleton name should start with letter') + if errors <= 0: + break + print_bold(f'Using singleton name "{display_name}"') + store_plugin_name(plname, cpp_plname, display_name) + +def input_cpp_type(include_void): + if include_void: + print_bold(' 0: void (no value)') + print_bold(' 1: int (integral number)') + print_bold(' 2: float (decimal number)') + print_bold(' 3: String') + print_bold(' 4: Array') + print_bold(' 5: Dictionary') + ret_type = '' + while True: + ret_type = input('Return type: ') + if ret_type == '': + cancel() + elif ret_type == '0' and include_void: + return 'void' + elif ret_type == '1': + return 'int' + elif ret_type == '2': + return 'float' + elif ret_type == '3': + return 'String' + elif ret_type == '4': + return 'Array' + elif ret_type == '5': + return 'Dictionary' + else: + print('Unknown answer') + +def add_new_method(use_swift): + print_green('Enter new method\'s name (use only letters, digits and dash):') + print_debug('Or press enter to cancel operation') + method_name = '' + while True: + method_name = input('Method name: ') + if method_name == '': + cancel() + errors = validate_unacceptable_symbols(method_name, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_') + if method_name[0] in '0123456789_': + errors += 1 + print_error('Method name should start with letter') + if errors <= 0: + break + print_bold('Select method\'s return type:') + ret_type = input_cpp_type(True) + args = [] + print_bold('Set input argument types') + new_arg = '' + while True: + print(f'Current method declaration: {ret_type} {method_name} (' + ', '.join(args) + ')') + print_green('Select operation:') + print_debug('Or press enter to cancel operation') + print_bold(' 0: Finish method declaration') + print_bold(' 1: Add one more input argument') + new_arg = input('Operation: ') + if new_arg == '': + cancel() + elif new_arg == '0': + break + elif new_arg == '1': + print_green('Select type for new argument') + new_arg = input_cpp_type(False) + args.append(new_arg) + else: + print('Uknown operation') + store_new_method(ret_type, method_name, args, use_swift) + +def show_menu(): + while True: + print_green('Select operation:') + print_bold(' 0: Change plugin name') + print_bold(' 1: Add new CPP method') + print_bold(' 2: Add new Swift method') + print_bold(' 3: Exit') + op = input('Operation: ') + if op == '': + cancel() + elif op == '0': + set_plugin_name() + elif op == '1': + add_new_method(False) + elif op == '2': + add_new_method(True) + elif op == '3': + exit() + else: + print('Uknown operation') + +print_green('*** Godot iOS plugin template ***') + +if not os.path.exists('nativelib.json'): + set_plugin_name() +else: + show_menu() + diff --git a/scripts/att.gd b/scripts/att.gd new file mode 100644 index 0000000..8fbd8b1 --- /dev/null +++ b/scripts/att.gd @@ -0,0 +1,30 @@ +extends Node + +signal requestCompleted(status) +var _att = null + +# Authorisation statuses: + +# 0 = notDetermined +# 1 = restricted +# 2 = denied +# 3 = authorized + +func _ready() -> void: + if Engine.has_singleton('ATT'): + _att = Engine.get_singleton('ATT') + _att.connect('requestCompleted', self, '_on_request_completed') + else: + push_warning('App Tracking Transparency plugin not found') + +func request() -> void: + if _att != null: + _att.requestTracking() + +func status() -> int: + if _att != null: + return _att.status() + return 0 + +func _on_request_completed(status: int) -> void: + emit_signal('requestCompleted', status)