mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
IAP work
This commit is contained in:
1 parent
445de0b5f4
commit
2dbb54667d
22 files changed
+322
-24
No files matched your search
+5
-1
@@ -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)"
|
||||
)
|
||||
|
||||
@@ -53,6 +53,7 @@ enum class Log {
|
||||
TexReplacement,
|
||||
GeDebugger,
|
||||
UI,
|
||||
IAP,
|
||||
|
||||
sceAudio,
|
||||
sceCtrl,
|
||||
|
||||
@@ -92,6 +92,7 @@ static const char * const g_logTypeNames[] = {
|
||||
"TEXREPLACE",
|
||||
"DEBUGGER",
|
||||
"UI",
|
||||
"IAP",
|
||||
"SCEAUDIO",
|
||||
"SCECTRL",
|
||||
"SCEDISP",
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+14
-1
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 &) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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<UIScreen>(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);
|
||||
}
|
||||
@@ -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"; }
|
||||
};
|
||||
+8
-3
@@ -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) {
|
||||
|
||||
+3
-1
@@ -52,6 +52,7 @@
|
||||
<ClCompile Include="GameScreen.cpp" />
|
||||
<ClCompile Include="GameSettingsScreen.cpp" />
|
||||
<ClCompile Include="GPUDriverTestScreen.cpp" />
|
||||
<ClCompile Include="IAPScreen.cpp" />
|
||||
<ClCompile Include="ImDebugger\ImConsole.cpp" />
|
||||
<ClCompile Include="ImDebugger\ImDebugger.cpp" />
|
||||
<ClCompile Include="ImDebugger\ImDisasmView.cpp" />
|
||||
@@ -99,6 +100,7 @@
|
||||
<ClInclude Include="GameSettingsScreen.h" />
|
||||
<ClInclude Include="CwCheatScreen.h" />
|
||||
<ClInclude Include="GPUDriverTestScreen.h" />
|
||||
<ClInclude Include="IAPScreen.h" />
|
||||
<ClInclude Include="ImDebugger\ImConsole.h" />
|
||||
<ClInclude Include="ImDebugger\ImDebugger.h" />
|
||||
<ClInclude Include="ImDebugger\ImDisasmView.h" />
|
||||
@@ -434,4 +436,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -18,6 +18,9 @@
|
||||
</ClCompile>
|
||||
<ClCompile Include="MiscScreens.cpp">
|
||||
<Filter>Screens</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="IAPScreen.cpp">
|
||||
<Filter>Screens</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ControlMappingScreen.cpp">
|
||||
<Filter>Screens</Filter>
|
||||
@@ -137,6 +140,9 @@
|
||||
</ClInclude>
|
||||
<ClInclude Include="MiscScreens.h">
|
||||
<Filter>Screens</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IAPScreen.h">
|
||||
<Filter>Screens</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ControlMappingScreen.h">
|
||||
<Filter>Screens</Filter>
|
||||
@@ -250,4 +256,4 @@
|
||||
<UniqueIdentifier>{fda6bc55-1386-4650-a274-44fac9605ea3}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -142,6 +142,7 @@
|
||||
<ClInclude Include="..\..\UI\MainScreen.h" />
|
||||
<ClInclude Include="..\..\UI\MemStickScreen.h" />
|
||||
<ClInclude Include="..\..\UI\MiscScreens.h" />
|
||||
<ClInclude Include="..\..\UI\IAPScreen.h" />
|
||||
<ClInclude Include="..\..\UI\OnScreenDisplay.h" />
|
||||
<ClInclude Include="..\..\UI\PauseScreen.h" />
|
||||
<ClInclude Include="..\..\UI\ProfilerDraw.h" />
|
||||
@@ -189,6 +190,7 @@
|
||||
<ClCompile Include="..\..\UI\MainScreen.cpp" />
|
||||
<ClCompile Include="..\..\UI\MemStickScreen.cpp" />
|
||||
<ClCompile Include="..\..\UI\MiscScreens.cpp" />
|
||||
<ClCompile Include="..\..\UI\IAPScreen.cpp" />
|
||||
<ClCompile Include="..\..\UI\NativeApp.cpp" />
|
||||
<ClCompile Include="..\..\UI\OnScreenDisplay.cpp" />
|
||||
<ClCompile Include="..\..\UI\PauseScreen.cpp" />
|
||||
@@ -218,4 +220,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -18,6 +18,7 @@
|
||||
<ClCompile Include="..\..\UI\MainScreen.cpp" />
|
||||
<ClCompile Include="..\..\UI\MemStickScreen.cpp" />
|
||||
<ClCompile Include="..\..\UI\MiscScreens.cpp" />
|
||||
<ClCompile Include="..\..\UI\IAPScreen.cpp" />
|
||||
<ClCompile Include="..\..\UI\NativeApp.cpp" />
|
||||
<ClCompile Include="..\..\UI\OnScreenDisplay.cpp" />
|
||||
<ClCompile Include="..\..\UI\PauseScreen.cpp" />
|
||||
@@ -78,6 +79,7 @@
|
||||
<ClInclude Include="..\..\UI\MainScreen.h" />
|
||||
<ClInclude Include="..\..\UI\MemStickScreen.h" />
|
||||
<ClInclude Include="..\..\UI\MiscScreens.h" />
|
||||
<ClInclude Include="..\..\UI\IAPScreen.h" />
|
||||
<ClInclude Include="..\..\UI\OnScreenDisplay.h" />
|
||||
<ClInclude Include="..\..\UI\PauseScreen.h" />
|
||||
<ClInclude Include="..\..\UI\ProfilerDraw.h" />
|
||||
@@ -123,4 +125,4 @@
|
||||
<UniqueIdentifier>{4013cb89-6145-451c-9cb1-d63d01f66bd5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -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 \
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#import "Core/System.h"
|
||||
#import "Core/Config.h"
|
||||
#import "Common/Log.h"
|
||||
#import "IAPManager.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <StoreKit/StoreKit.h>
|
||||
|
||||
@interface IAPManager : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
|
||||
|
||||
+ (instancetype)sharedIAPManager;
|
||||
|
||||
- (void)buyGoldWithRequestID:(int)requestID;
|
||||
- (void)restorePurchasesWithRequestID:(int)requestID;
|
||||
- (BOOL)isGoldUnlocked;
|
||||
- (void)startObserving;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,136 @@
|
||||
#import "IAPManager.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
#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<SKPaymentTransaction *> *)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
|
||||
@@ -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;
|
||||
|
||||
+21
-6
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user