v1.4: fixed settings issue, implemented popup on data reload, blocked store review

This commit is contained in:
eevee
2024-04-29 19:39:02 +03:00
parent 3f60185f06
commit f11e0cbb38
9 changed files with 227 additions and 59 deletions
@@ -0,0 +1,12 @@
import Orion
import UIKit
import StoreKit
// uncomment if you're building for jailbreak, cause ipa crashes:
// Fatal error: Error in tweak EeveeSpotify: Failed to hook method -[SKStoreReviewController requestReview:] (Could not hook method)
// Fatal error: Error in tweak EeveeSpotify: Failed to hook method -[SKStoreReviewController requestReviewInScene:] (Could not hook method)
// class SKStoreReviewControllerHook: ClassHook<SKStoreReviewController> {
// func requestReview() { }
// func requestReviewInScene(_ scene: UIWindowScene) { }
// }
+63
View File
@@ -0,0 +1,63 @@
import Foundation
class OfflineHelper {
static let persistentCachePath = FileManager.default.urls(
for: .applicationSupportDirectory, in: .userDomainMask
)
.first!
.appendingPathComponent("PersistentCache")
//
static var offlineBnkPath: URL {
persistentCachePath.appendingPathComponent("offline.bnk")
}
static var eeveeBnkPath: URL {
persistentCachePath.appendingPathComponent("eevee.bnk")
}
static var offlineBnkData: Data {
get throws { try Data(contentsOf: offlineBnkPath) }
}
static var eeveeBnkData: Data {
get throws { try Data(contentsOf: eeveeBnkPath) }
}
//
private static func writeOfflineBnkData(_ data: Data) throws {
try data.write(to: offlineBnkPath)
}
private static func writeEeveeBnkData(_ data: Data) throws {
try data.write(to: eeveeBnkPath)
}
//
static func restoreFromEeveeBnk() throws {
try writeOfflineBnkData(try eeveeBnkData)
}
static func backupToEeveeBnk() throws {
try writeEeveeBnkData(try offlineBnkData)
}
static func patchOfflineBnk() throws {
let fileData = try offlineBnkData
let usernameLength = Int(fileData[8])
let username = Data(fileData[9..<9 + usernameLength])
var blankData = try BundleHelper.shared.premiumBlankData()
blankData.insert(UInt8(usernameLength), at: 8)
blankData.insert(contentsOf: username, at: 9)
try writeOfflineBnkData(blankData)
}
}
+39 -24
View File
@@ -13,35 +13,50 @@ class PopUpHelper {
.shared()
static func showPopUp(
delayed: Bool = false,
message: String,
buttonText: String
buttonText: String,
secondButtonText: String? = nil,
onPrimaryClick: (() -> Void)? = nil,
onSecondaryClick: (() -> Void)? = nil
) {
if isPopUpShowing {
return
}
DispatchQueue.main.asyncAfter(deadline: delayed ? .now() + 3.0 : .now()) {
if isPopUpShowing {
return
}
let model = Dynamic.SPTEncorePopUpDialogModel
.alloc(interface: SPTEncorePopUpDialogModel.self)
.initWithTitle(
"EeveeSpotify",
description: message,
image: nil,
primaryButtonTitle: buttonText,
secondaryButtonTitle: nil
)
let model = Dynamic.SPTEncorePopUpDialogModel
.alloc(interface: SPTEncorePopUpDialogModel.self)
.initWithTitle(
"EeveeSpotify",
description: message,
image: nil,
primaryButtonTitle: buttonText,
secondaryButtonTitle: secondButtonText
)
let dialog = Dynamic.SPTEncorePopUpDialog
.alloc(interface: SPTEncorePopUpDialog.self)
.`init`()
dialog.update(model)
dialog.setEventHandler({ state in
switch (state) {
case .primary: onPrimaryClick?()
case .secondary: onSecondaryClick?()
}
sharedPresenter.dismissPopupWithAnimate(true, clearQueue: false, completion: nil)
isPopUpShowing.toggle()
})
let dialog = Dynamic.SPTEncorePopUpDialog
.alloc(interface: SPTEncorePopUpDialog.self)
.`init`()
dialog.update(model)
dialog.setEventHandler({
sharedPresenter.dismissPopupWithAnimate(true, clearQueue: false, completion: nil)
isPopUpShowing.toggle()
})
sharedPresenter.presentPopUp(dialog)
isPopUpShowing.toggle()
sharedPresenter.presentPopUp(dialog)
}
}
}
@@ -0,0 +1,19 @@
import Orion
class HookedInstances {
static var productState: SPTCoreProductState?
}
class SPTCoreProductStateInstanceHook: ClassHook<NSObject> {
static let targetName = "SPTCoreProductState"
func stringForKey(_ key: String) -> String {
HookedInstances.productState = Dynamic.convert(
target,
to: SPTCoreProductState.self
)
return orig.stringForKey(key)
}
}
@@ -0,0 +1,5 @@
import Foundation
@objc protocol SPTCoreProductState {
func stringForKey(_ key: String) -> String
}
@@ -3,5 +3,10 @@ import Foundation
@objc protocol SPTEncorePopUpDialog {
func `init`() -> SPTEncorePopUpDialog
func update(_ popUpModel: SPTEncorePopUpDialogModel)
func setEventHandler(_ handler: @escaping () -> Void)
func setEventHandler(_ handler: @escaping (_ state: ClickState) -> Void)
}
@objc enum ClickState: Int {
case primary
case secondary
}
@@ -0,0 +1,52 @@
import Foundation
import UIKit
func exitApplication() {
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { _ in
exit(EXIT_SUCCESS)
}
}
class OfflineObserver: NSObject, NSFilePresenter {
var presentedItemURL: URL?
var presentedItemOperationQueue: OperationQueue
override init() {
presentedItemURL = OfflineHelper.offlineBnkPath
presentedItemOperationQueue = .main
}
func presentedItemDidChange() {
let productState = HookedInstances.productState!
if productState.stringForKey("player-license") == "premium" {
do {
try OfflineHelper.backupToEeveeBnk()
NSLog("[EeveeSpotify] Settings has changed, updated eevee.bnk")
}
catch {
NSLog("[EeveeSpotify] Unable to update eevee.bnk: \(error)")
}
return
}
PopUpHelper.showPopUp(
message: "Spotify has just reloaded user data, and you've been switched to the Free plan. It's fine; simply restart the app, and the tweak will patch the data again. If this doesn't work, there might be a problem with the saved data. You can reset it and restart the app. Note: after resetting, you need to restart the app twice.",
buttonText: "Restart App",
secondButtonText: "Reset Data and Restart App",
onPrimaryClick: {
exitApplication()
},
onSecondaryClick: {
try! FileManager.default.removeItem(at: OfflineHelper.persistentCachePath)
exitApplication()
}
)
}
}
+30 -33
View File
@@ -27,52 +27,49 @@ struct EeveeSpotify: Tweak {
do {
let filePath = FileManager.default.urls(
for: .applicationSupportDirectory, in: .userDomainMask
)
.first!
.appendingPathComponent("PersistentCache")
.appendingPathComponent("offline.bnk")
defer {
NSFileCoordinator.addFilePresenter(OfflineObserver())
}
if !FileManager.default.fileExists(atPath: filePath.path) {
do {
try OfflineHelper.restoreFromEeveeBnk()
NSLog("[EeveeSpotify] Restored from eevee.bnk")
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
PopUpHelper.showPopUp(
message: "Please log in and restart the app to get Premium.",
buttonText: "Okay!"
)
}
NSLog("[EeveeSpotify] Not activating due to nonexistent file: \(filePath)")
return
}
let fileData = try Data(contentsOf: filePath)
catch CocoaError.fileReadNoSuchFile {
NSLog("[EeveeSpotify] Not restoring from eevee.bnk: doesn't exist")
}
let usernameLength = Int(fileData[8])
let username = Data(fileData[9..<9 + usernameLength])
//
var blankData = try BundleHelper.shared.premiumBlankData()
do {
try OfflineHelper.patchOfflineBnk()
try OfflineHelper.backupToEeveeBnk()
}
blankData.insert(UInt8(usernameLength), at: 8)
blankData.insert(contentsOf: username, at: 9)
catch CocoaError.fileReadNoSuchFile {
try blankData.write(to: filePath)
NSLog("[EeveeSpotify] Successfully applied")
NSLog("[EeveeSpotify] Not activating: offline.bnk doesn't exist")
PopUpHelper.showPopUp(
delayed: true,
message: "Please log in and restart the app to get Premium.",
buttonText: "Okay!"
)
}
}
catch {
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
PopUpHelper.showPopUp(
message: "Unable to apply tweak: \(error)",
buttonText: "OK"
)
}
NSLog("[EeveeSpotify] Unable to apply tweak: \(error)")
PopUpHelper.showPopUp(
delayed: true,
message: "Unable to apply tweak: \(error)",
buttonText: "OK"
)
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
Package: com.eevee.spotify
Name: EeveeSpotify
Version: 1.3
Version: 1.4
Architecture: iphoneos-arm
Description: A tweak to get Spotify Premium for free, just like Spotilife
Maintainer: Eevee