From cc89a0af199d6aac22965a0ade7d51a98b38a30a Mon Sep 17 00:00:00 2001 From: chikorita157 Date: Tue, 12 Jun 2018 19:11:03 -0400 Subject: [PATCH] inital --- Readme.md | 12 + SwinsainDiscord.entitlements | 5 + .../project.pbxproj | 99 ++++++-- .../contents.xcworkspacedata | 2 +- .../xcdebugger/Breakpoints_v2.xcbkptlist | 5 + .../xcschemes/xcschememanagement.plist | 5 + .../AppDelegate.h | 2 +- SwinsianDiscord/AppDelegate.m | 136 +++++++++++ .../AppIcon.appiconset/Contents.json | 0 SwinsianDiscord/Assets.xcassets/Contents.json | 6 + .../menubaricon.imageset/Contents.json | 21 ++ .../icons8-music_filled-1.png | Bin 0 -> 285 bytes .../icons8-music_filled.png | Bin 0 -> 458 bytes .../Base.lproj/MainMenu.xib | 67 ++++-- SwinsianDiscord/Credits.rtf | 43 ++++ SwinsianDiscord/DiscordManager.h | 16 ++ SwinsianDiscord/DiscordManager.m | 88 +++++++ .../DiscordRPC.framework/DiscordRPC | 1 + SwinsianDiscord/DiscordRPC.framework/Headers | 1 + SwinsianDiscord/DiscordRPC.framework/Modules | 1 + .../DiscordRPC.framework/Resources | 1 + .../Versions/A/DiscordRPC | Bin 0 -> 90116 bytes .../Versions/A/Headers/DiscordRPC.h | 19 ++ .../Versions/A/Headers/backoff.h | 40 ++++ .../Versions/A/Headers/connection.h | 19 ++ .../Versions/A/Headers/discord_register.h | 26 +++ .../Versions/A/Headers/discord_rpc.h | 87 +++++++ .../Versions/A/Headers/msg_queue.h | 36 +++ .../Versions/A/Headers/rpc_connection.h | 59 +++++ .../Versions/A/Headers/serialization.h | 215 ++++++++++++++++++ .../Versions/A/Modules/module.modulemap | 6 + .../Versions/A/Resources/Info.plist | 44 ++++ .../DiscordRPC.framework/Versions/Current | 1 + SwinsianDiscord/EULA.rtf | 21 ++ {iTunesDiscord => SwinsianDiscord}/Info.plist | 7 + SwinsianDiscord/PFAboutWindow.xib | 127 +++++++++++ SwinsianDiscord/PFAboutWindowController.h | 104 +++++++++ SwinsianDiscord/PFAboutWindowController.m | 201 ++++++++++++++++ .../iTunesDiscord.entitlements | 7 +- {iTunesDiscord => SwinsianDiscord}/main.m | 2 +- iTunesDiscord/AppDelegate.m | 28 --- 41 files changed, 1482 insertions(+), 78 deletions(-) create mode 100644 Readme.md create mode 100644 SwinsainDiscord.entitlements rename {iTunesDiscord.xcodeproj => SwinsianDiscord.xcodeproj}/project.pbxproj (64%) rename {iTunesDiscord.xcodeproj => SwinsianDiscord.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (53%) create mode 100644 SwinsianDiscord.xcodeproj/xcuserdata/takanashirikka.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist rename {iTunesDiscord.xcodeproj => SwinsianDiscord.xcodeproj}/xcuserdata/takanashirikka.xcuserdatad/xcschemes/xcschememanagement.plist (76%) rename {iTunesDiscord => SwinsianDiscord}/AppDelegate.h (91%) create mode 100644 SwinsianDiscord/AppDelegate.m rename {iTunesDiscord => SwinsianDiscord}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) create mode 100644 SwinsianDiscord/Assets.xcassets/Contents.json create mode 100644 SwinsianDiscord/Assets.xcassets/menubaricon.imageset/Contents.json create mode 100644 SwinsianDiscord/Assets.xcassets/menubaricon.imageset/icons8-music_filled-1.png create mode 100644 SwinsianDiscord/Assets.xcassets/menubaricon.imageset/icons8-music_filled.png rename {iTunesDiscord => SwinsianDiscord}/Base.lproj/MainMenu.xib (94%) create mode 100644 SwinsianDiscord/Credits.rtf create mode 100644 SwinsianDiscord/DiscordManager.h create mode 100644 SwinsianDiscord/DiscordManager.m create mode 120000 SwinsianDiscord/DiscordRPC.framework/DiscordRPC create mode 120000 SwinsianDiscord/DiscordRPC.framework/Headers create mode 120000 SwinsianDiscord/DiscordRPC.framework/Modules create mode 120000 SwinsianDiscord/DiscordRPC.framework/Resources create mode 100755 SwinsianDiscord/DiscordRPC.framework/Versions/A/DiscordRPC create mode 100644 SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/DiscordRPC.h create mode 100755 SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/backoff.h create mode 100755 SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/connection.h create mode 100755 SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/discord_register.h create mode 100755 SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/discord_rpc.h create mode 100755 SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/msg_queue.h create mode 100755 SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/rpc_connection.h create mode 100755 SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/serialization.h create mode 100644 SwinsianDiscord/DiscordRPC.framework/Versions/A/Modules/module.modulemap create mode 100644 SwinsianDiscord/DiscordRPC.framework/Versions/A/Resources/Info.plist create mode 120000 SwinsianDiscord/DiscordRPC.framework/Versions/Current create mode 100644 SwinsianDiscord/EULA.rtf rename {iTunesDiscord => SwinsianDiscord}/Info.plist (88%) create mode 100755 SwinsianDiscord/PFAboutWindow.xib create mode 100755 SwinsianDiscord/PFAboutWindowController.h create mode 100755 SwinsianDiscord/PFAboutWindowController.m rename {iTunesDiscord => SwinsianDiscord}/iTunesDiscord.entitlements (53%) rename {iTunesDiscord => SwinsianDiscord}/main.m (92%) delete mode 100644 iTunesDiscord/AppDelegate.m diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..67c8aae --- /dev/null +++ b/Readme.md @@ -0,0 +1,12 @@ +# SwinsianDiscord +SwinsianDiscord is a simple menubar app for macOS that allows you to share what you are playing in Swinsian in Discord. [Swinsian](https://swinsian.com) is a Advanced Music Player for Mac. You want to use Swinsian because iTunes on Mac sucks and it's bloated. + +This program works on macOS El Capitan or later and requires Swinsian version >= 1.8.8 to work. + +The logo and the program name, Swinsian is owned by the respective owners. + +# To use +1. Run the App +2. Enable + +SwinsianDiscord is licensed under BSD License. diff --git a/SwinsainDiscord.entitlements b/SwinsainDiscord.entitlements new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/SwinsainDiscord.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/iTunesDiscord.xcodeproj/project.pbxproj b/SwinsianDiscord.xcodeproj/project.pbxproj similarity index 64% rename from iTunesDiscord.xcodeproj/project.pbxproj rename to SwinsianDiscord.xcodeproj/project.pbxproj index 9d4b7e8..c47cc35 100644 --- a/iTunesDiscord.xcodeproj/project.pbxproj +++ b/SwinsianDiscord.xcodeproj/project.pbxproj @@ -7,14 +7,45 @@ objects = { /* Begin PBXBuildFile section */ + F85D493F20D078D800343699 /* DiscordManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F85D493D20D078D700343699 /* DiscordManager.m */; }; + F85D494620D0838300343699 /* PFAboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = F85D494420D0837E00343699 /* PFAboutWindowController.m */; }; + F85D494820D0838700343699 /* PFAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = F85D494720D0838600343699 /* PFAboutWindow.xib */; }; + F85D494A20D083CD00343699 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = F85D494920D083CD00343699 /* Credits.rtf */; }; + F85D494C20D0851600343699 /* EULA.rtf in Resources */ = {isa = PBXBuildFile; fileRef = F85D494B20D0851600343699 /* EULA.rtf */; }; + F85D494E20D085E600343699 /* DiscordRPC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F85D494D20D085E600343699 /* DiscordRPC.framework */; }; + F85D494F20D085EE00343699 /* DiscordRPC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F85D494D20D085E600343699 /* DiscordRPC.framework */; }; + F85D495020D085EE00343699 /* DiscordRPC.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F85D494D20D085E600343699 /* DiscordRPC.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F87417FC20D0750700614654 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F87417FB20D0750700614654 /* AppDelegate.m */; }; F87417FE20D0750700614654 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F87417FD20D0750700614654 /* Assets.xcassets */; }; F874180120D0750700614654 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = F87417FF20D0750700614654 /* MainMenu.xib */; }; F874180420D0750700614654 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F874180320D0750700614654 /* main.m */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + F85D494220D07BE800343699 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + F85D495020D085EE00343699 /* DiscordRPC.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - F87417F720D0750700614654 /* iTunesDiscord.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iTunesDiscord.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F85D493D20D078D700343699 /* DiscordManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DiscordManager.m; sourceTree = ""; }; + F85D493E20D078D800343699 /* DiscordManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiscordManager.h; sourceTree = ""; }; + F85D494320D07F8800343699 /* SwinsainDiscord.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwinsainDiscord.entitlements; sourceTree = ""; }; + F85D494420D0837E00343699 /* PFAboutWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFAboutWindowController.m; sourceTree = ""; }; + F85D494520D0838000343699 /* PFAboutWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFAboutWindowController.h; sourceTree = ""; }; + F85D494720D0838600343699 /* PFAboutWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = PFAboutWindow.xib; path = SwinsianDiscord/PFAboutWindow.xib; sourceTree = SOURCE_ROOT; }; + F85D494920D083CD00343699 /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; + F85D494B20D0851600343699 /* EULA.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = EULA.rtf; sourceTree = ""; }; + F85D494D20D085E600343699 /* DiscordRPC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = DiscordRPC.framework; sourceTree = ""; }; + F87417F720D0750700614654 /* SwinsianDiscord.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwinsianDiscord.app; sourceTree = BUILT_PRODUCTS_DIR; }; F87417FA20D0750700614654 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; F87417FB20D0750700614654 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; F87417FD20D0750700614654 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -29,6 +60,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F85D494E20D085E600343699 /* DiscordRPC.framework in Frameworks */, + F85D494F20D085EE00343699 /* DiscordRPC.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -38,7 +71,8 @@ F87417EE20D0750700614654 = { isa = PBXGroup; children = ( - F87417F920D0750700614654 /* iTunesDiscord */, + F85D494320D07F8800343699 /* SwinsainDiscord.entitlements */, + F87417F920D0750700614654 /* SwinsianDiscord */, F87417F820D0750700614654 /* Products */, ); sourceTree = ""; @@ -46,43 +80,52 @@ F87417F820D0750700614654 /* Products */ = { isa = PBXGroup; children = ( - F87417F720D0750700614654 /* iTunesDiscord.app */, + F87417F720D0750700614654 /* SwinsianDiscord.app */, ); name = Products; sourceTree = ""; }; - F87417F920D0750700614654 /* iTunesDiscord */ = { + F87417F920D0750700614654 /* SwinsianDiscord */ = { isa = PBXGroup; children = ( + F85D494720D0838600343699 /* PFAboutWindow.xib */, + F85D494520D0838000343699 /* PFAboutWindowController.h */, + F85D494420D0837E00343699 /* PFAboutWindowController.m */, F87417FA20D0750700614654 /* AppDelegate.h */, F87417FB20D0750700614654 /* AppDelegate.m */, F87417FD20D0750700614654 /* Assets.xcassets */, + F85D494D20D085E600343699 /* DiscordRPC.framework */, F87417FF20D0750700614654 /* MainMenu.xib */, + F85D493E20D078D800343699 /* DiscordManager.h */, + F85D493D20D078D700343699 /* DiscordManager.m */, F874180220D0750700614654 /* Info.plist */, F874180320D0750700614654 /* main.m */, + F85D494B20D0851600343699 /* EULA.rtf */, F874180520D0750700614654 /* iTunesDiscord.entitlements */, + F85D494920D083CD00343699 /* Credits.rtf */, ); - path = iTunesDiscord; + path = SwinsianDiscord; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - F87417F620D0750700614654 /* iTunesDiscord */ = { + F87417F620D0750700614654 /* SwinsianDiscord */ = { isa = PBXNativeTarget; - buildConfigurationList = F874180820D0750700614654 /* Build configuration list for PBXNativeTarget "iTunesDiscord" */; + buildConfigurationList = F874180820D0750700614654 /* Build configuration list for PBXNativeTarget "SwinsianDiscord" */; buildPhases = ( F87417F320D0750700614654 /* Sources */, F87417F420D0750700614654 /* Frameworks */, F87417F520D0750700614654 /* Resources */, + F85D494220D07BE800343699 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); - name = iTunesDiscord; + name = SwinsianDiscord; productName = iTunesDiscord; - productReference = F87417F720D0750700614654 /* iTunesDiscord.app */; + productReference = F87417F720D0750700614654 /* SwinsianDiscord.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -97,10 +140,15 @@ F87417F620D0750700614654 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 0; + }; + }; }; }; }; - buildConfigurationList = F87417F220D0750700614654 /* Build configuration list for PBXProject "iTunesDiscord" */; + buildConfigurationList = F87417F220D0750700614654 /* Build configuration list for PBXProject "SwinsianDiscord" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; @@ -113,7 +161,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - F87417F620D0750700614654 /* iTunesDiscord */, + F87417F620D0750700614654 /* SwinsianDiscord */, ); }; /* End PBXProject section */ @@ -123,8 +171,11 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + F85D494C20D0851600343699 /* EULA.rtf in Resources */, F87417FE20D0750700614654 /* Assets.xcassets in Resources */, + F85D494820D0838700343699 /* PFAboutWindow.xib in Resources */, F874180120D0750700614654 /* MainMenu.xib in Resources */, + F85D494A20D083CD00343699 /* Credits.rtf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -136,7 +187,9 @@ buildActionMask = 2147483647; files = ( F874180420D0750700614654 /* main.m in Sources */, + F85D494620D0838300343699 /* PFAboutWindowController.m in Sources */, F87417FC20D0750700614654 /* AppDelegate.m in Sources */, + F85D493F20D078D800343699 /* DiscordManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -202,7 +255,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -251,7 +304,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; }; @@ -261,11 +314,15 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = iTunesDiscord/iTunesDiscord.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 9HDZTER525; - INFOPLIST_FILE = iTunesDiscord/Info.plist; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/iTunesDiscord", + "$(PROJECT_DIR)/SwinsianDiscord", + ); + INFOPLIST_FILE = "$(SRCROOT)/SwinsianDiscord/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = pro.moyit.iTunesDiscord; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -276,11 +333,15 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = iTunesDiscord/iTunesDiscord.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 9HDZTER525; - INFOPLIST_FILE = iTunesDiscord/Info.plist; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/iTunesDiscord", + "$(PROJECT_DIR)/SwinsianDiscord", + ); + INFOPLIST_FILE = "$(SRCROOT)/SwinsianDiscord/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = pro.moyit.iTunesDiscord; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -290,7 +351,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - F87417F220D0750700614654 /* Build configuration list for PBXProject "iTunesDiscord" */ = { + F87417F220D0750700614654 /* Build configuration list for PBXProject "SwinsianDiscord" */ = { isa = XCConfigurationList; buildConfigurations = ( F874180620D0750700614654 /* Debug */, @@ -299,7 +360,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F874180820D0750700614654 /* Build configuration list for PBXNativeTarget "iTunesDiscord" */ = { + F874180820D0750700614654 /* Build configuration list for PBXNativeTarget "SwinsianDiscord" */ = { isa = XCConfigurationList; buildConfigurations = ( F874180920D0750700614654 /* Debug */, diff --git a/iTunesDiscord.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SwinsianDiscord.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 53% rename from iTunesDiscord.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to SwinsianDiscord.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 7f7acf9..3269266 100644 --- a/iTunesDiscord.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/SwinsianDiscord.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:/Users/takanashirikka/Desktop/iTunesDiscord/SwinsianDiscord.xcodeproj"> diff --git a/SwinsianDiscord.xcodeproj/xcuserdata/takanashirikka.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/SwinsianDiscord.xcodeproj/xcuserdata/takanashirikka.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..fe2b454 --- /dev/null +++ b/SwinsianDiscord.xcodeproj/xcuserdata/takanashirikka.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,5 @@ + + + diff --git a/iTunesDiscord.xcodeproj/xcuserdata/takanashirikka.xcuserdatad/xcschemes/xcschememanagement.plist b/SwinsianDiscord.xcodeproj/xcuserdata/takanashirikka.xcuserdatad/xcschemes/xcschememanagement.plist similarity index 76% rename from iTunesDiscord.xcodeproj/xcuserdata/takanashirikka.xcuserdatad/xcschemes/xcschememanagement.plist rename to SwinsianDiscord.xcodeproj/xcuserdata/takanashirikka.xcuserdatad/xcschemes/xcschememanagement.plist index 11e3eec..817ff18 100644 --- a/iTunesDiscord.xcodeproj/xcuserdata/takanashirikka.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/SwinsianDiscord.xcodeproj/xcuserdata/takanashirikka.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,6 +4,11 @@ SchemeUserState + SwinsianDiscord.xcscheme + + orderHint + 0 + iTunesDiscord.xcscheme orderHint diff --git a/iTunesDiscord/AppDelegate.h b/SwinsianDiscord/AppDelegate.h similarity index 91% rename from iTunesDiscord/AppDelegate.h rename to SwinsianDiscord/AppDelegate.h index 8426253..2b6b277 100644 --- a/iTunesDiscord/AppDelegate.h +++ b/SwinsianDiscord/AppDelegate.h @@ -1,6 +1,6 @@ // // AppDelegate.h -// iTunesDiscord +// SwinsianDiscord // // Created by 小鳥遊六花 on 6/12/18. // Copyright © 2018 Moy IT Solutions. All rights reserved. diff --git a/SwinsianDiscord/AppDelegate.m b/SwinsianDiscord/AppDelegate.m new file mode 100644 index 0000000..9b20b36 --- /dev/null +++ b/SwinsianDiscord/AppDelegate.m @@ -0,0 +1,136 @@ +// +// AppDelegate.m +// SwinsianDiscord +// +// Created by 小鳥遊六花 on 6/12/18. +// Copyright © 2018 Moy IT Solutions. All rights reserved. +// + +#import "AppDelegate.h" +#import "DiscordManager.h" +#import "PFAboutWindowController.h" + +@interface AppDelegate () +@property (weak) IBOutlet NSMenuItem *togglerichpresence; +@property (weak) IBOutlet NSMenu *statusMenu; +@property (strong) NSStatusItem *statusItem; +@property (strong) NSImage *statusImage; +@property (strong) DiscordManager *dm; +@property (strong) PFAboutWindowController *aboutWindowController; +@end + +@implementation AppDelegate + ++ (void)initialize +{ + //Create a Dictionary + NSMutableDictionary * defaultValues = [NSMutableDictionary dictionary]; + + // Defaults + defaultValues[@"startrichpresence"] = @NO; + //Register Dictionary + [[NSUserDefaults standardUserDefaults] + registerDefaults:defaultValues]; +} + +- (void) awakeFromNib { + + //Create the NSStatusBar and set its length + _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; + + //Allocates and loads the images into the application which will be used for our NSStatusItem + _statusImage = [NSImage imageNamed:@"menubaricon"]; + + //Yosemite Dark Menu Support + [_statusImage setTemplate:YES]; + + //Sets the images in our NSStatusItem + _statusItem.image = _statusImage; + + //Tells the NSStatusItem what menu to load + _statusItem.menu = _statusMenu; + + //Sets the tooptip for our item + [_statusItem setToolTip:NSLocalizedString(@"SwinsainDiscord",nil)]; + + //Enables highlighting + [_statusItem setHighlightMode:YES]; +} + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + // Insert code here to initialize your application + self.dm = [DiscordManager new]; + if ([NSUserDefaults.standardUserDefaults boolForKey:@"startrichpresence"]) { + [_dm startDiscordRPC]; + _togglerichpresence.title = @"Stop Rich Presence"; + } + + // Set Notification center + NSDistributedNotificationCenter *center = + [NSDistributedNotificationCenter defaultCenter]; + [center addObserver: self + selector: @selector(trackPlaying:) + name: @"com.swinsian.Swinsian-Track-Playing" + object: nil]; + [center addObserver: self + selector: @selector(trackPaused:) + name: @"com.swinsian.Swinsian-Track-Paused" + object: nil]; + [center addObserver: self + selector: @selector(trackStopped:) + name: @"com.swinsian.Swinsian-Track-Stopped" + object: nil]; +} + + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + // Insert code here to tear down your application +} + +- (IBAction)showabout:(id)sender { + // Properly show the about window in a menu item application + [NSApp activateIgnoringOtherApps:YES]; + if (!_aboutWindowController) { + _aboutWindowController = [PFAboutWindowController new]; + } + (self.aboutWindowController).appURL = [[NSURL alloc] initWithString:@"https://malupdaterosx.moe/hachidori/"]; + NSMutableString *copyrightstr = [NSMutableString new]; + NSDictionary *bundleDict = [NSBundle mainBundle].infoDictionary; + [copyrightstr appendFormat:@"%@",bundleDict[@"NSHumanReadableCopyright"]]; + (self.aboutWindowController).appCopyright = [[NSAttributedString alloc] initWithString:copyrightstr + attributes:@{ + NSForegroundColorAttributeName:[NSColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:1.0f], + NSFontAttributeName:[NSFont fontWithName:[NSFont systemFontOfSize:12.0f].familyName size:11]}]; + + [self.aboutWindowController showWindow:nil]; +} + +- (IBAction)togglerichpresence:(id)sender { + if (_dm.getStarted) { + [_dm shutdownDiscordRPC]; + _togglerichpresence.title = @"Start Rich Presence"; + } + else { + [_dm startDiscordRPC]; + _togglerichpresence.title = @"Stop Rich Presence"; + } +} +- (void)trackPlaying:(NSNotification *)myNotification { + if (_dm.getStarted) { + NSDictionary *userInfo = myNotification.userInfo; + [self.dm UpdatePresence:[NSString stringWithFormat:@"by %@ \n- %@",userInfo[@"artist"],userInfo[@"album"]] withDetails:userInfo[@"title"]]; + } + +} +- (void)trackPaused:(NSNotification *)myNotification { + if (_dm.getStarted) { + NSDictionary *userInfo = myNotification.userInfo; + [self.dm UpdatePresence:[NSString stringWithFormat:@"by %@ \n- %@",userInfo[@"artist"],userInfo[@"album"]] withDetails:userInfo[@"title"]]; + } +} +- (void)trackStopped:(NSNotification *)myNotification { + if (_dm.getStarted) { + [_dm removePresence]; + } +} +@end diff --git a/iTunesDiscord/Assets.xcassets/AppIcon.appiconset/Contents.json b/SwinsianDiscord/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from iTunesDiscord/Assets.xcassets/AppIcon.appiconset/Contents.json rename to SwinsianDiscord/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/SwinsianDiscord/Assets.xcassets/Contents.json b/SwinsianDiscord/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/SwinsianDiscord/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SwinsianDiscord/Assets.xcassets/menubaricon.imageset/Contents.json b/SwinsianDiscord/Assets.xcassets/menubaricon.imageset/Contents.json new file mode 100644 index 0000000..c6dc82c --- /dev/null +++ b/SwinsianDiscord/Assets.xcassets/menubaricon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "mac", + "filename" : "icons8-music_filled-1.png", + "scale" : "1x" + }, + { + "idiom" : "mac", + "filename" : "icons8-music_filled.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/SwinsianDiscord/Assets.xcassets/menubaricon.imageset/icons8-music_filled-1.png b/SwinsianDiscord/Assets.xcassets/menubaricon.imageset/icons8-music_filled-1.png new file mode 100644 index 0000000000000000000000000000000000000000..5bb468c8a638b97cb6ea4c53ddb2c177fff18b67 GIT binary patch literal 285 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%1|*NXY)uAIY)RhkE)4%caKYZ?lYt_aJY5_^ zJUZWAvGrnhlwf|A<%aWp00i_>zopr09XihIsgCw literal 0 HcmV?d00001 diff --git a/SwinsianDiscord/Assets.xcassets/menubaricon.imageset/icons8-music_filled.png b/SwinsianDiscord/Assets.xcassets/menubaricon.imageset/icons8-music_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..ef22187d65b09e2f4131a746f1bb7181e9c82695 GIT binary patch literal 458 zcmV;*0X6=KP)ci!@J3wluZ0iWqVic-h*1;G-exE) zNWaRaLY^a`f004pzFb0roCVf84&0YZNRUr~wGIOJRi=we#m>ij^-cZ@d5-V$h4_Hw zX4=f>p=>(1i*K^CGs172;GJyN8|5#NBuUb&znwv_6-^+N5C8xG07*qoM6N<$f=D;Y AO8@`> literal 0 HcmV?d00001 diff --git a/iTunesDiscord/Base.lproj/MainMenu.xib b/SwinsianDiscord/Base.lproj/MainMenu.xib similarity index 94% rename from iTunesDiscord/Base.lproj/MainMenu.xib rename to SwinsianDiscord/Base.lproj/MainMenu.xib index 380ed86..8936833 100644 --- a/iTunesDiscord/Base.lproj/MainMenu.xib +++ b/SwinsianDiscord/Base.lproj/MainMenu.xib @@ -1,7 +1,7 @@ - - + + - + @@ -10,20 +10,21 @@ - - + + - + + - + - + - + @@ -37,7 +38,7 @@ - + @@ -55,7 +56,7 @@ - + @@ -668,7 +669,7 @@ - + @@ -678,15 +679,37 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwinsianDiscord/Credits.rtf b/SwinsianDiscord/Credits.rtf new file mode 100644 index 0000000..b77ec47 --- /dev/null +++ b/SwinsianDiscord/Credits.rtf @@ -0,0 +1,43 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1561\cocoasubrtf200 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;\red56\green56\blue56;\red255\green255\blue255;} +{\*\expandedcolortbl;;\cssrgb\c28235\c28235\c28235;\cssrgb\c100000\c100000\c100000;} +\margl1440\margr1440\vieww9000\viewh8400\viewkind0 +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 + +\f0\fs24 \cf0 SwinsianDiscord is a basic utility that shares what you are playing in {\field{\*\fldinst{HYPERLINK "https://swinsian.com/support/about/"}}{\fldrslt Swinsain}} on Discord.\ +\ +Icons provided by {\field{\*\fldinst{HYPERLINK "https://icons8.com/"}}{\fldrslt Icons8}}.\ +\ + +\b Third Party Licenses: +\b0 \ +\ +DiscordRPC (MIT License)\ +Copyright 2017 Discord, Inc.\ +\ +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.\ +\ +PFAboutWindow (MIT License)\ +\pard\pardeftab720\partightenfactor0 +\cf2 \cb3 \expnd0\expndtw0\kerning0 +Copyright (c) 2015-2016 Perceval F\cb1 \ +\cb3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \'93Software\'94), 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:\cb1 \ +\cb3 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\cb1 \ +\cb3 THE SOFTWARE IS PROVIDED \'93AS IS\'94, 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.} \ No newline at end of file diff --git a/SwinsianDiscord/DiscordManager.h b/SwinsianDiscord/DiscordManager.h new file mode 100644 index 0000000..5b92441 --- /dev/null +++ b/SwinsianDiscord/DiscordManager.h @@ -0,0 +1,16 @@ +// +// DiscordManager.h +// Hachidori +// +// Created by 小鳥遊六花 on 1/31/18. +// + +#import + +@interface DiscordManager : NSObject +@property (getter=getStarted) bool discordrpcrunning; +- (void)startDiscordRPC; +- (void)shutdownDiscordRPC; +- (void)UpdatePresence:(NSString *)state withDetails:(NSString *)details; +- (void)removePresence; +@end diff --git a/SwinsianDiscord/DiscordManager.m b/SwinsianDiscord/DiscordManager.m new file mode 100644 index 0000000..31159e1 --- /dev/null +++ b/SwinsianDiscord/DiscordManager.m @@ -0,0 +1,88 @@ +// +// DiscordManager.m +// Hachidori +// +// Created by 小鳥遊六花 on 1/31/18. +// + +#import "DiscordManager.h" +#import + +static const char* APPLICATION_ID = "456219745302347776"; + +@implementation DiscordManager + +void InitDiscord() +{ + DiscordEventHandlers handlers; + memset(&handlers, 0, sizeof(handlers)); + handlers.ready = handleDiscordReady; + handlers.errored = handleDiscordError; + handlers.disconnected = handleDiscordDisconnected; + Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL); +} +static void handleDiscordReady(void) +{ + printf("\nDiscord: ready\n"); +} + +static void handleDiscordDisconnected(int errcode, const char* message) +{ + printf("\nDiscord: disconnected (%d: %s)\n", errcode, message); +} + +static void handleDiscordError(int errcode, const char* message) +{ + printf("\nDiscord: error (%d: %s)\n", errcode, message); +} + +- (void)startDiscordRPC { + InitDiscord(); + _discordrpcrunning = true; +} + +- (void)shutdownDiscordRPC { + Discord_Shutdown(); + _discordrpcrunning = false; +} + +- (void)UpdatePresence:(NSString *)state withDetails:(NSString *)details { + if ([self checkDiscordRunning]) { + Discord_ClearPresence(); + DiscordRichPresence discordPresence; + discordPresence.state = state.UTF8String; + discordPresence.details = details.UTF8String; + discordPresence.startTimestamp = 0; + discordPresence.largeImageKey = "default"; + discordPresence.smallImageKey = "default"; + discordPresence.largeImageText = ""; + discordPresence.smallImageText = ""; + discordPresence.partyId = NULL; + discordPresence.joinSecret = NULL; + discordPresence.spectateSecret = NULL; + discordPresence.matchSecret = NULL; + discordPresence.spectateSecret = NULL; + Discord_UpdatePresence(&discordPresence); + Discord_RunCallbacks(); + } +} + +- (void)removePresence { + if ([self checkDiscordRunning]) { + Discord_ClearPresence(); + Discord_RunCallbacks(); + } +} + +- (BOOL)checkDiscordRunning { + NSWorkspace *ws = [NSWorkspace sharedWorkspace]; + NSArray *runningApps = ws.runningApplications; + NSRunningApplication *a; + for (a in runningApps) { + if ([a.bundleIdentifier isEqualToString:@"com.hnc.Discord"]) { + return true; + } + } + return false; +} +@end diff --git a/SwinsianDiscord/DiscordRPC.framework/DiscordRPC b/SwinsianDiscord/DiscordRPC.framework/DiscordRPC new file mode 120000 index 0000000..1b19ef1 --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/DiscordRPC @@ -0,0 +1 @@ +Versions/Current/DiscordRPC \ No newline at end of file diff --git a/SwinsianDiscord/DiscordRPC.framework/Headers b/SwinsianDiscord/DiscordRPC.framework/Headers new file mode 120000 index 0000000..a177d2a --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/SwinsianDiscord/DiscordRPC.framework/Modules b/SwinsianDiscord/DiscordRPC.framework/Modules new file mode 120000 index 0000000..5736f31 --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Modules @@ -0,0 +1 @@ +Versions/Current/Modules \ No newline at end of file diff --git a/SwinsianDiscord/DiscordRPC.framework/Resources b/SwinsianDiscord/DiscordRPC.framework/Resources new file mode 120000 index 0000000..953ee36 --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/SwinsianDiscord/DiscordRPC.framework/Versions/A/DiscordRPC b/SwinsianDiscord/DiscordRPC.framework/Versions/A/DiscordRPC new file mode 100755 index 0000000000000000000000000000000000000000..1992e1094a0109bc77214c62dbbcb24f12ceedd6 GIT binary patch literal 90116 zcmd?S30PCd*FSy(L{Y(@qT-HQE3QO{f*2*T-Dq&%6%kn!Hxv@V9UG`zVybOzYg=vY z-rCyQZ#Tsa#ImSWT*tZGa;b0{eIu~{r%tP_dLJ*kUKNy%$zxM z=FFM7vvKV1uMZjug2G)8ga-Ke;uqLh5OTAd!6yi9@bd#QK7M@EOBFq&p%xImB6%gf@ZAqwtN z*y&{ePbWPpGd-MM{G8_~G)FEoIS!rf%G&u+z%{f8lR-Vq$!9R$}rZW4tk8 z)}#ELUCxtLlA!QV_#t{6?0Uz?C#EgTs1x9$^a{;9YL+*5Dg2Q9T$H#6^fHX;sS9U2 z;g8Y_d6pwCIxDa?TubIrUgP7_X3b5EUyy8^voK*n@?Yw=?W;UxPiH06hX0^eBIkn|aDYCnFSol&4zSQbHIq9XOReUOhcRiQKL4(@t5UDCVy zw8$)u#*gH};q+Dr;pvMKjC1-$re-9jr6-LY9nqH=iOjV0dHp6Pr)Q+X>h{ys!y^?v zT?8RJkUYqbX!^m;0?7@Z!Y&|O4R;eNZ^KuDFcU>39ie#d?-qn%2yeV82#Xd90(E=D z7d@8|iGCg7cSoF|4Ep$2*>ypvFX!4=?ckw&4o01_zW7p5&X$;Ovi2|g?nF1hd?->A zSRU`jLeJVT!SHzDHxoZfOS}rIZQ#DQqTjfs8OG!V{S2wI(i75`_KPMHO47;b7oBEW z2<3r<9w)PidU%i{`XTta=nIz+QV9J_8R`Azr_Q1u^&ow9eUp~L4_U2ac0R^kFKM^t?^qdw~yup==XbF>BL&D@{P==+@p2xbEfb8TlHXiE1)i_&8wWrjdh zr6t1CT5e=onP^=wNLBJc zSQd)?3}TDcmOFFE*&Bk0B9AZEAhPPK+>roLUb(L$rhdPJJ;X?^NK`vlTdYK0Ec!_) zDvCr4rxAM0*tjaDlZPtT3>Ko)RkpQ^mCuqurPNGsdPM8)`HX=P{`;yOf>1zk`F@Zf zNai_&u!KnmMDqbpV)-LhK%*CAMbT__B4APe`W!ie7#3Bj%Kd|2(gktNL6X}v-3(oP zoNe&~@MkXdktbq^rk6@k-XmZOo!Lqw})E^;BcpPt96sXIV38OR|Qb5L^>#+{WT!iSW^ z4Ju*X`~Dq=LtYGm@Ket6Bp6N<)vd0mrlt>-u~`0%RM4v3!zxlUoR!Zz4xxe-NGyNI z(OWpW7e^0qq1ORzce6uw#kKk0M6%la&wxjWRjx@zH(INmx(T}Rq@g`vGxbsxSJHTV ztZ*f*ASEnwW4bdJ+J?G>YE?KMI0{JD5C&_Xd5CGSzC+P0FPy?# z%spZ3inIplfh;(ayn`f`T*8**C@CR$R~K5Wl*&<#7N%PJ38Lr|4OYRRusMDz%_S5H zL|oKr)GzYXImCj=lZs~e6G~zUW4sxpU*wGpr^;Odg%>UE*&%`|_bupMX~ay@zE4Ms zrGzk$J609>n>^Hoc6Up-j}$SJtjAx- z>`8c(Oh-m$s6K2kBNNlH>Y#_I5sa%DiS12U8~PH$qE;{<-v)B)f-bjYuL(jcUxj>u{(GMT__8X8^D?62oFEOwG|&NK2>brt9LE zO(>T4%@%}|4ZEN)=kdF8-{EKqw^p~QA33R)9>iMrk*(5|K1~k(1if`1nJl>w>2=a; zWTo_$A)>h$HtK;WeJ3kZV7-cVD00e8jj29v*D#K#``}q`sQ1+7hn5s8mFT5odg&DP zVC#^RK}z&NQKSezgOuS9HypnZ`~vaoVUU)Kr4geM59N-S9GiQ|)N6kagyaG+fWA)h z6idUQ84BP($~znM<@Z1ttZn7!?Hqj{PQe;N=K)HFEXrT4#QT0_!Qz*2tZPh^^Q!{ZVuy{bBD@m+Wl&YDak$ZQr1HpW-k{s`! zDfcrCkb7e)_J&$IzC_2}(y&;yZ&o{bMOJnGkRQnqf&x>eG1MZ2(ij@jqe@~zN@K!NL-5AkAU%Eqeko$KD2tuN-fveUxc2> ze9cEM{f;;% zXTX3LslLRpu-bpgZ+gkAn`l8Si|MRh%JkGr4Rx02K#aH&vO@)<3C)|j3Jpr@gzG6U zvX+)fsP_phkAJ2-0hA}zql~%&ZIfDW8R?_9ObpRWBmE#SA6J9b_cCzhZ=l2(&hImQ!Y8=pADl(b7P)j0B(5#2TiQMmHtNR6NGwNEftFIe5uT;0Xk-CRA**uz|%V$#IfwxVR_>gC3c=4zv{lev1K z$v@842g%jtaW)-_7iU`u&5yICg0(o?eqiEkFGGQtUYLgpsuv2mg+o!ZLl&A6M60L} zrKZ_g3X@5TfEx?A%{K)i;LZZ>^G#hvbD@VQHGp~X-$&9Fr8Dv|A8uAf2n%+`ygE=o zs$L^~(1egZ;*1Jmq8q}>s=2^LHwdAEDI~fc2_#ExCyp-UXo1i)0V6j8n(@teHJWDE z@tSUmsN>aS8c@e;vMJzkymqb6Yf5Q&)5gNzaBRN?cN<(iTy_Z z$o_W5m}cQJ;?{c`@s~mTeHG8xe}v zXA_AnL$Hv{hQYG3P?tTliz=5oX}zOVw3-Sr4#W5hnsGstZpi~6e)DqZN0exZD>DS# z6CH=i?iI=YrlopwiGNhiC8LUI7L5>0Xm@sRS1^S&!%_?OyCyDS50avv+CCJ{>_5XBxUYcZAeqZv#TuXA)HM}JJ{BZRgBZTDaw z0G3$fH34~z@qvrHOvr07@@huSzB;a+%-Hzfe9ccUoe`zmFuWp-lb5SI87t__t03!6`JUM|D@{-GjMY^J$svdO@^6eyXv7c|8}&E`fY~*u!F0e& zqYk1Tgm`wkB#-7d&8KlZx zODu4i{HMgCP|8@8G8Uzb;-ysOYA~*-W3vFmUB;#=w>=R5LsnI}XD|k~m<~EhRQo*5 zHN8!%B-6qFBG-?p8&&RV(9I6%qRM#z9*h}S2j&5Z+f$wFV{(HOq85i1B-mrksL;VY zf2z*8%~^HkszHP$Gkyb|zn_JaR!!Oxa=br*Q_zx>dgn9%oLQllK%W>LVyi;E)&iB3 zhYb{Va|(Spgo5EM~|coZV-AB&~+n_$G3)Qs@&amHScN-Pc?p| zHF*3VZVer>L*gE83#nXqIb|g1huea)3Dl`oVs)+dfBQdFDbq%#PSiGgn==0E+Vr?K zkTVXdZVSQ7sXI?oksj6^R@p5PCA%ihG_6jB5}R=qy6aLQOb2OOW>Q6KdK<~{o)4!W znpDU%g#g+rR`VKfj=oI_5J2dOgm!9JLsBLQk;{4HN-PP0mOs%7rpN(^{1~UKyGPD+w}FJyI`y^NDv(p2qGS4FA6SC`wqA--yfzx?vcZT>y+=bgmQ zqsi{1B-!^gs}AX;ucPvPBBcsmlt}3};{0Ej_dJZ|sdfx*q*_}FpWYg-5O*I2Q^c3S zI!z&lp3m$fT7AFNONBbszFU!sqoOrh7nMgbd3wU$4b33_nj^x7&Kff7&^Q8&)3 z3BA2wn+h8do`Y|0wIf!lwkM-zF(0jpyrDN2x~XCC3jxN^LM*zfG4;y67rOchVqa98 zKnYa)ZleH>W9sik#c9gr7(>n@v=AsHn z`Ib)Av*?y;aYIM~2kdCyrR+Y-tC!*B9qOxu8qo-0kY+GIu~LacaqT7!RuU6+@V$C#s(KO)k!F%WcJh!Hq#F{SegeUkaNqtR9L5Uje=+ zb@`JN5C^Z)jL!D!T&yrSBv~yQu-B?4y8PBGLZcIQ^7PI-tAr9jY%<6;+|D zDpFw7yw%hxr_dOmz4A;SnuJj07NYRto%8Y(tgYF0BA`>;)vdXE7&2bHQfGaNDwt|R zdjt^Gx+f6aN#4kS3N3x*tIT)szNMPs9Zv)5O}5`bqmKMbMBZHL9Pz$}8rR+Vkq7Shn%jh&Yxh__g*@hNtOB7-_uv$C zBCb3*p*J{sGe`eG=yXC~9?gpU6i2V*=)HvgiqIuM+r8|K?Vk1q{Bw>h9wc5y1`i)u zFgMKd;Y+SB;!QNQ6xAu^x~YP2TeZ5OUK*muw>r_$TcUgDEi?S^iLNiY z^DsC>-8_)5!|GxUE!P8JTO?limiEzGBfDXe%U!4Xay&d%P3cKpf2i*)( zSG@(?<(8{*VknJujDqGM^Axr{U?f($tPl7m`>siq`wpZF6tKJGG7 zL`s$21+-nQa8?kE-uP0B#H?r-?2LUOhUM8I)A^S+pB`#LlW(fr1j-qmIvA~^y^?6t z*F2wGk&4#*M1)2P^h zYh*^fm`GAWFj~L@j%)o0(dSWgiqZs8?BBzBJdO29SSmAfyo=!!lmaLBjU~8t97mU6 z3eUqT-TnzP)6m}$)TT^M2; zLElFJG^zy*0svfmgM7hcIH!|MbZFEhOQ5sCU}=DXthz8pwa?23)*w<*7Aciy7hzn4 zK^?kudj}TYpIxJ*aJk)EmtFL_j)JYsg&&Nt8hmJiRqr^UGvD=C@s+&=n<#CHNm4XS zG|EXKbxq+z&dL`lOdx;2DR>DQEk78+ZIk)I>dfXaGgNJ=+^>+H&GD{z;An`2z}B^x zoNd5JH;ZfyvdatYSwN#FtB1BY(O%i*mG-bT=zN=@C*MfyaN5TCl)8C#Io7mBgMG9G z7)!1#V+iGy{nOCw^6bB2gLt-ogKZg}{mans?4w9X)6Szj!}9`}ZH!~W*G%Pg@CwyI zDcF&fC^)KU3rBOuyOPlOniS+;h7-%!n?lr7BF){y`l#}eW(*(`(S&y>YVAx7ld7hQ z3wMAkk2gSs+ZpUH;nQ~5BcsO(W<$~ZbG6>ml!$tZmNA~9)uXK#rdZNZZ&~7rZzfo> z+i0w^KbV0vX-RQew8&SydKClM5_vyDLttQ{MOmL`17(tU@*MVRr<7PK%O*N`l)2Mljdc<{ZXDP4p? zRcTC9rH4r4J3LT&T7+wKgODUp`$u9m9DO>fV0nlr)rh@DVCkzywWJeST@soGWkEJ2k(dL18WDFWm?%)AFY^o+ zC$bFJQ-;qXg6vBfTD1lS*KAB+=vNRbn1psK{~@BTpo#TXS(J}kWogaHH|ONNiF`hh z|ANxH=D2BwYP3!|t*2Sed-}bIfyd!UDUE3AjuB{;u}>7HBZ@95Md=tVY@NoS$k273 zr@qnG1_=|j5=}}Qq>FMf=-NFDo#%N*N*NxJ5{8!#S;xoA?wiY~i~mG+(O|1ic5%rh z6^({h);NT??9Rq8+4(TIX7WrXyBP>^+3f=QKP$UO{jy_af|dO{^gE9&dnxGt+p^P& z`t7q#9u-vq!6H(LHkp*D9_ACz=AN=aB{y_XucCIg)*geWFlaI96@6VBI%{CMT^#xIJ0OzWsxpAU}8(< z$qb7uuLHB5VeUFk>ZKnD8OG=r1CvWxTU$Osj&woiz`l}FxjjSY7E+-s-4D&H#bL~b z7$C{scPO@HFO`A&sIbhZT}|XHnn31sSOWr_sp3dtxdf;VKp8CCSr*cDVhn3yrShAI zKnwa7jRzvI)s`rAVU)U@jsCu4(jzVrF9H^Ostr~QWV)s0Ru#a|66p)hcBA>btB8X%QI)aa(O}Vb zWR4~V#DRxek#KTZE+m|WK(p#%@CAUAB1jh;Eju7ZZ0@f{6KE-jh%0rBEA?@vx)38v z-H)Zl<`bm8kV>f?#+e4`3Up2G3cjQw^jTSGEdK&hw9t-4EefbEln%*X0WVs%ki=|H zqmYQTj>W>DK>OQFw8R#G^8K1G)y1`^AY!5`;yH@w>x#IrUc_MMdZ$BvPRChsW@l^* z68%PI}$_2j+VZ_p-LSsn@}X|1PNngm6vEVi6$Qw#dmx?T%X1A5#;0S(_t-q zP-%MUCbL}151dJ*C>gcdIO+c|nzkIJ0N+ zB*b;@6RNdWv}baYC!2lHNk^rVNFU(Jzf!d3?!@*_(Q2(AN7B77O8Ol%-qBUySB4+? zcZ#LDv$V~-R98tKekHm~;Ats7hOr54G3@NpYEY_VB*HPQjjeI zWmE{ZnG+hw5-<)5sHMt{86gIcd)D#7bDzxxlGVi&p0-oHvdaFjHchCG?kp@ajfarN`gr^ zgVXeTh~`;uS1h7g+=>Mm&oB89541P0(5S>lSyO+6dbmPZ(~1dng<2sL!aCm%P*c=2&^1dPZj2JrvjG5mi3-azu>I+6vfzFODge<0^S z9P!q%c(pn#Ymht30}JLL?#w42UTE{2TAiY%rQ`XGs+SMa3i2nY6X+DhmCv;U2}`{A zP<_%YvJ-&nya=`s#qdy+%T4M>*-xBXImLL^hPZTICXKbkjCM(f>U**)MD;zB)%SDY zi`BOT&!cT)Da%W<4om7z9ts*#Ij-=WsG-RX5eF4J8uFT77f%-47WQjKvGJx_*?jKG5#pJi_&4ZtMI&yk#*+~xD8~I=mp_QFxk;6_iU>_;= z46QjxeFZub+N>*khT-EQz#eGFN+UK$^4d5K%65MM>?7$l!QQNP`j>(&5Fctov00&Q zyy6+JKf-VKrdYo8!jvyXW2_(G(HQGrlHbWXV|ENN3+=Oo)@=tGhYeIcoFB^;<)j2& z4k@aVoAXVL0I+H{PsON#S^_0+4An7MR~E7v!z%e>)Gxg9c2H-X35Gb*0XdHePN{TK z#5oUB6MX*ksFlg@wM@`=2!@*v=*&;A>@dOj1j~ty2jyWz1$mBU#9@wM#X!6lz^*S3 zI&t$T1=2!JL2y!N`Y;8O=RZ%XHwR)zCWO-&W0Xz(zFp8)YQ3`9W zWh0R&9mb>u+J-v~1<*DyO2gQukeZIwdX}93*(gb7B-kiPc8-#=8HhLT4SZZgAg;>a zFn~shX?wI5g7a~37!`{H_{ik#x|B35SxKUI;#p^q!wK!1=zI7l8aP=Lk}9hzc_+oG zYx!BAA(xTNon#rIL>B3Sd{0w<08K>n&hcxiGmrn}nyKQQG}Qq5une}e#_EIDQ+Eg% zCg+D(a(F$l@y+wx6pfis2Pd(^>~~<8dL%Bg95h=VB{3QiX-MZrqT#fuaa5s(&XM?> zHMA1eqgIrUsiDRYC~Bw|b>4qhL#+_UjtM!Jb!K(ctsY@kNBe_811d8KZrw*t{W|)Z zdO76szg$OSd4KDyqhmM>_W07J<_t^9Nb(Wm7f45=h}n>`vpm!^X(Ta(XHO*Mi(D@$OR~)aC>!T^ ziVpHZBmQorHeO9=rHMX^S*xwA9jQ@8!mhHB+6=_9x3&A(TT$Ef=OZhYGu>5!4a9Zh zw@LN#V~TPs5TsG&e7GsD^HKGV8?kw9c4})M(h4$0g(&NCPc~MSTau_s0m8IExF?vz zj%l3y3KKmun(&pP(LN=L<*sR7Mh&YBV3N*q_!$?nWeuZf8GVC}Uuv_nszC({PD?!v z7M+i=g+8=8<4HYRD;wjZD-n9z+YrdN>VV>3y^U`F8l=ZgFU&#s=<`WQ!Z$Ga<`X^KzY2DH;K{{OSz;Rxeb^NgO@^DalIU=3LuMdXEihp z%rhuy%cag-*hputZXun{IFwtss-3tAKsHyLKVEq>uQdpYjxsT{Z1we$hA_D)r40j- ztI{@}XC4++ZNjOYgIHyif9&I2{}C-i)m8P@EjE_&b&Gl$&-A??X{t%Yri2?+g2FTd z>xKS&_2VyXXXOqBTSq*_Rdp6~yemjmuTrIIkvCNJ3yzN9=yQa&6S^ZuzsS+uIQj~q zuMyhO`)@e^uH^BY_XOt(9I^rD|G;#dlj$#cw%b{k>)npBt)Xo5kS&;giKCz4=;sK1 zm(Z!TT(O%8>edlyR=GQ~=47%yr9l|moPtGAzADJh;CBKE5KrU8)tvaYC=maKhCEYXD zGSw$G^mayTy)^<`J%+(XxlgZ$!{$~(H*ul!+GXFvrj+fRgx)fhkJ{}72lkoZq= zSS_wBl0Amydtm?ZG%zt0#?bu{0^vIIB~?Vehlk!e0#Sxj6nn2o)t6l&^4ojme6Pyw zMVmG(oxreYiGV;Nm<^}({}Dcsd9c_OIB;hOtxiAmh|Fk10GatNw2)fwtS`S;f}Pr- znd?ue`((|w(qJ`xn_HEo#6~cCW6>IQx+L7u=gxWhQZA8EVS+xi#E3f#V!x%SQA;|w zN%zd~%5NiDS0S@yK6xF}pOe z>y&FB1G~_D-ti<+Q7%;nREUbgH4Yhlo9d}e$QcVj=fkmy$+uY^4$YU?tbna?I4b{8 zs@Cp;U%I<%O6#wVi>gl!fli;u)FK~Hm#QNJ3Js};%;(!h=!-I|{s37P4R5RL8_|Ea=IC<}yU^ZbJ|c&%6dyh`CT?R(vV!mkD4yH1)Xf(;rGI{u+mH z=#?y+ZRwVvBZ(efb(k_%IcC^rvRQ+4u~RU3`MA zw^(8>Iv(R$f)Jy+d@mnR(t$nhbz=Z`g!4Uo@}fO(s#j?%1RG`t_QF32ZM3QJG8YBr zHFb8U(t(GBpISq*52!Yze#ZrG??1}w(N0XQ;ZnceeV!vJpvGYJY9yvs*TM`|-=Xu) zBn4C$tih=64%dg^a;ALWX?YW!%=!A|O$2iz@V1ZbN# zvO&H_L|$}5o%tvL9v$HAQu1HC<_0RMxk@ku4@#oS**Sgn#%(6n>Cn%XGZg;qFSr1 z)fXv$?!YFtn4Rv|CSi6~xeEDt^1luKPz5a++lx`4Fdm#XCP? zTR>Ye^#Ca$EsF&dOUHHlsa+XTZ?dXXeR7PN7jInw2lPe1p-uh5+f)(fO71?dFe#vj zwW;q2q%X3gO%0v98dX5{3fV2ORjSy2SI7e5zN6tF{Lv;ey1$IhYZ0+uEvRhX8`jEb0z8gsZbSDM100`lIM0E)P;Y}>5g5}L}Q$Ae}f(V&Bfjf(#Gan`*iS=O!k7G&P zIFUmXLq`xVH)c){5b60Ek=ONVHmN#vX3YR%>fc zMXR-MH&KDyzG+VmqCz<(4C7dgaBYwkO8Y56U?ZF6dkq3Rpvye4A0gm{8MlyHDx z=9gU{FHt*YxV5Ru%uYPASWQIjk4wQv> zv7X@&`r`g#>aqIl9y6q#;BEUV+V-KOfJ1uKCxwt~pVTr+6eShw0uI+7Do`0?Q|&y_ zuSoPw62-83e}PHmVfpR#d2uI4Slc&(3g@?Pk{5R>zx~p@xNk-4;GyPY0IBlgP9xeF zoO?sG=7<)V->pex{@^hwUBpSE`tPlMCJ!Kj>`A-)LAFP?}f-7I&6^?NHD&h z80n>su&(GMfP8rVU{!Q}?P26!XuHfQ{?aHB>X*U{@qw?gAL?D>Y~wLU;~co~ z-LII6=uDe8o70sZ*u0M6?os*Fj}C{uF&!emBX2y1>7tDGFb>M4xE28h=MX;PRGCGQ~!*}K!e4&^vPNXkg= zXt?tPcUp7D6Aq5|NgXD!a0ih)q1@@oop#*uA_o`E5Rdhck6ICM3T)%y(hV&z}kG})c45!hhz_&!P+BcVw5b|{{9$DIdQD(NWF^(>bf zA-#EdmSAFGss$+`ODqK~aL|@GpyS5)B=;r1Mfoa7)B5ljst2|$5@eCT7vzuNG6^{1Y|CKUITtvEgRRAumP;r- zbTiAm%+uYJZfTjUcNffe8)nXClYBQ+MVb+SV5o;w@mgl-*Y#GLIiI`qqJ4+kbhOF!ib|W%B7SAYF=g0 zzWWw2uXzs+-Fc^7LXnRlGHOowCg{DdFyf4>b_T&e1suuyJCn0p^0bkJ+2TyGn5PIr zirxfY%~SZ)WvG3dWf%>IWvHZRGZ0ORJ4Ja0atb&Wti6hP30cc2oSwy5##Lq7#_u@O zmQ#c-&aA87Wz15-PYLe96TVX~;qlsp8z_RUE!0W*4yJu+4{8pS7#H8so=<%Dtc`I; z$h1SZhuYnZNL5kyEJVb9H~AwZgAgV-=?=i$K|^fTZZzEL)fFd6cj&E#_Wtnf)LYlJ z&q8u`ycGUy?k_{rcT^CiIiPgI;oTL4Ey@mz57RT|m8aZ|RBhKH!Q)UwaaG#^Tu|ez zw(D;&Dc*p?q`03V`w?esMc#Ejrv4!`uKO=g%-`A&TNmUVaO=0aEJ7Y8BrIxYsMri- ziPA>+8MVmSqz7Mv!VpJ7S?poVY#N9wNvY*dU+#3`jt?B$Cy*eq?S;hm2O;rM0+_Xp zfh5}g#uh}o6P20Jbi4=~lbtEf5oj|{QAzg(BSnw86h{bs59qoSy9hLwQt%$(L!_c? zz~mWn?+lRwAssX85S2Xstv>XbG*5L}tdjLF6 z>rf=Ox>#!)u17L%#CSNIn}(EqooyhyiQG#O@8R6Hvy|Q`|CD>tFlX*N;c?|Y7^g2p zW|oDvpVg$4Q#N!c^OuyMtMqsW!nhh>}^RE>1F6lCzUC zK>HaAzzx>*x%v>cf8?H=$rd@6XUQFjS?Yu?v?8vH!Qut#gl)ef%(j0cQVroKQbAoL z`3U^fQ_JYoYVXIZKl4uY4t=;8hhFe=oK!%ctX%M>J2uSE9p}#YgQXY&FBED#oVa31 z8~hrAbUkT)E7x_nJpjw|#U~uyhPzaOf;*6!?pSQf?pUlb{Uj=??KpCzyuZbruETX| zoVCN(;ouuhxg}U-?Qsv7L3Z1@^BQ;7b0?cSi|DZ~v$0l# zn?fut1Si?UyLy}%Zt*%M9mVtsPI`!qO7zyJn6V)mkEGOFbKDs2_;2Q)6ylw8Vu7tq z$_G=^+`bSy9}{>L0ZpZVA{B- zvKh?@z5Au=H{G}(-;gq zqd{+6D(?juv<|(cle$n(N4&l_!dhOC*8)!NNw`?ywYQ)qgm4*t;Ta=yAgYemS+?|18bZ(DKha(?DAyv*d ztYR4e=l@_ytbV|W)^HKg{Ncs91iHQ%m*?_ZjXPW$f~!9*%05%6*<+xg0R?lGC$5cd zfr^t x@QBkrbCt!o3EoCpX>(W-4W3Jq}s?25A?vXeW?+mk`bqWu9WO1WtjZ~X{xwp#A#%4kA=WHjlKj4Fs7OYXnTh{(_+XwI*wc6Lj3&d>0% z%z`L0lGbs8(8-~L4dB67gH_Wg2$?p9kbuwKPGIX^!Nt-|V(DPxhs*xTf5y@lA|r+# zR^=>%*;9QLfg2BT<@N%yfjB3Hd~PfuA)JSdqXUr7bmRkQHHXSY6tv{BNMX4IHF`U* z>Q&{w}~`Om^MSiT@K`p~0BAD!x}Xky#c z05{zE*xhmIHy7JeYLGt(vp`}!LSkjoTuR_QPB7hn!@))Zzr(?~B3Q+Z+NzujgvZ6$ zjOQvyVLe5gNSbBcyMsgJ-3S^iJW?2gs&d{%LU0zt1-c0#Ts=er-NX{1^Ka>c6?nM` zC$*JBuen$Es^s%#tJGVb0W(OUPGaXvuuY9L7NtL>qPX|SOKc* zuLD_seU$p^ni2);U1z+4L9h}u7?XdJGFkWTbTJu4Op+-8w9v_97SmT22}}}Hl~c@A zl9xT_OQw=wvT-ewZv*0F^6Gzs$%2=`B!;{K?)*`I&yIvusT~&O)59^KtZt}U8%a2J za+tn`v?+iyHSP^KD_B>GV7`KHp(~B>Ar&>qU5HiOSLHMzczy#MRF2w(ni6z*k>03p zER33KI%Rt4@*|r%atcN1xasn+lE|J91bdfKWs4V)igXl*y}Farp(P+TNL0|3M4_aZ zRZ?CIbw|Gu+0Z|y-1vn0M4lHEYB(w+9TieD2~jbCGPJ-A&{&o>{WRPf+1DQLBA zRc;4xLR*xv)rg54{pNWn%(s0}7i+mmSp)>2@-+%#3qJ+J_7b$74$_507$Dldfy!>xsEk-zuXX{H7*`(KJ-X)@?PH-E5=>~D-+Z$1%*1eTPg9#(G8!}qP zGm<$}zCb|>j}*3-pvEViY@?1AgM_go%OssL$t?tMI)El0`w)1?uq>2*Ak0)1H%qaG z)bXDi(m7{C@_h*^u!ppQis8d^fb@-H*+u1p@R4A3E$Gpa4f#a}n+Zpthyqfx~Qsr}$oTm+9-IBb4Pp7SR0U`nHL3B`Du#dv`Y z9;B8N5s~J}AWH36o=EOSq^;VYslQ%<*!G~E5QTtmVr9DlFe{aqc-FS0gsFv|;S|^C zhxzV_)$*TjI2@Q6snO8P$ZfnaL@kEi`BDEYv48wf!Uh94_`iyftNAFvHJHmyt^zsCXTbvVErb=S+-FD#YYx&#u-^PWiec+Q9BMK_9*UNa z5dW7BXPN2N9^&Z996is4-o?=$a&#z18(e5MGO=pcadcab?&Cr)M{1ek*az5y?fvP* ziM_YIr@e>$3D||&5^8Eo*otC4`Iku_u5s_70--!qVX_qhMn^Ayg5gEoD45^g&CXuj z(Mx}d<}hJZto#k)U_2@xWGlsADxd2m3%C3fgiKWgrz90&a?_Ehr_1 z`Ju;ETM9AQue*4geNE5_k$VQ~XLGWl;#TtpIBW~%E&&oCZS9E`BU&?N0yz`N0s*gG z^rFxC?o@iKc`JgLJ)p!C%44BT2(cM@8iGSYi6BI2a$#cGr@u3Q^d9`OBV&L*Y?Y6( zm5AeivNw}eNA!$HYx)I;dax~vZg~e=PP&vHvo)4@{5zWOkqmiw^V;BF*S6(k%!ymfMp)y}S++njQ93QBDe$Ql;fcG_MC*hKRe&4r>PRUaM)z*)qa69Y8@LX< z(NR%K_mpt!5K2=e&jyAr=&L)wgC0SC1J)gP-$ys4@ngsO*XKuj;*JsLCjzcKsk=x2 zBdB23*76>?vip~G#J^9Wt66?idK23a?IdK+|3mO z8rO9?T@cvGU|m3yrDO$X0jCd(6O6q$kj@UtTaXH7374}M`%`otbs(aOVR03vbG(%G z?+ij)E@JFzK4fbI-un*{H{`%K(lwK;K&C)sK4J|R@ zKF|wtKLpUo3Eqvop~c2Fw40A=zC_A!dT}R? z$T-M7kqqcx7^j+T9DN<3g4TqlD~Px~Knt^U@a#pgHJvf!FjzGg^NFDg)V!`!|76vO zKsX^kGKls;KZUT*fU|&d2?uC#0V*yr0_`t2U~o79ze73&Gp_==7ypn&UPLHK*#?8k z5@lPwF%_n1K|{<^r4O9PY(n4}EkPG{~k=Z-sfey3#=NqL!^QYGPbf4XBZ zAYmNen>!u2)08_7p6CW&f|-JuPn43)5h}qNlSrIx7`#-QB}(fvj78*^)Lp5P%y1j>$B3#`eQgr3zFc?kT&Xv#jXmv5%$`3c8@8`TY;_ zL_{-KyKm@9PM>Mm7KFU^%|Q~>Bsc~Dci7m4s@*tMx*rZyqjDWto>4rx zmt2aXnM1moydNUu%=DaG0Cb(c*vlN<3!a4e?!Je;_px^&dmmu$gX~?x-ev54h`o=n z_fht)VDA&`eTuzLv-er{{*JwWVDIznUCG`T+4~ZE|H9rfdtYVmYwZ0Sd*5L1TkQQi zdsne{HGAJ>??2i50ecHQs6=k;?ZMs+*xQr68?(0;dpBioANFp}-oEVJioN~VyDfXS zXYY>e?a$s_@Fu0}5c&i(Wt^jX0Nf38;sCV$g624f{uP%EOU8~lo5^>Ae0#}PM!tNC z2X%JL*+IU~$#^AvKKgo{GlzWL$d^gJw&Z)7d_LrZSOi-G@^vQPpSU$^&H(b=f)9H4 zYVMlsY{8w*ItjvbdeK83OkfIBH$obIZSl*Z0KNc(qxi9hrV~8jW0-gZUPE9BMZ1px zmPGjz0tm)HJlZ5@fJLJh+5mqXueNmeM&HTe#=>Q$lz0RjUrGWBC3LS34{(1n24P}> zKJkQ)@S}_lQ-EIG$U~3g)y;x?+g0#UvFOH1R+4RS*~9Ya0xu=k;pfV`7}C*=0)!Ga zql7E)!w^AGQGoKLG~N`Tjhqx{LIHXOz-3RJoT+Mv_#)!^GWx&9a2HY_pdlB=d_CAjCfJ?lgzQK$VPemDUvyhZ^M0!B+hJOyszhY3*OWeRjbNeyt{qQEKy z2$Mwt>dX4T&7eTF6O%@PKq$oE8(JTwSwMj)2uy~%i~^UOn8g%mg9e1PK4CZo z3(C6?YKn4Xm;YI4f9>=T2q&w>npxBud;aU7hY%3^Gh9Bi=qQFJ`BJpFGCjcR3 zWzYXPI(NX2iC7?h?3622BPpo@G8YcwNBmqNOrumwXng=7rLqx*?t><_ zZ%`l?FBM!CZ6gA7Mu0s`=b*R({V*J=(=F)SW8si9rR+&BddTC8Um^vD;5V2&^f+EE zS%ZQV69*5QFftmURI+6J0`X%HF-1Tfy>+zdk15Si!koqrlQjHk1mc9z5~eu< zE^5P^7`jqc7=b`GT=pDBfCL>um3`SG9sTAmg-+c1W<9Ky9Y{X6~k8p@y^pJ<5UL+=i!2evhl@y>UYb)H- z6v##@YP&yBAQ-K>J6v}A5Owbq_?$2&5Fk7J9R)Hyc;GAr?t{TzaQ{VtF=)V4QkG^4 z0wWRFNtpK#m;m=n3iv?j2*WtojQ|yr5hz695(4ab8>EQZc}nvUeC?-jFxwgUhyqVL1KTL@4qh|ivd2~LSY_r9d79%lhk`Nq1(Sy!SKP5Y zEC}SG?r9zYsBa*~9(e*IptMmB0|`ct3!cq_s1j|M4@O#U#eaM$<#$^kyxJ1^=&o-5APQ>lV)4 zDclWT&)vLY?#``dZgx&zoq(sJc_MRj&b-9jvVIdEyQE<)IPHg{6+k_e$}j~@FCstRa&Bov*44;o-Fo!yY4MNJ zxcC3+_wD1O+;SdVT6|85TGPB!zbj7`Ms+^(%iT)#?@?FRZ4I3=Bp|xm4$r*n*7)ew z3zc7dvGe)p&E?Tuetx$s+I_{@tFKl(h<+-zG(BNhP|VN%OOMqwpA*wAbyCF6*005U z`bMjsCy$0-jx%7a;dyelp(^Q$`juOCFi z%41VXt~b-aHa+6TSM#FvB|+n_z0+WYe)`tWKYKFgQ+-C+8(Y;qDAbbsY8+uj+iua%gxx8H6s@6^jP@D5B&_0w+~#pWJ@%(+;O|Zj7ysg z?KUrZ$JF7N;n{?gwW;auv47MIwYiT9iA}Fw|MS6F3u9yMg*M%N|IOGo%gT-i>^&3v z`ldnA#*e&5CcS>?!!paTk=oK9PZ#vf9O)JM!n)MEAB}u3;>SB{+Fu;`?8Z~3H{WVC z>Wf|a78ZkHR5RbE*G_-3W>l+}o4t3W-It@LIlisf|E7J^%lmULeihYu^y+?3e&7A2 z38Ulh-57KI&-~HtwhVZ_<@v(Vvm!mJUupTr=;afXUn-{c9J63TakHPYW{xTA89cGg zOB=^5(-eJpVNdy(`9+ftH)$n|Exp-y-!tuk$A*4fupqrl>e$jL0gY7iULV`x?5h||zCcNo4^M~tWpPq22W7f$Juk4;Mv_-F^!;5ZCXnIzoeQ>1v#O5Q-N3`M7CLZ=V z@%j^<&rW=>u_PvN_}3G)gLY2Yv-$4Clx{6{{_#fOr0ic?Kap`LX;P)U<+|$r%ahi9 zdf<|~8j z%xZS8>6D@#ee74KM@+eyUj0t@;H)Xz*Dq1^(|tVUNSke`d*zE$hHmvec3szwqjtrmS0VMt&$?+Z{c>>J@XbZjwEkbp)7`74#lHV@m*AXU(*yNKU)b6|Vfu>ib-TsYFHD~{ zw)xez7YC9eLhOr8fR+zMaZP=Q*q4` zE;d-Owb6_ZSHyOU^a!7E`umxECMbnzn$%_QOt+V&Gd#`J`&l|%Px%AQR) zJ$-%eh(RR@UcsN;IaziuVXaWHEGJGq>jNR@<;$NZ&pIVsc699W>Z~09>+$3MsF?MR ze0AXa{gsKw-n<+&cJ-jdqTEmBJ->cYV*lB%MrVEYcH)EZVUs6To=bdvPQV>aZ=a-+ z1GkM410$2NJ_{V{8?`iPV&$HcDKCDKG{9>><(<7hCq=A}IBVMOm+YG{e&orsBa^R{ z{_c|#V@{T9)Gd?S>`YF`S~YcFr{9vbP1aYRKG`M3>*&9}Jd!slW&N7?OOCnLl$ss8 zf4ea2KuXA#xmRDhSC#T~r1J90g5I-F4e$ENGruRymO5OU`me<=&bD`Z?`FrQM`pLk z^qh1uPci4r#zVb5?V35>gYFMJlss>a82Mnxab=H|PepigCHwS&=eZhZQ>gmX>b1dU4Q@tjZ4f{CNckcRm%TBh*(a+6j zFh%sgyn3$Z+XMRqU;k`wV{2}R!Y+<5otqVApM1y1bo`=-os^LEecuUJ44p?$7U<@#>(7o%=7aj~Z6~;Rh)Te);*i-L1xNUa;tBnC6+!k1y~#kbmIQ&J7l> zPwx}daa-8JmuLTZVeIh53sAkM9@uUb*MDjTe2=mTaEab5Oge zw2kHlUwF=1mZojqZE@7f9cf2eE%@fe&6m?aSGC!ljSlZ>w$~Q7R#xDKv=gFrto_=@Ii|wyBG5VHO{kYhq zGx{wvPB?nPWPEMY@vCcMJ~Spe4)p)j>nG#j{TZu&9M#g)L$*%tIBxusek03%yF6Q3vO8hf+zUnf zm$Y2D?#*83ZZDa-xu0dpu^yR=%=h|?caP6}qW|8_zO$as+_CCf@ymnCGN-?GB{*FF zAhYIxPv^2<`)BREJw9O22eY%@3EcKs-t$|sX3rSDNVDQZmZECFFF&D`+sOKl71J(sw$v9|M$2AP{y8rB6&FWY!@<&HZqhu&}RzG|~c$QyiV z;41g$g>55#T(IimwD94hw!OLPyY!0oKaW4NYQuVuOYM4kuP!+E)WR`?hOG|R(Dl&e zvL&m7j2{i|wEUyhkB(;p)%}BlH3J5QgzAPxL`FrMoX;%dLQ_h5ZK%%0Ob3Qw|GkHg zXa9R?|F=>F1_r7F`v(RE1_x>a2Luib3<(TX2ddTT{^}rguv()YpdP3WQHS;q?62=@O`t}t>8}aW1Zy;!0h)oD5KZWSzyaz3{RadM2p*srFkryI0U-lI2L=vQ59~iM zXkhR_&Av*z=lUfjIRgl zqo%JB{@fUvxFAVLUSbp?_4w}w<0C|2Y?Ll?iZD8LQL=yH{IrbZBw=(yx-m6jzJF4J zF~L7|p?^wx!h&QWF)b-sSdg5NkuWhIO7 zS5@~`Rp;<%X8YP?ERoDFRlV6FB{W*h_arl_P-MoMR3?E0BCV>suwh_ZGG1J=RAo@4 z%exEkoXQLVn;+pFqp>u+xwrGf1>4v4$)a&W3`50X$~eLeXA;VFp{G|!!dEgjf<(mP z+nwd)hy*WL+HlP^l63v7sPvW+cdE)Ql_@6knOM3}$BeRb3(`5mCxZN-lo8_mwXuTE zV>qAB=23JSP|2%QVR(uS)V;@Qzmu>qA>ml7VIV=~d7$Lmv` ziQ<*UX-zC$@VX7Mo2e42Eb})nxKgEcC82oy<7?Qs)dSV8Kg-o5GFm*XA}YPL1zq1z zN{K?zF>i`~ENheFAr;Q(w&Tp&g(L-nMAHjN!R4~3YbI|2KV=TpST{nZH2RW*D1P$_ zHtgBcfU=;KBC%;xl~WZo9EJ?2qeReF31Pqy3(w*~!N6Fc(x*e*}V z@8T!bJF44(NOotye}*OQzG*oM426=RrqWL}GegDF`S&Rk+ z<$|_hWn*K0G&6)|D3xejRKV6E1Nm5fm+Hvn(kV2dsPqld(OfQ@FRFz3z9{ltP}EGT zTNlK$BO|d)V!_IW1&ay`-Xor_Mp(VshJn#cBAskVB^m}%*%Py^d!zZZ+|;iyePVi| z(PXhJIT#yF7uO=xbTYpto8Q#e6O9iiN0KWW79j}BcVBWSg=~-y;}#9~I~|?<-8Xjk zzf;BXLj{Eoii*XHsj*aX7rlwaq)H@R)*l4wC=g+j7eP-&2p zPHH5EublZ^OpX^-VFWD+-!{MX?Ht&6*a=0IKoyelJcTh5E5?V_Hq=m6$f3QbxKkNK zm5C?Sn2M)U_`o?}MK`UEcJ_6z4y#SQrQgwTZ z-H%S)wk~Q2QnE|{li^BaCYwsCk}mxcibl}2>cE32b9dws6o=_6g*F4vIzJl6o7XUi z3WqLLxUX+RpP~*GR}%fW^grqesMr{s^7h4qh>mzKb{_y&UYAlZAsM$k1xq27gWYlZ?jKqq=o!OCGHiK%nlHG4g z6^A$VuW2q<2$wbO>PbCX0V@0HVV6=#zN&2=+qA00o4e(a2RJvmJXV;)q z;XgZqrYfD@wNkzgLL7;uGOKkHgx(u9C*29;JvEp@(#;LD9E!TsZ^1>g_C2@=M{w2P zg7M8)HIQKWe1EsO-WT)LlJmw(F07;b8-cNfk&p03xNmGYpj10V9_8!<>5FdUNcIb^t9f3UzDarG&i&)k93hbZ06=A+dsU?s` zJPrFJLEMA=JOs9XfIYhdvLHx29;8`y*xitO?ty&_Qk8GQ?!N*238gMV<~GCL{VquP zVc&Bz(gORXt%&n|&>V{)9k351AX0+ehYtR)VZR8w;db2L0m0CpDs^`r7inIQY#g5Z-(UdEcgdFT73FM;0pIvkR4) zyGYgEy-4{kTdZpDSgdOLu2l8tB>70s3VKep3zsS1fn{pe&L&m&hbHCwPP3YsT%l%O z(57Z$tNd9nu2MBCufgc}TIIX>T8QX)@cl?3+BVJA9N-H{G8V?n2? z`-@KH3$0Ogf3rr_z6`C&uJw@q_JY4b)qEZjz`6*+jHn9^qw*ZVZ|vrG;lU{K+OKME zyHU-2@kYoyZ&EdRs0!TjPBr_|Eo#=oThxVzl4`~+gKFmUgUZ)1q-uJG)T|>zkZ}#G z+UHS;E>0oNl=A&>8 z!(*Cj`QPvUJf$B`;JqaCH*7!6_OrXp{R6v=t=OK;_GN5e!S)iigKV#2dkxzWwzsgI zU^~P1INP_geK*@2yK8-s?;mFSuh_na?WfrOKHJZ+{d2ZoV*7V&*KvL?VtX#z3)o)9 zb_?6>Y_DVc$6QV?vi&mK{p`M#?P0d_Z0}+F18nbS`;%-x!1g0-*Rke9&8S-x`o~jf zU&usAZvuv)0r)TOo7o*rZ{f3FkJ%EB`{qA(+C}&G%=ZNy$bB4yp9SC?Jje(f5xXT&ytBHq1jlj46GVpjRK0dtN85)h{ z6N&=EO-NGnIe^5jbOLa6z!`v)8gru)w6sne-~Km}-_A9iG@BjEu;95f4LJ#fYsoyO zT$5_zmD(fqbN8d{DV4o#u0$}<;uXhr$HRdDX8Mb}Oi_l_#vK>y%$ZCdJ!z)(6W*~F z>FQi#!syg<%rrmT$oHG`K z9C{#~4EQry%wX?wFyU=NzT@N^Br1`0NZyrjcE(c0a9?lK2?RznsU4$92f7m5yK%!* z$Z$A}-!1Xsd^VE}_=)6bDJ#4*JAq(f7bI>Y4wOXV+u^g>X-$kmyofY*k5eG}cXv-Z z5XJ@d9AgkF87hhWCrz)E#=Ye}+uWih?T)lVauibp2g*DK{n}%8r^9L0VuWxiazmWr zf2ho}$#h8PMn9;7Rh(N!GbImCRPi7wK2qi;g%~7JYhsrvv!+aTr%Clw$MI|xaLM!0 za?hrYg-Ez)0(-WayCmy*;}|Wrk;sMQN=cp9SA4p8Aem>r=;VtTDxZlOeRE)UGM^=U zhr#jjm;>H;s;D1g4OLW!ZVo^Qn{gm3hOiUf?={|anG-%>a3Pa}JgPY8pcSIq5Akg+ zB&3#!L}cV}kQ}rM5}_wF%4RYU9}zx6crczRYQIluWCAAFqZ;Rv`n`Ns11%{cx915C zB_YYuS$}~c$%oL>-1=t~wy^BJQ<1+NDv>Kr#IMwmt+JY?S{i8TbwkZbA0 zA0#)(+jKskAW)$15?DtJC>HSUW^=kgpVGJZ;gFxB`UX5CwfnOg7V}&Z&ohJ~1$lmf zAT<#H#`qjnuWFnv>W!@8rT*YkwY_RVQ2l9bfOaRS38YMD+ko zjk-0o8Xr{a22M$=YcU^!FumxX^R0lcSuvm874C~f9c%~Ltp36mQs4E}sw0;}cJ=u` zfrPJ)UV*}&bbKb zp?UbX_iZ3J>O(b9zq=8iV@l-LW~ll;^<+(V25mSg5hc}c>Y~G=#YA>zM*Y5SQw~j7 zQoXOf6WxP6B=&et_?rGX3NWdDR5P0CM1>rH6uqFngnLHUK^?0pK!%%84`Q>6gM+>( zsc~FS(&K-tsZ~GCKp+&Nbm|QCrQ*X9E`;-u_(%%PV4zLZ?<{Bag}Q*tBjt<2aBO>$ zNmQZ_*EPHD(b0iI98y|50j>T)0h6CYbt_yy(1SXTuP5qi)$>^huI{|uhyM>^d1jsZ z_8piLu4#4Y>(I3IC3ERr2pd7CQtFp=LFlMO^4U1l54#g%>ML0Ie(WwH{~xT{ty4dL zjDnE-s!!Cj4oN|M89smaIQjYYSLpu)wl=C$_h6snI`z`m>HkMx1lO%WW9ldM<@M)( z*Eg$Q)`!%-8FlKj@LQ)25yP!Pcg(PA&==w9$J&{z&;v7c6^fvC9faHdV-&#AU()~2 zzl8sF2k(TmxrTc$M`y5x(BH%F#Qin!brAo_9JzdO$*RxRKUbf--M2U~XS{W7`%AvV zHS5~9%z4;%2s(k=|->B<-!MAejfA!txn|r@+?rk^!xaKb32kZCw4$tX*&Nur|&AP2E zyXV~F`%&FDYi_%3pDz=9(znm|3q<)z-v=&##5dt*0Wp43yM}P$3K##lZj$g<3bDPf%h6G{h$0V z#5KtG2-ChEHm2}C&*44J;RzQ05)a?T#Qz@ipJBdW;ji-WZA^U90U+})%oltSlymL; z+~mO${%1`!(B(tB!ngBNEq{syiJM+$;ukD@e%x;SHm3OJFn@&kf`!lOBQD>@#3vmRG6$J2Soq70 zv&*+J@xR9W7nv_u_-j0T8xy~d1^OFN@2LC*3%}jNw=wa%nE!R=3*Ph<9zPk1(_ z@JNS-%wK~~=@BgH3DFN2vU&B7&6W_+f??JzX%nsCRieIqs?fOT28xwys z^Y3N8VBtS(f_KwzW8&A%H6AjkSGqnh7XBd*-^Rp$oc&WhAiiMX|CNVtW8#y}6q&c6 z9uQx!@a^Z_i<>qk{zGpv{Fj+8Sojx_(i03};@g<`k3)Bf%>Tf1)ahp|{N**qUHCR8 zKIwXq`2wB;@dZzyT=p6#{r{*3OZfZn9VH{-+nB=7^9Sv%C?^VEu<%zJy34mQ@gHXX zSC}tY_**@E8xw!_Qsd!9lrzOISon5*>cveP6aR7M|AP5~h2QIm-^RqRyUKXDMOz0_dIXbyYA@{en)o&*ewz6K)KlUM7XE+<-i_bJ#Qz@i^UN14{4E~7jfqb> zjbt8UzF^@G8)r9u8xwy&^XZfFIf2P9=?ra%0Y36^G`GSRSr?(e3ZA|?BfQjI`1{1$v;oI>M-^Rp$iusQ)U$F4gp7h(8 z_@SWj;Gb{e7c6}H{ipbCOnlO1CG#lr1q*+Hadw}-jfsDl`JHf}@)s=ph=*@u;)_1n zhnX+d~TF(!RrGJ6=4UND&tF(& zekt^rDLlc=z}3QgKgS{A*_gsxb)E6>0S-?v`Co`@wTEwG;@`&nzhu7P$?}gdpZ1=l z{0N>bzoy+JaPbnu7d%;h7xQmszTnC7KfwGCGhgsz`A;$bN#+ZlET44y$^4%Af`$Jn z`T;{$FaFt>o*%6VATtmBCMtiyv;y=31c$k3?6+M5!!oSbM zw=wZ)?Fg9#%oi+t9KA8kzhmd%ErV7_4C_j=;DG4b~@|0weX3;#I}U+_xw)2Tm4 z>#fN2Fs5}^WVSM<^;cweFs5}_WcD(q^;l#+&bWO-L-j?*n;Cx{xWPE-|83}}+xU5dZ-^RrMHS^anU$F4! zc=$FZzU)i-ZsrTl0Ndp&;oaj2&&CuUttlk)2!|(F_**>fi;am-YZ1wOllg*$-$g%Q z$m+#E8xuct3%O#?PUZ_H|5P9B_MP}PCjOyY;0fks<_n%I-ybvlOPfu761))DF3$lE zmhkQNlG0~mN*}G!B=cPkU$F28jI&!lHYWZuzVTD$3l=_JQ*8)S{5B^3R_0%XicIgL z;CBGq`6E7+_meub((|%0g|{$nJUqbR3I6(V!n4bR(r04|?+}N#6yq{VpJ4pd>G_2x zJvJu(frRn!3+4+Bw3g@Z&pminTe*AAgC+h$p7?D{@za`HGCv2O(l1!}pES0Tjjfqcdo5{>tWzsKL_(kLFrr*ZI zR|VrC$9%!Ux4%CWzm16>Vg3)9FIf0TJ@MO^_zj$(!ZjxSf`yN6g*JpKejDrfng2!R zM;Hs=uCK(mG4W+x=Avs&{DOtAH>yY6go!UW4FA->%1yul<|f9Up3qRG7!SYC;9D8f zdUZ1U7}L6SGWRj2_3LB~Gp2RyWd1v2TF*}AM~v@ge2j2CzDN6w`~2&GFU30k&l%jz zc%1QC#!oPg5`F{Zdk^|FVSw@e2WvGRWqg3n*@3x>@x9+L_%q_p_)Cl%ju`jDjF&L} zHsj5VpJ99l;};n3XZ){>?}cs#nK!qa^d4ZmO0dtkM;ISuJS4crxbG2MYw*Ve*BSg@ z7=M}ZlZ+2Beva{@jDNxSFk>pkgegk=LV3+9@6XnCl;gR;)#mFi8`E_43Aj`HDi}ZY z_hGhicI&5&seWI<{D+w@SooKD_%r1p+nD%AcWP&~i1~ua|3X|h8fTYpW8yC!Hy*;w7cBe%4_|N` z{^|Yu+UJaiB4NB=j~%Ce+z$5yheg)^_27R6w(G}jok}%W{y*-)ls>!u9fbQ8z6n>q z<+?6|MgQdMj76Vhe%QE+en=Z*(f8QHSoAlZU@ZC=^VS%?=vVw1W6_sbbG>mFeTOBO z_oDQP{=x?ti$1~?m{%fq(Jy$JvFHoT!-t97MgIRUj77fxZOrSCyU6G7>oHj5?YCmy zgWN@a-rvjq=b1uYw!vVLZ`Vf*7J2k%7>j&))kfnk^5P#d7WwaQ8H+sk<9)`z$Y|{#XmBZ`NGS#8otc`y@#>P=e@vK=I1s$#=p$B{WW8mFZ(HD zng5F1V*JZ|R)Mk1PyG{PnQ!Wj8UHfBGQe2oH{Q!w<`X^zOwTujaL7Ey_|PpHs=s6Z zGT!_NV;LX*f&I&PZ}ET$PsVRiV0u0>{z@~J@lzmf++{peWGv&8dNj7gm+?j;V;MjE zC1V*6e4nxO?=MYqdE`xg?qMwb?|)+~{po>0EKOkIJ zlGpx>vB+zG%UI;Kvxkj;k=M>+Eb>}EW0BW(G8TF5jf_QJn_?{T+C7X#Ui%@>(R~ zW0`M!l(Eb=KFL_-8~+z$nQwfNar=ZRUpjz`^1qq!r744Rj2AMN{&x#w>3^?fEdB3y zF_!*!hOzX&-^W<`-ydZx{qHX@mj3rwfv>2aPz`%cdY5lA*w6SG#_jJn?!RaJ(msQC zZa4nbhYWs6@P30Ar;*?KQvZGnVe~Jgf1hD2{rmSZmj3@#8~?OYZy!a|7OP0->bo6 zd_A6@|HI~azL~K;$O^whgz>)1d?>mr8q0h%ns1Fo-(Ux0(I2>*vCM~lhOx|NKFV0; zi@(KK<}3e^vCQZGhOvw{W+82qzGGoHz${@ryTjmhj76VdFXIPS8+SShllUT!{XS#S zC&vN09#7bqj~P5e@B<$FErUTZ=I=fDm~WtnQ>V`j|1BQ8+=Dwj zxW|K|9*lhnDgSQxaSu*=aNdJ=d+_ZZe5VJ0#DhQS!S@@Cx5gMc`3vcD@&EMTpLp;; zd+-})mFI7f2e0(tb`M_T!F?Wlvj@{@j&^y{S&=p_dhl)!u0_qjsxXB$ZE6Ot*|;vm zH3!%0aFL$%>v7RxtC!%q6xSPYU54w8xGu-_C%ER~`cJstgzL??=Ha>m*IRKl;F^!? zZMYWVx)RqCTuX6Xh3oCOmf@mfT$kf&#D(*)RRC8IR}-!lTq|(3;%dXS64yI$t-^H; zu4{2!hpQb|2QHX)EX&jBTmX9!E^7PeIJstAuf=>fC`Z;8x@#eML0hE*FL@IOr+g`- zd)rz<>E4sI{cdzvlyBBZmexdCp3SPs(hYlgrF++1o$1~&i!|Mpgr58EVnghE5b!Kd zq+2Ii&j|P6iI-fGkJDSsNz9Wk*Cbz)S+EIzy@B!-xFKHISk0;Ga@i^#n`QZJH8*QH zWff<8sc#kM^0k}VgJ;2TS^B+h?qbbq%<@!wsLkGFd)y`6W2#!vmB~J}h0-V35yP_= z2H=Tz!Km2(;yC-McyHS~sd(MwUH9yvK_y|fM5(@e#tGc@1_ze=$v4b6iRV|(P8lao zs5j%s*&Cx2$cZ+^DETVi4a0lCv<-&W#gm*9ZG2IpmV8z2cwzY}-|C{mL830-+ydV8 zeloCW)0~rUVFMr5HW)x={|nyZf;YZkWbb%^TV|^Z{HU@O5AK%jd_lN;%Zsu*W_uoe z%WTfWJ7GB32u5##!FyjggS_d5Gf2B#IDk`rl8$*XiEyR zX)g-12}Q+5MyGSwh|1^UQr_6bVR<~4oBS~f1EZ;Q!aCAuxw+A2_6VtT!2Sx@(V}p5 zhBmfOH;%WqI4z+@PYSZ;V^RJAmM;lyyKp+$^d4+OFP7&UU->!mcqsoAmZwiXu&0wY zcmF8+9H=u3$UP`!`SM1+%?q8aK&Qx5$#9B|DJN zo=qV-ya9V^(Vk6#wkjuJk*7egGhaH}3EMCQ{cBR=$wW^ogMCLj;i#H@c1bKNsYADXFd?Hi)4rWW|nQKvjDT&FqZ_$u0 z7YfUiD(%*n#R!)=r(-a}D_QgVh_AotLLOR`xXQ_Q8JUE4m9|Iz_1R)jqTAx9Qx|d4sQl%v%lc zROE^dAbc(5imR$VJn=P^FZYvyqqoj)#s;RiaWOeFqoebivnkG)X*KnUbg!v=nFnU3 z`OkC4&a4+CA8VXln=fV)XFO1G!;=#_%fG^>+Bpc(5wqu@cW|odLL2Lzefug*UQ2j9 zhdnK)I%U|;ksfED*)x=0fxa>gtMwpjidzuV^DyH8IlEWCap)zRj`B%|3>Ytj!{|Q+ z(Hm~^c2@&a(S@KBf2pufnv`&OTVG}0RHijr!urtQlZF(~{e9%f*>;A5AUQ%(riQGp zmA^k9%ixIhDubYKo9lMcw9=RjgM@H+0Li63thatlb`0OL-qf**j{RfZ8-ncbR)vvRGexP=Z?$d1u^HY(*r-fwA0wV z+Q4k83O^8pjL&4gFSaurq4BUM?bK#8qb_digLVF%QY)U4?h{o!BoeAm$!uC6fWpvm z=(4~Yc%Jjz+?^@5gu>(Juhd&Mp>u*H>^gr*2()O04?QumArr>#^6s=^spUP@7Zs0s z7@N&+rvrkEdfx-%}rDT$|i@V>b1l97t}s#Q9?Kn&cia4chI=zX;z?654!Y#={JeGr8wS3}i5{H_2inCOMBiWq+e>fEm=umrFF{^71 zgioZRHR!s%uk6i6t4XesKC7r8s$>J%!T~m{Z@&st< z>1nD_o!L-Q3+3}hJk=8@DRT8@vCV&9!0+yLpHEHxIol0T3_|{G*z3R19b{tAIyjn% zn>Yh)S{rFYj*d)-rOl9`yQ9UqA&%a^9Y1=ak*Mj39*5L=I;24Gv{-@DVKto&srhtB zZ6_p6LZxp?74uG}VrWSwTXc2EcL!R#QaB%kMPI8&2XT&*-HDAholL6Jv&|pIIZpU& z=edJfF~2^=k+ne6axgVs`j~hcF~~* zoaf|N^CV}I5;HFn2$}gnJxf7T)T!`TB;Iev8G(RloU{T=l}_KJqn})5tr!Oe{5pO; zA~^feAAf)|#rbS8t-Ky}dne_mI4XXER)n5+a*E??)ej7#rtp+~E>p#9tU`UneT7>_ zn}YBV#<@9XH)mWIs}BgR}EjjRd1Q%hibYgt8sW%c%Wij;;*<< zWdK#_DNs2Msy583^cAca2TgHqD_@y#+b|A+Obw-q72~0^AGbBWu?K%{jngX{OyYV* zF_I1k{Baj5cdU?$pARKTJ9^fT40UJwhe?fPZ8nWrTB8~)LlSCaLY0c>1a?2=Xrf zaklgXT4bHk8qzC4g&`eWjQD%dvBrt0X8i7P>s_%Lx$LClDckDu0Xk4}O^07&!M zf0U4PB(f~r@;o@{EMakz6kX}n3imcGbo{N3lgf-`x6{#jG!t@MpMjE3eSX~8&zL#a zRKJq)MA|aR@nTmp9ouzoUdQxM7TWs5rgY81mtfNf&J0XK$KNULOyRiDypzn}Sj}Yj zuAULh(DsZp1%mh=Xl~KJ!#%qKP5w|*z~9#9Z*FUA4K%kl1)AU%Y=c`%Ak^9x2(`2Y zf=w;WffYf2Gm%%osWs>iwzh_v{jH(aUtZ1Tt&24a8;is5G2zFOX zQ%fk&)DjFfwT4<&v^Dt=VmMAG(9+zP$wZ5J|2u=-m2bN=ci~Oxv9PlapOWx~ey1z9 zf#hFj|EU+D$?O#KIQq754r0Seq1pVda}ZH+G?z}EgLvfNU5w!{g`Uf0^H_o5KKDRK z+?`xLJHCqqV$C{f-Rnr?y;AryHtD}$YA9VGFWwR=uH#^Zm4_^oOwI9?&r>GcET%0V&gZVZ6Qui zphql{_wz(JNW#*+@&01EVUE88K#d#z3TG=z5klRt%Zry6`OY%a&ew@j?UXLhy z0)bxiZ}_NWO>LE7l15DtS}90L3hAH{rM2$3*;~p8 zTGR^Iofu>p*On@->v5!SY>0EF>E)z=Ch2=+UPDv}boy~LiNmqEXj`K=4I52!((BN6 zQ>?IV)J|HGv);lkoISbSe0{c98EJq#bG)V$nsLXDRPkd#F**49jh~P4u{Q~Nj9g`p z0#x4ev$SPpp=rUru5JUbkJUyUsNk2c3iJdn%L1zs^#mVUCQ+;Na!D{uDs`sNy>+oQ zlFB8a(v+Y4=;~DEjTqjg#*&F>b~KMes2ddzfN5ufd8$!YUn%NMKuJ_v zI)~h}#qL12xuW|)^)PA|gK3vZ|NWi@(i(~Iyh zutoP^%2orHS5>Qrab`_xY2EhpB0O5!@bBS~O)tVTy9}VwKQ|BTRKLEicGi^j4{4gX za-Gqsrpv3X^(srArM)$kDU;#&^eQ)ZhaB^bK98&1JUx!-wP~lk2@>tEGrcyA1Pk=Z z_HGfS{$Hl`PQ zxa(fd!eS3=s(8w^HMN2uFJ1Nu)2BGH>BSYE1u$o4t%ub;pY#K)l9K7g70`v?tt$0; zoax0CXTF%rUR+T=ws6PD)5{^WhG==|O!`Hhc1FUn3am9A6kE)kr zV5l{%jj30RPu)0vS{u_;s{c^hn3Zakm0Fe6mfW3>olU$-o0ZC*RnlJR=}Z5k1p-)m2oS RU#)u8^uS7bzUHjN{}1GHl(zr? literal 0 HcmV?d00001 diff --git a/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/DiscordRPC.h b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/DiscordRPC.h new file mode 100644 index 0000000..d073044 --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/DiscordRPC.h @@ -0,0 +1,19 @@ +// +// DiscordRPC.h +// DiscordRPC +// +// Created by 小鳥遊六花 on 1/30/18. +// Copyright © 2018 Moy IT Solutions. All rights reserved. +// + +#import + +//! Project version number for DiscordRPC. +FOUNDATION_EXPORT double DiscordRPCVersionNumber; + +//! Project version string for DiscordRPC. +FOUNDATION_EXPORT const unsigned char DiscordRPCVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import +#import + diff --git a/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/backoff.h b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/backoff.h new file mode 100755 index 0000000..a3e736f --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/backoff.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +struct Backoff { + int64_t minAmount; + int64_t maxAmount; + int64_t current; + int fails; + std::mt19937_64 randGenerator; + std::uniform_real_distribution<> randDistribution; + + double rand01() { return randDistribution(randGenerator); } + + Backoff(int64_t min, int64_t max) + : minAmount(min) + , maxAmount(max) + , current(min) + , fails(0) + , randGenerator((uint64_t)time(0)) + { + } + + void reset() + { + fails = 0; + current = minAmount; + } + + int64_t nextDelay() + { + ++fails; + int64_t delay = (int64_t)((double)current * 2.0 * rand01()); + current = std::min(current + delay, maxAmount); + return current; + } +}; diff --git a/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/connection.h b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/connection.h new file mode 100755 index 0000000..a8f99b9 --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/connection.h @@ -0,0 +1,19 @@ +#pragma once + +// This is to wrap the platform specific kinds of connect/read/write. + +#include +#include + +// not really connectiony, but need per-platform +int GetProcessId(); + +struct BaseConnection { + static BaseConnection* Create(); + static void Destroy(BaseConnection*&); + bool isOpen{false}; + bool Open(); + bool Close(); + bool Write(const void* data, size_t length); + bool Read(void* data, size_t length); +}; diff --git a/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/discord_register.h b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/discord_register.h new file mode 100755 index 0000000..4c16b68 --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/discord_register.h @@ -0,0 +1,26 @@ +#pragma once + +#if defined(DISCORD_DYNAMIC_LIB) +# if defined(_WIN32) +# if defined(DISCORD_BUILDING_SDK) +# define DISCORD_EXPORT __declspec(dllexport) +# else +# define DISCORD_EXPORT __declspec(dllimport) +# endif +# else +# define DISCORD_EXPORT __attribute__((visibility("default"))) +# endif +#else +# define DISCORD_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); +DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); + +#ifdef __cplusplus +} +#endif diff --git a/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/discord_rpc.h b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/discord_rpc.h new file mode 100755 index 0000000..72e5a4f --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/discord_rpc.h @@ -0,0 +1,87 @@ +#pragma once +#include + +// clang-format off + +#if defined(DISCORD_DYNAMIC_LIB) +# if defined(_WIN32) +# if defined(DISCORD_BUILDING_SDK) +# define DISCORD_EXPORT __declspec(dllexport) +# else +# define DISCORD_EXPORT __declspec(dllimport) +# endif +# else +# define DISCORD_EXPORT __attribute__((visibility("default"))) +# endif +#else +# define DISCORD_EXPORT +#endif + +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DiscordRichPresence { + const char* state; /* max 128 bytes */ + const char* details; /* max 128 bytes */ + int64_t startTimestamp; + int64_t endTimestamp; + const char* largeImageKey; /* max 32 bytes */ + const char* largeImageText; /* max 128 bytes */ + const char* smallImageKey; /* max 32 bytes */ + const char* smallImageText; /* max 128 bytes */ + const char* partyId; /* max 128 bytes */ + int partySize; + int partyMax; + const char* matchSecret; /* max 128 bytes */ + const char* joinSecret; /* max 128 bytes */ + const char* spectateSecret; /* max 128 bytes */ + int8_t instance; +} DiscordRichPresence; + +typedef struct DiscordJoinRequest { + const char* userId; + const char* username; + const char* discriminator; + const char* avatar; +} DiscordJoinRequest; + +typedef struct DiscordEventHandlers { + void (*ready)(void); + void (*disconnected)(int errorCode, const char* message); + void (*errored)(int errorCode, const char* message); + void (*joinGame)(const char* joinSecret); + void (*spectateGame)(const char* spectateSecret); + void (*joinRequest)(const DiscordJoinRequest* request); +} DiscordEventHandlers; + +#define DISCORD_REPLY_NO 0 +#define DISCORD_REPLY_YES 1 +#define DISCORD_REPLY_IGNORE 2 + +DISCORD_EXPORT void Discord_Initialize(const char* applicationId, + DiscordEventHandlers* handlers, + int autoRegister, + const char* optionalSteamId); +DISCORD_EXPORT void Discord_Shutdown(void); + +/* checks for incoming messages, dispatches callbacks */ +DISCORD_EXPORT void Discord_RunCallbacks(void); + +/* If you disable the lib starting its own io thread, you'll need to call this from your own */ +#ifdef DISCORD_DISABLE_IO_THREAD +DISCORD_EXPORT void Discord_UpdateConnection(void); +#endif + +DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); +DISCORD_EXPORT void Discord_ClearPresence(void); + +DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); + +DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers); + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/msg_queue.h b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/msg_queue.h new file mode 100755 index 0000000..470ce96 --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/msg_queue.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +// A simple queue. No locks, but only works with a single thread as producer and a single thread as +// a consumer. Mutex up as needed. + +template +class MsgQueue { + ElementType queue_[QueueSize]{}; + std::atomic_uint nextAdd_{0}; + std::atomic_uint nextSend_{0}; + std::atomic_uint pendingSends_{0}; + +public: + MsgQueue() {} + + ElementType* GetNextAddMessage() + { + // if we are falling behind, bail + if (pendingSends_.load() >= QueueSize) { + return nullptr; + } + auto index = (nextAdd_++) % QueueSize; + return &queue_[index]; + } + void CommitAdd() { ++pendingSends_; } + + bool HavePendingSends() const { return pendingSends_.load() != 0; } + ElementType* GetNextSendMessage() + { + auto index = (nextSend_++) % QueueSize; + return &queue_[index]; + } + void CommitSend() { --pendingSends_; } +}; diff --git a/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/rpc_connection.h b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/rpc_connection.h new file mode 100755 index 0000000..d3c30d6 --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/rpc_connection.h @@ -0,0 +1,59 @@ +#pragma once + +#include "connection.h" +#include "serialization.h" + +// I took this from the buffer size libuv uses for named pipes; I suspect ours would usually be much +// smaller. +constexpr size_t MaxRpcFrameSize = 64 * 1024; + +struct RpcConnection { + enum class ErrorCode : int { + Success = 0, + PipeClosed = 1, + ReadCorrupt = 2, + }; + + enum class Opcode : uint32_t { + Handshake = 0, + Frame = 1, + Close = 2, + Ping = 3, + Pong = 4, + }; + + struct MessageFrameHeader { + Opcode opcode; + uint32_t length; + }; + + struct MessageFrame : public MessageFrameHeader { + char message[MaxRpcFrameSize - sizeof(MessageFrameHeader)]; + }; + + enum class State : uint32_t { + Disconnected, + SentHandshake, + AwaitingResponse, + Connected, + }; + + BaseConnection* connection{nullptr}; + State state{State::Disconnected}; + void (*onConnect)(){nullptr}; + void (*onDisconnect)(int errorCode, const char* message){nullptr}; + char appId[64]{}; + int lastErrorCode{0}; + char lastErrorMessage[256]{}; + RpcConnection::MessageFrame sendFrame; + + static RpcConnection* Create(const char* applicationId); + static void Destroy(RpcConnection*&); + + inline bool IsOpen() const { return state == State::Connected; } + + void Open(); + void Close(); + bool Write(const void* data, size_t length); + bool Read(JsonDocument& message); +}; diff --git a/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/serialization.h b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/serialization.h new file mode 100755 index 0000000..106dce7 --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Headers/serialization.h @@ -0,0 +1,215 @@ +#pragma once + +#include + +#ifndef __MINGW32__ +#pragma warning(push) + +#pragma warning(disable : 4061) // enum is not explicitly handled by a case label +#pragma warning(disable : 4365) // signed/unsigned mismatch +#pragma warning(disable : 4464) // relative include path contains +#pragma warning(disable : 4668) // is not defined as a preprocessor macro +#pragma warning(disable : 6313) // Incorrect operator +#endif // __MINGW32__ + +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" + +#ifndef __MINGW32__ +#pragma warning(pop) +#endif // __MINGW32__ + +// if only there was a standard library function for this +template +inline size_t StringCopy(char (&dest)[Len], const char* src) +{ + if (!src || !Len) { + return 0; + } + size_t copied; + char* out = dest; + for (copied = 1; *src && copied < Len; ++copied) { + *out++ = *src++; + } + *out = 0; + return copied - 1; +} + +size_t JsonWriteHandshakeObj(char* dest, size_t maxLen, int version, const char* applicationId); + +// Commands +struct DiscordRichPresence; +size_t JsonWriteRichPresenceObj(char* dest, + size_t maxLen, + int nonce, + int pid, + const DiscordRichPresence* presence); +size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName); + +size_t JsonWriteUnsubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName); + +size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce); + +// I want to use as few allocations as I can get away with, and to do that with RapidJson, you need +// to supply some of your own allocators for stuff rather than use the defaults + +class LinearAllocator { +public: + char* buffer_; + char* end_; + LinearAllocator() + { + assert(0); // needed for some default case in rapidjson, should not use + } + LinearAllocator(char* buffer, size_t size) + : buffer_(buffer) + , end_(buffer + size) + { + } + static const bool kNeedFree = false; + void* Malloc(size_t size) + { + char* res = buffer_; + buffer_ += size; + if (buffer_ > end_) { + buffer_ = res; + return nullptr; + } + return res; + } + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) + { + if (newSize == 0) { + return nullptr; + } + // allocate how much you need in the first place + assert(!originalPtr && !originalSize); + // unused parameter warning + (void)(originalPtr); + (void)(originalSize); + return Malloc(newSize); + } + static void Free(void* ptr) + { + /* shrug */ + (void)ptr; + } +}; + +template +class FixedLinearAllocator : public LinearAllocator { +public: + char fixedBuffer_[Size]; + FixedLinearAllocator() + : LinearAllocator(fixedBuffer_, Size) + { + } + static const bool kNeedFree = false; +}; + +// wonder why this isn't a thing already, maybe I missed it +class DirectStringBuffer { +public: + using Ch = char; + char* buffer_; + char* end_; + char* current_; + + DirectStringBuffer(char* buffer, size_t maxLen) + : buffer_(buffer) + , end_(buffer + maxLen) + , current_(buffer) + { + } + + void Put(char c) + { + if (current_ < end_) { + *current_++ = c; + } + } + void Flush() {} + size_t GetSize() const { return (size_t)(current_ - buffer_); } +}; + +using MallocAllocator = rapidjson::CrtAllocator; +using PoolAllocator = rapidjson::MemoryPoolAllocator; +using UTF8 = rapidjson::UTF8; +// Writer appears to need about 16 bytes per nested object level (with 64bit size_t) +using StackAllocator = FixedLinearAllocator<2048>; +constexpr size_t WriterNestingLevels = 2048 / (2 * sizeof(size_t)); +using JsonWriterBase = + rapidjson::Writer; +class JsonWriter : public JsonWriterBase { +public: + DirectStringBuffer stringBuffer_; + StackAllocator stackAlloc_; + + JsonWriter(char* dest, size_t maxLen) + : JsonWriterBase(stringBuffer_, &stackAlloc_, WriterNestingLevels) + , stringBuffer_(dest, maxLen) + , stackAlloc_() + { + } + + size_t Size() const { return stringBuffer_.GetSize(); } +}; + +using JsonDocumentBase = rapidjson::GenericDocument; +class JsonDocument : public JsonDocumentBase { +public: + static const int kDefaultChunkCapacity = 32 * 1024; + // json parser will use this buffer first, then allocate more if needed; I seriously doubt we + // send any messages that would use all of this, though. + char parseBuffer_[32 * 1024]; + MallocAllocator mallocAllocator_; + PoolAllocator poolAllocator_; + StackAllocator stackAllocator_; + JsonDocument() + : JsonDocumentBase(rapidjson::kObjectType, + &poolAllocator_, + sizeof(stackAllocator_.fixedBuffer_), + &stackAllocator_) + , poolAllocator_(parseBuffer_, sizeof(parseBuffer_), kDefaultChunkCapacity, &mallocAllocator_) + , stackAllocator_() + { + } +}; + +using JsonValue = rapidjson::GenericValue; + +inline JsonValue* GetObjMember(JsonValue* obj, const char* name) +{ + if (obj) { + auto member = obj->FindMember(name); + if (member != obj->MemberEnd() && member->value.IsObject()) { + return &member->value; + } + } + return nullptr; +} + +inline int GetIntMember(JsonValue* obj, const char* name, int notFoundDefault = 0) +{ + if (obj) { + auto member = obj->FindMember(name); + if (member != obj->MemberEnd() && member->value.IsInt()) { + return member->value.GetInt(); + } + } + return notFoundDefault; +} + +inline const char* GetStrMember(JsonValue* obj, + const char* name, + const char* notFoundDefault = nullptr) +{ + if (obj) { + auto member = obj->FindMember(name); + if (member != obj->MemberEnd() && member->value.IsString()) { + return member->value.GetString(); + } + } + return notFoundDefault; +} diff --git a/SwinsianDiscord/DiscordRPC.framework/Versions/A/Modules/module.modulemap b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Modules/module.modulemap new file mode 100644 index 0000000..af096cb --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module DiscordRPC { + umbrella header "DiscordRPC.h" + + export * + module * { export * } +} diff --git a/SwinsianDiscord/DiscordRPC.framework/Versions/A/Resources/Info.plist b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Resources/Info.plist new file mode 100644 index 0000000..03af01c --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Versions/A/Resources/Info.plist @@ -0,0 +1,44 @@ + + + + + BuildMachineOSBuild + 17C205 + CFBundleDevelopmentRegion + en + CFBundleExecutable + DiscordRPC + CFBundleIdentifier + pro.moyit.DiscordRPC + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DiscordRPC + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 9C40b + DTPlatformVersion + GM + DTSDKBuild + 17C76 + DTSDKName + macosx10.13 + DTXcode + 0920 + DTXcodeBuild + 9C40b + NSHumanReadableCopyright + Copyright © 2018 Moy IT Solutions. All rights reserved. + + diff --git a/SwinsianDiscord/DiscordRPC.framework/Versions/Current b/SwinsianDiscord/DiscordRPC.framework/Versions/Current new file mode 120000 index 0000000..8c7e5a6 --- /dev/null +++ b/SwinsianDiscord/DiscordRPC.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/SwinsianDiscord/EULA.rtf b/SwinsianDiscord/EULA.rtf new file mode 100644 index 0000000..dcbd387 --- /dev/null +++ b/SwinsianDiscord/EULA.rtf @@ -0,0 +1,21 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1561\cocoasubrtf200 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;\red0\green0\blue0;} +{\*\expandedcolortbl;;\csgenericrgb\c0\c0\c0;} +\paperw11900\paperh16840\margl720\margr720\vieww19440\viewh8200\viewkind0 +\deftab543 +\pard\tx543\pardeftab543\pardirnatural\partightenfactor0 + +\f0\fs22 \cf2 \CocoaLigature0 Copyright \'a9 2018, Moy IT Solutions.\ +All rights reserved.\ +\ +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: \ +\ +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. \ +\ +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. \ +\ +3. Neither the name of the Moy IT Solutions nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\ +\ +\ +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \'93AS IS\'94 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.} \ No newline at end of file diff --git a/iTunesDiscord/Info.plist b/SwinsianDiscord/Info.plist similarity index 88% rename from iTunesDiscord/Info.plist rename to SwinsianDiscord/Info.plist index aa3e413..929fba0 100644 --- a/iTunesDiscord/Info.plist +++ b/SwinsianDiscord/Info.plist @@ -28,5 +28,12 @@ MainMenu NSPrincipalClass NSApplication + NSUIElement + 1 + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + diff --git a/SwinsianDiscord/PFAboutWindow.xib b/SwinsianDiscord/PFAboutWindow.xib new file mode 100755 index 0000000..e3f6363 --- /dev/null +++ b/SwinsianDiscord/PFAboutWindow.xib @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwinsianDiscord/PFAboutWindowController.h b/SwinsianDiscord/PFAboutWindowController.h new file mode 100755 index 0000000..0e7b710 --- /dev/null +++ b/SwinsianDiscord/PFAboutWindowController.h @@ -0,0 +1,104 @@ +// +// PFAboutWindowController.m +// +// Copyright (c) 2015 Perceval FARAMAZ (@perfaram). All rights reserved. +// + +#import + +/** + * The about window. + */ +@interface PFAboutWindowController : NSWindowController + +/** + * The application name. + * Default: CFBundleName + */ +@property (copy) NSString *appName; + +/** + * The application version. + * Default: "Version %@ (Build %@)", CFBundleVersion, CFBundleShortVersionString + */ +@property (copy) NSString *appVersion; + +/** + * The copyright line. + * Default: NSHumanReadableCopyright + */ +@property (copy) NSAttributedString *appCopyright; + +/** + * The credits. + * Default: contents of file at [[NSBundle mainBundle] pathForResource:@"Credits" ofType:@"rtf"]; + */ +@property (copy) NSAttributedString *appCredits; + +/** + * The EULA. + * Default: contents of file at [[NSBundle mainBundle] pathForResource:@"EULA" ofType:@"rtf"]; + */ +@property (copy) NSAttributedString *appEULA; + +/** + * The URL pointing to the app's website. + * Default: none + */ +@property (strong) NSURL *appURL; + +/** + * The current text shown. + */ +@property (copy) NSAttributedString *textShown; + +@property int windowState; + +/** + * Visit the website. + * + * @param sender The object making the call. + */ +- (IBAction)visitWebsite:(id)sender; + +/** + * Show credits for libraries used etc. + * + * @param sender The object making the call. + */ +- (IBAction)showCredits:(id)sender; + +/** + * Show the End User License Agreement for your app. + * + * @param sender The object making the call. + */ +- (IBAction)showEULA:(id)sender; + +/** + * Show the Copyrights for your app. + * + * @param sender The object making the call. + */ +- (IBAction)showCopyright:(id)sender; + +/** + * Called when window is about to close. + * + * @param sender The object making the call. + */ +- (BOOL)windowShouldClose:(id)sender; + +/** + * Specify whether or not the window should use a shadow. + * Default: YES + */ +@property (assign) BOOL windowShouldHaveShadow; + +/** + * Select the text (Acknowledgments & EULA) color. + * Default : light grey + */ +- (instancetype) initWithBackgroundColor:(NSColor*)background titleColor:(NSColor*)title textColor:(NSColor*)text; + +@end diff --git a/SwinsianDiscord/PFAboutWindowController.m b/SwinsianDiscord/PFAboutWindowController.m new file mode 100755 index 0000000..f1831b3 --- /dev/null +++ b/SwinsianDiscord/PFAboutWindowController.m @@ -0,0 +1,201 @@ +// +// PFAboutWindowController.m +// +// Copyright (c) 2015 Perceval FARAMAZ (@perfaram). All rights reserved. +// + +#import "PFAboutWindowController.h" + +@interface PFAboutWindowController() + +/** The window nib to load. */ ++ (NSString *)nibName; + +/** The info view. */ +@property (assign) IBOutlet NSView *infoView; + +/** The main text view. */ +@property (assign) IBOutlet NSTextView *textField; + +/** The app version's text view. */ +@property (assign) IBOutlet NSTextField *versionField; + +/** The app version's text view. */ +@property (assign) IBOutlet NSTextField *nameField; + +/** The button that opens the app's website. */ +@property (assign) IBOutlet NSButton *visitWebsiteButton; + +/** The button that opens the EULA. */ +@property (assign) IBOutlet NSButton *EULAButton; + +/** The button that opens the credits. */ +@property (assign) IBOutlet NSButton *creditsButton; + +/** The view that's currently active. */ +@property (assign) NSView *activeView; + +/** The string to hold the credits if we're showing them in same window. */ +@property (copy) NSAttributedString *creditsString; + +/** Select a background color (defaults to white). */ +@property () NSColor *backgroundColor; + +/** Select the title (app name & version) color (defaults to black). */ +@property () NSColor *titleColor; + +/** Select the text (Acknowledgments & EULA) color (defaults to light grey). */ +@property () NSColor *textColor; + +@end + +@implementation PFAboutWindowController + +#pragma mark - Class Methods + ++ (NSString *)nibName { + return @"PFAboutWindow"; +} + +#pragma mark - Overrides + +- (id)init { + self.windowShouldHaveShadow = YES; + self.backgroundColor = [NSColor whiteColor]; + self.titleColor = [NSColor blackColor]; + self.textColor = (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9) ? [NSColor lightGrayColor] : [NSColor tertiaryLabelColor]; + + return [super initWithWindowNibName:[[self class] nibName]]; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + self.windowState = 0; + self.infoView.layer.cornerRadius = 10.0; + self.window.backgroundColor = self.backgroundColor; + [self.window setHasShadow:self.windowShouldHaveShadow]; + // Change highlight of the `visitWebsiteButton` when it's clicked. Otherwise, the button will have a highlight around it which isn't visually pleasing. + [self.visitWebsiteButton.cell setHighlightsBy:NSContentsCellMask]; + + // Load variables + NSDictionary *bundleDict = [[NSBundle mainBundle] infoDictionary]; + + // Set app name + if(!self.appName) { + self.appName = [bundleDict objectForKey:@"CFBundleName"]; + } + + // Set app version + if(!self.appVersion) { + NSString *version = [bundleDict objectForKey:@"CFBundleVersion"]; + NSString *shortVersion = [bundleDict objectForKey:@"CFBundleShortVersionString"]; + self.appVersion = [NSString stringWithFormat:NSLocalizedString(@"Version %@ (Build %@)", @"Version %@ (Build %@), displayed in the about window"), shortVersion, version]; + } + + // Set copyright + if(!self.appCopyright) { + self.appCopyright = [[NSAttributedString alloc] initWithString:[bundleDict objectForKey:@"NSHumanReadableCopyright"] attributes:@{ + NSFontAttributeName:[NSFont fontWithName:@"HelveticaNeue" size:11]/*/NSParagraphStyleAttributeName : paragraphStyle*/}]; + } + + // Code that can potentially throw an exception + // Set credits + if(!self.appCredits) { + @try { + self.appCredits = [[NSAttributedString alloc] initWithPath:[[NSBundle mainBundle] pathForResource:@"Credits" ofType:@"rtf"] documentAttributes:nil]; + } + @catch (NSException *exception) { + // hide the credits button + [self.creditsButton setHidden:YES]; + NSLog(@"PFAboutWindowController did handle exception: %@",exception); + } + } + // Set EULA + if(!self.appEULA) { + @try { + self.appEULA = [[NSAttributedString alloc] initWithPath:[[NSBundle mainBundle] pathForResource:@"EULA" ofType:@"rtf"] documentAttributes:nil]; + } + @catch (NSException *exception) { + // hide the eula button + [self.EULAButton setHidden:YES]; + NSLog(@"PFAboutWindowController did handle exception: %@",exception); + } + } + + [self.textField.textStorage setAttributedString:self.appCopyright]; + self.creditsButton.title = NSLocalizedString(@"Credits", @"Caption of the 'Credits' button in the about window"); + self.EULAButton.title = NSLocalizedString(@"License Agreement", @"Caption of the 'License Agreement' button in the about window"); + + _textField.textColor = self.textColor; + _versionField.textColor = self.titleColor; + _nameField.textColor = self.titleColor; + self.window.backgroundColor = self.backgroundColor; +} + +- (BOOL)windowShouldClose:(id)sender{ + return YES; +} + +- (void) showCredits:(id)sender { + if (self.windowState!=1) { + CGFloat amountToIncreaseHeight = 100; + NSRect oldFrame = [self.window frame]; + oldFrame.size.height += amountToIncreaseHeight; + oldFrame.origin.y -= amountToIncreaseHeight; + [self.window setFrame:oldFrame display:YES animate:NSAnimationLinear]; + self.windowState = 1; + } + [self.textField.textStorage setAttributedString:self.appCredits]; + _textField.textColor = self.textColor; +} + +- (void) showEULA:(id)sender { + if (self.windowState!=1) { + CGFloat amountToIncreaseHeight = 100; + NSRect oldFrame = [self.window frame]; + oldFrame.size.height += amountToIncreaseHeight; + oldFrame.origin.y -= amountToIncreaseHeight; + [self.window setFrame:oldFrame display:YES animate:NSAnimationLinear]; + self.windowState = 1; + } + [self.textField.textStorage setAttributedString:self.appEULA]; + _textField.textColor = self.textColor; +} + +- (void) showCopyright:(id)sender { + if (self.windowState!=0) { + CGFloat amountToIncreaseHeight = -100; + NSRect oldFrame = [self.window frame]; + oldFrame.size.height += amountToIncreaseHeight; + oldFrame.origin.y -= amountToIncreaseHeight; + [self.window setFrame:oldFrame display:YES animate:NSAnimationLinear]; + self.windowState = 0; + } + [self.textField.textStorage setAttributedString:self.appCopyright]; + _textField.textColor = self.textColor; +} + +- (IBAction)visitWebsite:(id)sender { + [[NSWorkspace sharedWorkspace] openURL:self.appURL]; +} + +- (void)showWindow:(id)sender +{ + // make sure the window will be visible and centered + [NSApp activateIgnoringOtherApps:YES]; + [self.window center]; + [self showCopyright:sender]; + [super showWindow:sender]; +} + +#pragma mark - Public Methods + +- (id)initWithBackgroundColor:(NSColor*)background titleColor:(NSColor*)title textColor:(NSColor*)text { + self.windowShouldHaveShadow = YES; + self.backgroundColor = background; + self.titleColor = title; + self.textColor = text; + return [super initWithWindowNibName:[[self class] nibName]]; +} + +@end diff --git a/iTunesDiscord/iTunesDiscord.entitlements b/SwinsianDiscord/iTunesDiscord.entitlements similarity index 53% rename from iTunesDiscord/iTunesDiscord.entitlements rename to SwinsianDiscord/iTunesDiscord.entitlements index f2ef3ae..0c67376 100644 --- a/iTunesDiscord/iTunesDiscord.entitlements +++ b/SwinsianDiscord/iTunesDiscord.entitlements @@ -1,10 +1,5 @@ - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - - + diff --git a/iTunesDiscord/main.m b/SwinsianDiscord/main.m similarity index 92% rename from iTunesDiscord/main.m rename to SwinsianDiscord/main.m index 41ee8e2..dce32dd 100644 --- a/iTunesDiscord/main.m +++ b/SwinsianDiscord/main.m @@ -1,6 +1,6 @@ // // main.m -// iTunesDiscord +// SwinsianDiscord // // Created by 小鳥遊六花 on 6/12/18. // Copyright © 2018 Moy IT Solutions. All rights reserved. diff --git a/iTunesDiscord/AppDelegate.m b/iTunesDiscord/AppDelegate.m deleted file mode 100644 index 8e63c46..0000000 --- a/iTunesDiscord/AppDelegate.m +++ /dev/null @@ -1,28 +0,0 @@ -// -// AppDelegate.m -// iTunesDiscord -// -// Created by 小鳥遊六花 on 6/12/18. -// Copyright © 2018 Moy IT Solutions. All rights reserved. -// - -#import "AppDelegate.h" - -@interface AppDelegate () - -@property (weak) IBOutlet NSWindow *window; -@end - -@implementation AppDelegate - -- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - // Insert code here to initialize your application -} - - -- (void)applicationWillTerminate:(NSNotification *)aNotification { - // Insert code here to tear down your application -} - - -@end