mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Compare commits
7 Commits
0.6.2
...
xcode-26-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
430ed56ca9 | ||
|
|
c74dc28773 | ||
|
|
5058eb7709 | ||
|
|
242429b161 | ||
|
|
724e8db980 | ||
|
|
669d33183e | ||
|
|
a12b6cd62b |
4
.github/workflows/pr.yml
vendored
4
.github/workflows/pr.yml
vendored
@@ -12,8 +12,8 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: 'macos-14'
|
- os: 'macos-15'
|
||||||
version: '16.1'
|
version: '26.0-beta'
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
2
.github/workflows/stable.yml
vendored
2
.github/workflows/stable.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: 'macos-15'
|
- os: 'macos-15'
|
||||||
version: '16.2'
|
version: '26.0'
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ class MyAppsViewController: UICollectionViewController, PeekPopPreviewing
|
|||||||
var minimuxerStatus: Bool {
|
var minimuxerStatus: Bool {
|
||||||
// added isMinimuxerStatusCheckEnabled to forcefully ignore minimuxer status if status check is disabled in settings
|
// added isMinimuxerStatusCheckEnabled to forcefully ignore minimuxer status if status check is disabled in settings
|
||||||
guard !UserDefaults.standard.isMinimuxerStatusCheckEnabled || minimuxer.ready() else {
|
guard !UserDefaults.standard.isMinimuxerStatusCheckEnabled || minimuxer.ready() else {
|
||||||
ToastView(error: (OperationError.noWiFi as NSError).withLocalizedTitle("No WiFi or VPN!")).show(in: self)
|
ToastView(error: (OperationError.noWiFi as NSError).withLocalizedTitle("No Wi-Fi or VPN!")).show(in: self)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -220,16 +220,16 @@ struct OperationError: ALTLocalizedError {
|
|||||||
case .openAppFailed:
|
case .openAppFailed:
|
||||||
let appName = self.appName ?? NSLocalizedString("The app", comment: "")
|
let appName = self.appName ?? NSLocalizedString("The app", comment: "")
|
||||||
return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), appName)
|
return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), appName)
|
||||||
case .noWiFi: return NSLocalizedString("You do not appear to be connected to WiFi and/or StosVPN!\nSideStore will never be able to install or refresh applications without WiFi and the StosVPN.", comment: "")
|
case .noWiFi: return NSLocalizedString("You do not appear to be connected to Wi-Fi and/or StosVPN!\nSideStore will never be able to install or refresh applications without Wi-Fi and StosVPN.", comment: "")
|
||||||
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled therefore SideStore cannot enable it without SideJITServer at this time, sorry for any inconvenience.\nWe will let everyone know once we have a solution!", comment: "")
|
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled, therefore, SideStore cannot enable JIT without SideJITServer, StikDebug, or SideStore-nightly at this time, sorry for any inconvenience.", comment: "")
|
||||||
case .unableToConnectSideJIT: return NSLocalizedString("Unable to connect to SideJITServer Please check that you are on the Same Wi-Fi and your Firewall has been set correctly", comment: "")
|
case .unableToConnectSideJIT: return NSLocalizedString("Unable to connect to SideJITServer. Please check that you are on the same Wi-Fi of and your Firewall has been set correctly on your server", comment: "")
|
||||||
case .unableToRespondSideJITDevice: return NSLocalizedString("SideJITServer is unable to connect to your iDevice Please make sure you have paired your Device by doing 'SideJITServer -y' or try Refreshing SideJITServer from Settings", comment: "")
|
case .unableToRespondSideJITDevice: return NSLocalizedString("SideJITServer is unable to connect to your iDevice. Please make sure you have paired your Device by running 'SideJITServer -y', or try refreshing SideJITServer from Settings", comment: "")
|
||||||
case .wrongSideJITIP: return NSLocalizedString("Incorrect SideJITServer IP Please make sure that you are on the Samw Wifi as SideJITServer", comment: "")
|
case .wrongSideJITIP: return NSLocalizedString("Incorrect SideJITServer IP. Please make sure that you are on the same Wi-Fi as SideJITServer", comment: "")
|
||||||
case .refreshsidejit: return NSLocalizedString("Unable to find App Please try Refreshing SideJITServer from Settings", comment: "")
|
case .refreshsidejit: return NSLocalizedString("Unable to find app; Please try refreshing SideJITServer from Settings", comment: "")
|
||||||
case .anisetteV1Error: return NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: "")
|
case .anisetteV1Error: return NSLocalizedString("An error occurred while getting anisette data from a V1 server: %@. Try using another anisette server.", comment: "")
|
||||||
case .provisioningError: return NSLocalizedString("An error occurred when provisioning: %@ %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
case .provisioningError: return NSLocalizedString("An error occurred while provisioning: %@ %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||||
case .anisetteV3Error: return NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
case .anisetteV3Error: return NSLocalizedString("An error occurred while getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||||
case .cacheClearError: return NSLocalizedString("An error occurred while clearing cache: %@", comment: "")
|
case .cacheClearError: return NSLocalizedString("An error occurred while clearing the cache: %@", comment: "")
|
||||||
case .SideJITIssue: return NSLocalizedString("An error occurred while using SideJIT: %@", comment: "")
|
case .SideJITIssue: return NSLocalizedString("An error occurred while using SideJIT: %@", comment: "")
|
||||||
|
|
||||||
case .refreshAppFailed:
|
case .refreshAppFailed:
|
||||||
@@ -260,7 +260,7 @@ struct OperationError: ALTLocalizedError {
|
|||||||
var recoverySuggestion: String? {
|
var recoverySuggestion: String? {
|
||||||
switch self.code
|
switch self.code
|
||||||
{
|
{
|
||||||
case .noWiFi: return NSLocalizedString("Make sure the VPN is toggled on and you are connected to any WiFi network!", comment: "")
|
case .noWiFi: return NSLocalizedString("Make sure StosVPN is toggled on and you are connected to any Wi-Fi network!", comment: "")
|
||||||
case .serverNotFound: return NSLocalizedString("Make sure you're on the same Wi-Fi network as a computer running AltServer, or try connecting this device to your computer via USB.", comment: "")
|
case .serverNotFound: return NSLocalizedString("Make sure you're on the same Wi-Fi network as a computer running AltServer, or try connecting this device to your computer via USB.", comment: "")
|
||||||
case .maximumAppIDLimitReached:
|
case .maximumAppIDLimitReached:
|
||||||
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
||||||
@@ -308,7 +308,7 @@ extension MinimuxerError: LocalizedError {
|
|||||||
case .NoDevice:
|
case .NoDevice:
|
||||||
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
||||||
case .NoConnection:
|
case .NoConnection:
|
||||||
return NSLocalizedString("Unable to connect to the device, make sure StosVPN is enabled and you're connected to WiFi. This could mean an invalid pairing.", comment: "")
|
return NSLocalizedString("Unable to connect to the device, make sure StosVPN is enabled and you're connected to Wi-Fi. This could mean an invalid pairing.", comment: "")
|
||||||
case .PairingFile:
|
case .PairingFile:
|
||||||
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use idevice_pair to regenerate it", comment: "")
|
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use idevice_pair to regenerate it", comment: "")
|
||||||
|
|
||||||
|
|||||||
@@ -380,13 +380,8 @@ private extension ErrorLogViewController
|
|||||||
|
|
||||||
func searchFAQ(for loggedError: LoggedError)
|
func searchFAQ(for loggedError: LoggedError)
|
||||||
{
|
{
|
||||||
let baseURL = URL(string: "https://faq.altstore.io/getting-started/error-codes")!
|
let staticURL = URL(string: "https://docs.sidestore.io/docs/troubleshooting/error-codes")!
|
||||||
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)!
|
let safariViewController = SFSafariViewController(url: staticURL)
|
||||||
|
|
||||||
let query = [loggedError.domain, "\(loggedError.error.displayCode)"].joined(separator: "+")
|
|
||||||
components.queryItems = [URLQueryItem(name: "q", value: query)]
|
|
||||||
|
|
||||||
let safariViewController = SFSafariViewController(url: components.url ?? baseURL)
|
|
||||||
safariViewController.preferredControlTintColor = .altPrimary
|
safariViewController.preferredControlTintColor = .altPrimary
|
||||||
self.present(safariViewController, animated: true)
|
self.present(safariViewController, animated: true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ final class AboutPatreonHeaderView: UICollectionReusableView
|
|||||||
imageView.layer.cornerRadius = imageView.bounds.midY
|
imageView.layer.cornerRadius = imageView.bounds.midY
|
||||||
}
|
}
|
||||||
|
|
||||||
for button in [self.supportButton, self.accountButton].compactMap({$0})
|
for button in [self.supportButton, self.accountButton, self.twitterButton, self.instagramButton].compactMap({$0})
|
||||||
{
|
{
|
||||||
button.clipsToBounds = true
|
button.clipsToBounds = true
|
||||||
button.layer.cornerRadius = 16
|
button.layer.cornerRadius = 16
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Configuration settings file format documentation can be found at:
|
// Configuration settings file format documentation can be found at:
|
||||||
// https://help.apple.com/xcode/#/dev745c5c974
|
// https://help.apple.com/xcode/#/dev745c5c974
|
||||||
|
|
||||||
MARKETING_VERSION = 0.6.2
|
MARKETING_VERSION = 0.6.3
|
||||||
CURRENT_PROJECT_VERSION = 0602
|
CURRENT_PROJECT_VERSION = 0603
|
||||||
|
|
||||||
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
||||||
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ Why iOS 15? Targeting such a recent version of iOS allows us to accelerate devel
|
|||||||
SideStore is a just regular, sandboxed iOS application. The AltStore app target contains the vast majority of SideStore's functionality, including all the logic for downloading and updating apps through SideStore. SideStore makes heavy use of standard iOS frameworks and technologies most iOS developers are familiar with.
|
SideStore is a just regular, sandboxed iOS application. The AltStore app target contains the vast majority of SideStore's functionality, including all the logic for downloading and updating apps through SideStore. SideStore makes heavy use of standard iOS frameworks and technologies most iOS developers are familiar with.
|
||||||
|
|
||||||
### EM Proxy
|
### EM Proxy
|
||||||
[SideServer mobile](https://github.com/jkcoxson/em_proxy) powers the defining feature of SideStore: untethered app installation. By leveraging an App Store app with additional entitlements (WireGuard or StosVPN) to create the VPN tunnel for us, it allows SideStore to take advantage of [Jitterbug](https://github.com/osy/Jitterbug)'s loopback method without requiring a paid developer account.
|
[SideServer mobile](https://github.com/jkcoxson/em_proxy) powers the defining feature of SideStore: untethered app installation. By leveraging a custom-built App Store app with additional entitlements (StosVPN) to create the VPN tunnel for us, it allows SideStore to take advantage of [Jitterbug](https://github.com/osy/Jitterbug)'s loopback method without requiring a paid developer account.
|
||||||
|
|
||||||
### Minimuxer
|
### Minimuxer
|
||||||
[Minimuxer](https://github.com/jkcoxson/minimuxer) is a lockdown muxer that can run inside iOS’s sandbox. It replicates Apple’s usbmuxd protocol on MacOS to “discover” devices to interface with wireguard On-Device.
|
[Minimuxer](https://github.com/jkcoxson/minimuxer) is a lockdown muxer that can run inside iOS’s sandbox. It replicates Apple’s usbmuxd protocol on MacOS to “discover” devices to interface with StosVPN On-Device.
|
||||||
|
|
||||||
### Roxas
|
### Roxas
|
||||||
[Roxas](https://github.com/rileytestut/roxas) is Riley Testut's internal framework from AltStore used across many of their iOS projects, developed to simplify a variety of common tasks used in iOS development.
|
[Roxas](https://github.com/rileytestut/roxas) is Riley Testut's internal framework from AltStore used across many of their iOS projects, developed to simplify a variety of common tasks used in iOS development.
|
||||||
|
|||||||
@@ -59,48 +59,97 @@ public class AbstractConsoleLogger<T: OutputStream>: ConsoleLogger{
|
|||||||
originalStdout = dup(STDOUT_FILENO)
|
originalStdout = dup(STDOUT_FILENO)
|
||||||
originalStderr = dup(STDERR_FILENO)
|
originalStderr = dup(STDERR_FILENO)
|
||||||
|
|
||||||
|
let redirectedOutStream = self.outPipe?.fileHandleForWriting.fileDescriptor ?? -1
|
||||||
|
let redirectedErrStream = self.errPipe?.fileHandleForWriting.fileDescriptor ?? -1
|
||||||
|
|
||||||
// Redirect stdout and stderr to our pipes
|
// Redirect stdout and stderr to our pipes
|
||||||
dup2(self.outPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDOUT_FILENO)
|
dup2(redirectedOutStream, STDOUT_FILENO)
|
||||||
dup2(self.errPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDERR_FILENO)
|
dup2(redirectedErrStream, STDERR_FILENO)
|
||||||
|
|
||||||
|
// Disable libc-level buffering
|
||||||
|
// (libc by default uses bufferring except its own console/TTYs such as for pipes)
|
||||||
|
// we do have our own buffering so we disable stdlib io level bufferring
|
||||||
|
setvbuf(stdout, nil, _IONBF, 0) // disable buffering for stdout
|
||||||
|
setvbuf(stderr, nil, _IONBF, 0) // disable buffering for stderr
|
||||||
|
|
||||||
// Setup readability handlers for raw data
|
// Setup readability handlers for raw data
|
||||||
setupReadabilityHandler(for: outputHandle, isError: false)
|
setupReadabilityHandler(for: outputHandle, isError: false)
|
||||||
setupReadabilityHandler(for: errorHandle, isError: true)
|
setupReadabilityHandler(for: errorHandle, isError: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let shutdownLock = NSLock()
|
||||||
|
|
||||||
private func setupReadabilityHandler(for handle: FileHandle?, isError: Bool) {
|
private func setupReadabilityHandler(for handle: FileHandle?, isError: Bool) {
|
||||||
handle?.readabilityHandler = { [weak self] handle in
|
handle?.readabilityHandler = readHandler(isError: isError)
|
||||||
let data = handle.availableData
|
}
|
||||||
if !data.isEmpty {
|
|
||||||
self?.writeQueue.async {
|
private func readHandler(isError: Bool) -> (FileHandle) -> Void {
|
||||||
try? self?.writeData(data)
|
return { [weak self] _ in
|
||||||
}
|
// Lock first before touching anything
|
||||||
|
self?.shutdownLock.lock()
|
||||||
// Forward to original std stream
|
defer { self?.shutdownLock.unlock() }
|
||||||
if let originalFD = isError ? self?.originalStderr : self?.originalStdout {
|
|
||||||
data.withUnsafeBytes { (bufferPointer) -> Void in
|
// Capture strong self *after* lock is acquired
|
||||||
if let baseAddress = bufferPointer.baseAddress, bufferPointer.count > 0 {
|
guard let self = self else { return }
|
||||||
write(originalFD, baseAddress, bufferPointer.count)
|
|
||||||
}
|
let handle = isError ? self.errorHandle : self.outputHandle
|
||||||
|
guard let data = handle?.availableData else { return }
|
||||||
|
|
||||||
|
writeQueue.async {
|
||||||
|
try? self.writeData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Echo to original stdout/stderr if still valid
|
||||||
|
guard let fd = isError ? self.originalStderr : self.originalStdout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String ?? "UnknownApp"
|
||||||
|
guard fcntl(fd, F_GETFD) != -1 else {
|
||||||
|
NSLog("[%@] ConsoleLogger: Original FD (%d) is invalid, skipping echo", appName, fd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data.withUnsafeBytes { rawBufferPointer in
|
||||||
|
guard let base = rawBufferPointer.baseAddress else { return }
|
||||||
|
var remaining = data.count
|
||||||
|
var offset = 0
|
||||||
|
let maxChunkSize = 16 * 1024 // 16 KB chunks
|
||||||
|
|
||||||
|
// write in chunks, else will throw 'Result too large'
|
||||||
|
while remaining > 0 {
|
||||||
|
let chunkSize = min(maxChunkSize, remaining)
|
||||||
|
let written = write(fd, base.advanced(by: offset), chunkSize)
|
||||||
|
|
||||||
|
if written < 0 {
|
||||||
|
NSLog("[%@] ConsoleLogger: Failed to re-echo to FD %d: %s", appName, fd, strerror(errno))
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remaining -= written
|
||||||
|
offset += written
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func writeData(_ data: Data) throws {
|
func writeData(_ data: Data) throws {
|
||||||
throw AbstractClassError.abstractMethodInvoked
|
throw AbstractClassError.abstractMethodInvoked
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopCapturing() {
|
func stopCapturing() {
|
||||||
|
shutdownLock.lock()
|
||||||
|
defer { shutdownLock.unlock() }
|
||||||
|
|
||||||
ostream.close()
|
ostream.close()
|
||||||
|
|
||||||
// Restore original stdout and stderr
|
// Restore original stdout and stderr
|
||||||
if let stdout = originalStdout {
|
if let stdout = originalStdout, stdout != STDOUT_FILENO {
|
||||||
dup2(stdout, STDOUT_FILENO)
|
dup2(stdout, STDOUT_FILENO)
|
||||||
close(stdout)
|
close(stdout)
|
||||||
}
|
}
|
||||||
if let stderr = originalStderr {
|
if let stderr = originalStderr, stderr != STDERR_FILENO {
|
||||||
dup2(stderr, STDERR_FILENO)
|
dup2(stderr, STDERR_FILENO)
|
||||||
close(stderr)
|
close(stderr)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user