diff --git a/CMakeLists.txt b/CMakeLists.txt index 74fb94d857..241b6c9e15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1334,6 +1334,8 @@ elseif(IOS AND NOT LIBRETRO) ios/ViewControllerMetal.h ios/iOSCoreAudio.mm ios/iOSCoreAudio.h + ios/IAPManager.mm + ios/IAPManager.h ios/CameraHelper.mm ios/CameraHelper.h ios/LocationHelper.mm @@ -1536,6 +1538,8 @@ list(APPEND NativeAppSource UI/EmuScreen.cpp UI/GameInfoCache.h UI/GameInfoCache.cpp + UI/IAPScreen.cpp + UI/IAPScreen.h UI/MainScreen.h UI/MainScreen.cpp UI/MiscScreens.h @@ -3011,7 +3015,7 @@ if(IOS AND NOT LIBRETRO) XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${DEVELOPMENT_TEAM_ID} XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Development" XCODE_ATTRIBUTE_CODE_SIGN_STYLE "Automatic" - XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${BUNDLE_ENTITLEMENTS} + #XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${BUNDLE_ENTITLEMENTS} XCODE_ATTRIBUTE_SKIP_INSTALL NO XCODE_ATTRIBUTE_INSTALL_PATH "$(LOCAL_APPS_DIR)" ) diff --git a/Common/Log.h b/Common/Log.h index 54c9a84fd1..8b6a20d87e 100644 --- a/Common/Log.h +++ b/Common/Log.h @@ -53,6 +53,7 @@ enum class Log { TexReplacement, GeDebugger, UI, + IAP, sceAudio, sceCtrl, diff --git a/Common/Log/LogManager.cpp b/Common/Log/LogManager.cpp index e6cc564299..959ac264c3 100644 --- a/Common/Log/LogManager.cpp +++ b/Common/Log/LogManager.cpp @@ -92,6 +92,7 @@ static const char * const g_logTypeNames[] = { "TEXREPLACE", "DEBUGGER", "UI", + "IAP", "SCEAUDIO", "SCECTRL", "SCEDISP", diff --git a/Common/System/Request.h b/Common/System/Request.h index bd2efe5dd2..47cb1e52ae 100644 --- a/Common/System/Request.h +++ b/Common/System/Request.h @@ -192,6 +192,14 @@ inline void System_SendDebugScreenshot(std::string_view data, int height) { g_requestManager.MakeSystemRequest(SystemRequestType::SEND_DEBUG_SCREENSHOT, NO_REQUESTER_TOKEN, nullptr, nullptr, data, "", height); } +inline void System_IAPRestorePurchases(RequesterToken token, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) { + g_requestManager.MakeSystemRequest(SystemRequestType::IAP_RESTORE_PURCHASES, token, callback, failedCallback, "", "", 0); +} + +inline void System_IAPMakePurchase(RequesterToken token, std::string_view productID, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) { + g_requestManager.MakeSystemRequest(SystemRequestType::IAP_MAKE_PURCHASE, token, callback, failedCallback, productID, "", 0); +} + void System_MoveToTrash(const Path &path); void System_RunCallbackInWndProc(void (*callback)(void *, void *), void *userdata); diff --git a/Common/System/System.h b/Common/System/System.h index de592b90bd..a629d05c70 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -100,6 +100,10 @@ enum class SystemRequestType { RUN_CALLBACK_IN_WNDPROC, MOVE_TO_TRASH, + + // for iOS IAP support + IAP_RESTORE_PURCHASES, + IAP_MAKE_PURCHASE, }; // Run a closure on the main thread. Used to safely implement UI that runs on another thread. @@ -224,6 +228,8 @@ enum SystemProperty { SYSPROP_ENOUGH_RAM_FOR_FULL_ISO, SYSPROP_HAS_TRASH_BIN, + + SYSPROP_USE_IAP, }; enum class SystemNotification { diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 4eb16b8ed2..203b9e26a3 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -693,7 +693,7 @@ bool System_GetPropertyBool(SystemProperty prop) { #ifdef GOLD return true; #else - return false; + return true; #endif case SYSPROP_CAN_JIT: return true; @@ -724,6 +724,10 @@ case SYSPROP_HAS_FILE_BROWSER: #else return true; #endif + + // hack for testing - do not commit + case SYSPROP_USE_IAP: + return false; default: return false; } @@ -999,6 +1003,15 @@ static void ProcessSDLEvent(SDL_Window *window, const SDL_Event &event, InputSta System_PostUIMessage(UIMessage::REQUEST_GAME_RESET); Core_Resume(); } + /* + // TODO: Enable this? + if (k == SDLK_F11) { +#if !defined(MOBILE_DEVICE) + g_Config.bFullScreen = !g_Config.bFullScreen; + System_ToggleFullscreenState(""); +#endif + } + */ } break; } diff --git a/UI/DeveloperToolsScreen.cpp b/UI/DeveloperToolsScreen.cpp index 3385ef0258..8500475f9e 100644 --- a/UI/DeveloperToolsScreen.cpp +++ b/UI/DeveloperToolsScreen.cpp @@ -184,7 +184,7 @@ void DeveloperToolsScreen::CreateGeneralTab(UI::LinearLayout *list) { }); #endif -#if PPSSPP_PLATFORM(IOS_APP_STORE) +#if PPSSPP_PLATFORM(IOS) list->Add(new NoticeView(NoticeLevel::WARN, ms->T("Moving the memstick directory is NOT recommended on iOS"), "")); list->Add(new Choice(sy->T("Set Memory Stick folder")))->OnClick.Add( [=](UI::EventParams &) { diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 1e6e1a90f0..6090420133 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -781,9 +781,6 @@ void EmuScreen::onVKey(VirtKey virtualKeyCode, bool down) { case VIRTKEY_PAUSE: if (down) { - // Trigger on key-up to partially avoid repetition problems. - // This is needed whenever we pop up a menu since the mapper - // might miss the key-up. Same as VIRTKEY_OPENCHAT. // Note: We don't check NetworkWarnUserIfOnlineAndCantSpeed, because we can keep // running in the background of the menu. pauseTrigger_ = true; diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index f6e29f1123..8dfdf87b2c 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -210,4 +210,7 @@ private: }; void TriggerRestart(const char *why, bool editThenRestore, const Path &gamePath); + +#if PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS) void SetMemStickDirDarwin(int requesterToken); +#endif diff --git a/UI/IAPScreen.cpp b/UI/IAPScreen.cpp new file mode 100644 index 0000000000..5e7041e05d --- /dev/null +++ b/UI/IAPScreen.cpp @@ -0,0 +1,67 @@ +// NOTE: This is only used on iOS, to present the availablility of getting PPSSPP Gold through IAP. + +#include "UI/IAPScreen.h" +#include "Common/System/System.h" +#include "Common/Data/Text/I18n.h" +#include "Common/System/OSD.h" + +void IAPScreen::CreateViews() { + using namespace UI; + + auto di = GetI18NCategory(I18NCat::DIALOG); + auto mm = GetI18NCategory(I18NCat::MAINMENU); + + root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); + + const bool bought = System_GetPropertyBool(SYSPROP_APP_GOLD); + + if (bought) { + root_->Add(new TextView("You have PPSSPP Gold! Thank you!")); + } else { + root_->Add(new TextView("PPSSPP Gold", ALIGN_LEFT | FLAG_WRAP_TEXT, false, new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 15, 105, 330, 10)))->SetClip(false); + } + + ViewGroup *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new AnchorLayoutParams(300, WRAP_CONTENT, NONE, 105, 15, NONE)); + root_->Add(rightColumnItems); + + if (!bought) { + Choice *buyButton = rightColumnItems->Add(new Choice(mm->T("Buy PPSSPP Gold"))); + const int requesterToken = GetRequesterToken(); + buyButton->OnClick.Add([this, requesterToken](UI::EventParams &) { + INFO_LOG(Log::System, "Showing purchase UI..."); + System_IAPMakePurchase(requesterToken, "org.ppsspp.gold", [this](const char *responseString, int intValue) { + INFO_LOG(Log::System, "Purchase successful!"); + RecreateViews(); + }, []() { + WARN_LOG(Log::System, "Purchase failed or cancelled!"); + }); + // TODO: What do we do here? + return UI::EVENT_DONE; + }); + } + + Choice *moreInfo = rightColumnItems->Add(new Choice(di->T("More info"))); + moreInfo->OnClick.Add([](UI::EventParams &) { + System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/docs/reference/whygold/"); + return UI::EVENT_DONE; + }); + + Choice *backButton = rightColumnItems->Add(new Choice(di->T("Back"))); + backButton->OnClick.Handle(this, &UIScreen::OnBack); + + // Put the restore purchases button in the bottom right corner. It's rarely useful, but needed. + rightColumnItems->Add(new Spacer(new LinearLayoutParams(1.0f))); + Choice *restorePurchases = new Choice(di->T("Restore purchase")); + const int requesterToken = GetRequesterToken(); + restorePurchases->OnClick.Add([requesterToken, restorePurchases](UI::EventParams &) { + restorePurchases->SetEnabled(false); + INFO_LOG(Log::System, "Requesting purchase restore"); + System_IAPRestorePurchases(requesterToken, [restorePurchases](const char *responseString, int) { + INFO_LOG(Log::System, "Successfully restored purchases!"); + }, []() { + WARN_LOG(Log::System, "Failed restoring purchases"); + }); + return UI::EVENT_DONE; + }); + rightColumnItems->Add(restorePurchases); +} diff --git a/UI/IAPScreen.h b/UI/IAPScreen.h new file mode 100644 index 0000000000..f4143bbd95 --- /dev/null +++ b/UI/IAPScreen.h @@ -0,0 +1,13 @@ +#pragma once + +// NOTE: This is only used on iOS, to present the availablility of getting PPSSPP Gold through IAP. + +#include "ppsspp_config.h" + +#include "UI/MiscScreens.h" + +class IAPScreen : public UIDialogScreenWithBackground { +public: + void CreateViews() override; + const char *tag() const override { return "IAP"; } +}; diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 04fb63ce9f..79facb8cf9 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -57,6 +57,7 @@ #include "UI/GameSettingsScreen.h" #include "UI/MiscScreens.h" #include "UI/ControlMappingScreen.h" +#include "UI/IAPScreen.h" #include "UI/RemoteISOScreen.h" #include "UI/DisplayLayoutScreen.h" #include "UI/SavedataScreen.h" @@ -1579,13 +1580,17 @@ UI::EventReturn MainScreen::OnCredits(UI::EventParams &e) { } void LaunchBuyGold(ScreenManager *screenManager) { + if (System_GetPropertyBool(SYSPROP_USE_IAP)) { + screenManager->push(new IAPScreen()); + } else { #if PPSSPP_PLATFORM(IOS_APP_STORE) - System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://apps.apple.com/us/app/ppsspp-gold-psp-emulator/id6502287918"); + System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://apps.apple.com/us/app/ppsspp-gold-psp-emulator/id6502287918"); #elif PPSSPP_PLATFORM(ANDROID) - System_LaunchUrl(LaunchUrlType::BROWSER_URL, "market://details?id=org.ppsspp.ppssppgold"); + System_LaunchUrl(LaunchUrlType::BROWSER_URL, "market://details?id=org.ppsspp.ppssppgold"); #else - System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/buygold"); + System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/buygold"); #endif + } } UI::EventReturn MainScreen::OnPPSSPPOrg(UI::EventParams &e) { diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index 4f173aee1a..10637b1c08 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -52,6 +52,7 @@ + @@ -99,6 +100,7 @@ + @@ -434,4 +436,4 @@ - \ No newline at end of file + diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index 3c3907aa69..32cee47d56 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -18,6 +18,9 @@ Screens + + + Screens Screens @@ -137,6 +140,9 @@ Screens + + + Screens Screens @@ -250,4 +256,4 @@ {fda6bc55-1386-4650-a274-44fac9605ea3} - \ No newline at end of file + diff --git a/UWP/UI_UWP/UI_UWP.vcxproj b/UWP/UI_UWP/UI_UWP.vcxproj index 90a0746499..e70cf7eb6c 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj +++ b/UWP/UI_UWP/UI_UWP.vcxproj @@ -142,6 +142,7 @@ + @@ -189,6 +190,7 @@ + @@ -218,4 +220,4 @@ - \ No newline at end of file + diff --git a/UWP/UI_UWP/UI_UWP.vcxproj.filters b/UWP/UI_UWP/UI_UWP.vcxproj.filters index bf92220109..455d5a8899 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj.filters +++ b/UWP/UI_UWP/UI_UWP.vcxproj.filters @@ -18,6 +18,7 @@ + @@ -78,6 +79,7 @@ + @@ -123,4 +125,4 @@ {4013cb89-6145-451c-9cb1-d63d01f66bd5} - \ No newline at end of file + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 930e9ef4d6..61c6c126b8 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -908,6 +908,7 @@ LOCAL_SRC_FILES := \ $(SRC)/UI/MainScreen.cpp \ $(SRC)/UI/TabbedDialogScreen.cpp \ $(SRC)/UI/MemStickScreen.cpp \ + $(SRC)/UI/IAPScreen.cpp \ $(SRC)/UI/MiscScreens.cpp \ $(SRC)/UI/RemoteISOScreen.cpp \ $(SRC)/UI/ReportScreen.cpp \ diff --git a/ios/AppDelegate.mm b/ios/AppDelegate.mm index 1d348ab568..a547240c38 100644 --- a/ios/AppDelegate.mm +++ b/ios/AppDelegate.mm @@ -8,6 +8,7 @@ #import "Core/System.h" #import "Core/Config.h" #import "Common/Log.h" +#import "IAPManager.h" #import @@ -89,6 +90,8 @@ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMediaServicesWereReset:) name:AVAudioSessionMediaServicesWereResetNotification object:nil]; + [IAPManager sharedIAPManager]; // Kick off the IAPManager early. + return [self launchPPSSPP:argc argv:argv]; } diff --git a/ios/IAPManager.h b/ios/IAPManager.h new file mode 100644 index 0000000000..af074c9b41 --- /dev/null +++ b/ios/IAPManager.h @@ -0,0 +1,13 @@ +#import +#import + +@interface IAPManager : NSObject + ++ (instancetype)sharedIAPManager; + +- (void)buyGoldWithRequestID:(int)requestID; +- (void)restorePurchasesWithRequestID:(int)requestID; +- (BOOL)isGoldUnlocked; +- (void)startObserving; + +@end diff --git a/ios/IAPManager.mm b/ios/IAPManager.mm new file mode 100644 index 0000000000..9e7d18007d --- /dev/null +++ b/ios/IAPManager.mm @@ -0,0 +1,136 @@ +#import "IAPManager.h" +#import +#include "Common/System/Request.h" +#include "Common/Log.h" + +// Only one operation can be in progress at once. +@implementation IAPManager { + SKProduct *_goldProduct; + int _pendingRequestID; +} + ++ (instancetype)sharedIAPManager { + static IAPManager *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[self alloc] init]; + }); + return shared; +} + +- (instancetype)init { + if (self = [super init]) { + [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; + } + return self; +} + +- (void)startObserving { + [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; +} + +- (BOOL)isGoldUnlocked { + return [[NSUserDefaults standardUserDefaults] boolForKey:@"isGold"]; +} + +- (void)buyGoldWithRequestID:(int)requestID { + if (_pendingRequestID) { + ERROR_LOG(Log::IAP, "A transaction is pending. Failing the new request."); + g_requestManager.PostSystemFailure(requestID); + return; + } + _pendingRequestID = requestID; + + if ([SKPaymentQueue canMakePayments]) { + NSSet *productIds = [NSSet setWithObject:@"org.ppsspp.gold"]; + SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:productIds]; + request.delegate = self; + [request start]; + } else { + NSLog(@"[IAPManager] In-App Purchases are disabled (requestID: %d)", requestID); + g_requestManager.PostSystemFailure(requestID); + } +} + +- (void)restorePurchasesWithRequestID:(int)requestID { + if (_pendingRequestID) { + ERROR_LOG(Log::IAP, "A transaction is pending. Failing the new request."); + g_requestManager.PostSystemFailure(requestID); + return; + } + _pendingRequestID = requestID; + INFO_LOG(Log::IAP, "Restoring purchases (id=%d)", requestID); + // NOTE: This is deprecated, but StoreKit 2 is swift only. We'll keep using it until + // there's a replacement. + [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; +} + +- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { + NSLog(@"Restore completed successfully (requestID: %d)", _pendingRequestID); + g_requestManager.PostSystemSuccess(_pendingRequestID, "", 0); + _pendingRequestID = 0; + // Notify your app/UI here +} + +- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { + NSLog(@"Restore failed (requestID: %d): %@", _pendingRequestID, error.localizedDescription); + // Notify failure to game layer + g_requestManager.PostSystemFailure(_pendingRequestID); + _pendingRequestID = 0; +} + +- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { + _goldProduct = response.products.firstObject; + if (_goldProduct) { + // Received a valid product. Send a payment. + SKPayment *payment = [SKPayment paymentWithProduct:_goldProduct]; + [[SKPaymentQueue defaultQueue] addPayment:payment]; + } else { + NSLog(@"[IAPManager] Gold product not found (requestID: %d)", _pendingRequestID); + g_requestManager.PostSystemFailure(_pendingRequestID); + _pendingRequestID = 0; + } +} + +- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { + for (SKPaymentTransaction *transaction in transactions) { + switch (transaction.transactionState) { + case SKPaymentTransactionStatePurchased: + case SKPaymentTransactionStateRestored: + INFO_LOG(Log::IAP, transaction.transactionState == SKPaymentTransactionStatePurchased ? "IAP Purchase" : "IAP Restore"); + // Perform the unlock (updaing the variable and switching the icon). + [self unlockGold]; + [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; + g_requestManager.PostSystemSuccess(_pendingRequestID, "", 0); + _pendingRequestID = 0; + break; + case SKPaymentTransactionStateFailed: + NSLog(@"[IAPManager] Purchase failed (requestID: %d): %@", _pendingRequestID, transaction.error.localizedDescription); + [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; + // Optionally post failure callback here + g_requestManager.PostSystemFailure(_pendingRequestID); + _pendingRequestID = 0; + break; + default: + break; + } + } +} + +- (void)unlockGold { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isGold"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + if ([[UIApplication sharedApplication] supportsAlternateIcons]) { + [[UIApplication sharedApplication] setAlternateIconName:@"GoldIcon" + completionHandler:^(NSError * _Nullable error) { + if (error) { + NSLog(@"[IAPManager] Failed to set Gold icon: %@", error.localizedDescription); + } + }]; + } + + NSLog(@"[IAPManager] Gold unlocked!"); +} + +@end diff --git a/ios/ViewControllerMetal.mm b/ios/ViewControllerMetal.mm index 54a9f84bc7..38cf6ddf35 100644 --- a/ios/ViewControllerMetal.mm +++ b/ios/ViewControllerMetal.mm @@ -61,13 +61,13 @@ public: bool InitAPI(); bool InitFromRenderThread(CAMetalLayer *layer, int desiredBackbufferSizeX, int desiredBackbufferSizeY); - void ShutdownFromRenderThread(); // Inverses InitFromRenderThread. + void ShutdownFromRenderThread() override; // Inverses InitFromRenderThread. - void Shutdown(); - void Resize(); + void Shutdown() override; + void Resize() override; - void *GetAPIContext() { return g_Vulkan; } - Draw::DrawContext *GetDrawContext() { return draw_; } + void *GetAPIContext() override { return g_Vulkan; } + Draw::DrawContext *GetDrawContext() override { return draw_; } private: VulkanContext *g_Vulkan = nullptr; diff --git a/ios/main.mm b/ios/main.mm index 1792adf40d..6a8f069fde 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -25,6 +25,7 @@ #import "PPSSPPUIApplication.h" #import "ViewController.h" #import "iOSCoreAudio.h" +#import "IAPManager.h" #include "Common/MemoryUtil.h" #include "Common/System/NativeApp.h" @@ -372,11 +373,15 @@ bool System_GetPropertyBool(SystemProperty prop) { // If a hardware keyboard is connected, and we add support, we could return false here. return true; case SYSPROP_APP_GOLD: + // TODO: Check the IAP status. #ifdef GOLD + // This is deprecated. return true; #else - return false; + return [[IAPManager sharedIAPManager] isGoldUnlocked]; #endif + case SYSPROP_USE_IAP: + return true; case SYSPROP_CAN_JIT: return g_jitAvailable; case SYSPROP_LIMITED_FILE_BROWSING: @@ -426,7 +431,7 @@ void System_Notify(SystemNotification notification) { bool System_MakeRequest(SystemRequestType type, int requestId, const std::string ¶m1, const std::string ¶m2, int64_t param3, int64_t param4) { switch (type) { case SystemRequestType::RESTART_APP: - dispatch_async(dispatch_get_main_queue(), ^{ + dispatch_async(dispatch_get_main_queue(), ^{ [(AppDelegate *)[[UIApplication sharedApplication] delegate] restart:param1.c_str()]; }); return true; @@ -511,6 +516,16 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string } return true; } + case SystemRequestType::IAP_RESTORE_PURCHASES: + { + [[IAPManager sharedIAPManager] restorePurchasesWithRequestID:requestId]; + return true; + } + case SystemRequestType::IAP_MAKE_PURCHASE: + { + [[IAPManager sharedIAPManager] buyGoldWithRequestID:requestId]; + return true; + } /* // Not 100% sure the threading is right case SystemRequestType::COPY_TO_CLIPBOARD: @@ -522,10 +537,10 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string } */ case SystemRequestType::SET_KEEP_SCREEN_BRIGHT: - dispatch_async(dispatch_get_main_queue(), ^{ - INFO_LOG(Log::System, "SET_KEEP_SCREEN_BRIGHT: %d", (int)param3); - [[UIApplication sharedApplication] setIdleTimerDisabled: (param3 ? YES : NO)]; - }); + dispatch_async(dispatch_get_main_queue(), ^{ + INFO_LOG(Log::System, "SET_KEEP_SCREEN_BRIGHT: %d", (int)param3); + [[UIApplication sharedApplication] setIdleTimerDisabled: (param3 ? YES : NO)]; + }); return true; default: break;