Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f52acef
feat: 添加 SwiftScaffolding 库与 SwiftScaffolding 和 EasyTier 的 LICENSE 文件
AnemoFlower Jan 15, 2026
56c697d
feat: 联机页面
AnemoFlower Jan 15, 2026
83d4cd9
feat: 联机界面 ViewModel 与 EasyTierManager
AnemoFlower Jan 16, 2026
4edac52
feat: 联机中心启动逻辑
AnemoFlower Jan 17, 2026
27b3592
Merge branch 'main' into feature/multiplayer
AnemoFlower Jan 17, 2026
c042b71
Merge remote-tracking branch 'origin/main' into feature/multiplayer
AnemoFlower Jan 26, 2026
5244ad8
chore: 整理目录结构
AnemoFlower Jan 26, 2026
e58b870
refactor: EasyTierManager
AnemoFlower Jan 26, 2026
54e99d4
feat: 优化错误处理
AnemoFlower Jan 26, 2026
332a187
feat: 加入房间逻辑与错误处理优化
AnemoFlower Jan 27, 2026
3dcc20a
chore: update package
AnemoFlower Jan 27, 2026
7b5f434
feat: 优化界面
AnemoFlower Jan 27, 2026
a4f9f87
feat: 为部分方法添加 \@MainActor,减少 Task 数量,提升性能
AnemoFlower Jan 28, 2026
2b6d5ae
feat: 添加提示卡片 自动复制邀请码和本地地址
AnemoFlower Jan 28, 2026
da70166
feat: 由用户输入多人游戏端口
AnemoFlower Jan 28, 2026
99d0096
feat: 调整操作卡片布局 添加图标
AnemoFlower Jan 28, 2026
1333c7a
feat: 免责声明提示
AnemoFlower Jan 28, 2026
b721643
chore: update package
AnemoFlower Jan 29, 2026
54fc856
chore: Update package
AnemoFlower Jan 30, 2026
ecee102
feat: 房间验活
AnemoFlower Jan 30, 2026
0b744aa
feat: 主动检查是否安装了 EasyTier
AnemoFlower Jan 30, 2026
c8ef1ac
chore: Update package
AnemoFlower Jan 30, 2026
774bc76
feat: 架构判断
AnemoFlower Jan 30, 2026
ab6a614
feat: 通过扩展协议实现强制关闭违规房间
AnemoFlower Jan 30, 2026
7f5c504
chore: Update package
AnemoFlower Feb 1, 2026
136e1f9
chore: 修改按钮描述
AnemoFlower Feb 1, 2026
3867fa6
feat: 离线账号提示
AnemoFlower Feb 1, 2026
d412d05
feat: EasyTier 移除
AnemoFlower Feb 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions PCL.Mac.Core/Task/MyTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,7 @@ public class MyTask<Model: TaskModel>: ObservableObject, Identifiable {
private let model: Model
private var cancellables: [AnyCancellable] = []

/// 创建一个任务。
/// - Parameters:
/// - name: 任务名。
/// - model: 任务模型,用于在子任务间共享数据。
/// - subTasks: 该任务的子任务列表。
public init(name: String, model: Model, _ subTasks: SubTask...) {
private init(name: String, model: Model, _ subTasks: [SubTask]) {
self.name = name
self.model = model
self.subTasks = subTasks
Expand All @@ -47,6 +42,15 @@ public class MyTask<Model: TaskModel>: ObservableObject, Identifiable {
}
}

/// 创建一个任务。
/// - Parameters:
/// - name: 任务名。
/// - model: 任务模型,用于在子任务间共享数据。
/// - subTasks: 该任务的子任务列表。
public convenience init(name: String, model: Model, _ subTasks: SubTask...) {
self.init(name: name, model: model, subTasks)
}

/// 开始按顺序执行任务。
/// 执行时,会按 `ordinal` 将 `subTasks` 分组,`ordinal` 越小的越先执行。
public func start() async throws {
Expand Down Expand Up @@ -137,6 +141,16 @@ public class MyTask<Model: TaskModel>: ObservableObject, Identifiable {
}
}

extension MyTask where Model == EmptyModel {
/// 当任务不需要共享模型数据时的便捷构造函数。
/// - Parameters:
/// - name: 任务名。
/// - subTasks: 子任务列表。
public convenience init(name: String, _ subTasks: SubTask...) {
self.init(name: name, model: EmptyModel(), subTasks)
}
}

public enum SubTaskState {
case waiting, executing, finished, failed
}
8 changes: 8 additions & 0 deletions PCL.Mac.Core/Utils/Architecture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,12 @@ public enum Architecture: String {
default: return .unknown
}
}

public static func systemArchitecture() -> Architecture {
#if arch(arm64)
return .arm64
#elseif arch(x86_64)
return .x64
#endif
}
}
4 changes: 4 additions & 0 deletions PCL.Mac.Core/Utils/FileUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ public enum FileUtils {
let digest: Insecure.SHA1.Digest = hasher.finalize()
return digest.map { String(format: "%02x", $0) }.joined()
}

public static func isExecutable(at url: URL) -> Bool {
return Architecture.architecture(of: url) != .unknown
}
}
1 change: 1 addition & 0 deletions PCL.Mac.Core/Utils/Requests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public enum Requests {
var request: URLRequest = .init(url: url)
request.httpMethod = method
request.allHTTPHeaderFields = headers
request.setValue("PCL-Mac/0.1.1", forHTTPHeaderField: "User-Agent")
if noCache {
request.cachePolicy = .reloadIgnoringLocalCacheData
}
Expand Down
2 changes: 2 additions & 0 deletions PCL.Mac.Core/Utils/URLConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ public struct URLConstants {
public static let cacheURL: URL = applicationSupportURL.appending(path: "Caches")
public static let temperatureURL: URL = applicationSupportURL.appending(path: "Temp")
public static let authlibInjectorURL: URL = applicationSupportURL.appending(path: "authlib-injector.jar")
public static let easyTierURL: URL = applicationSupportURL.appending(path: "EasyTier")

public static func createDirectories() {
let fileManager: FileManager = .default
try? fileManager.createDirectory(at: applicationSupportURL, withIntermediateDirectories: true)
try? fileManager.createDirectory(at: logsDirectoryURL, withIntermediateDirectories: true)
try? fileManager.createDirectory(at: cacheURL, withIntermediateDirectories: true)
try? fileManager.createDirectory(at: temperatureURL, withIntermediateDirectories: true)
try? fileManager.createDirectory(at: easyTierURL, withIntermediateDirectories: true)
}
}
24 changes: 24 additions & 0 deletions PCL.Mac.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

/* Begin PBXBuildFile section */
EC114A4A2ED05F070027201F /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = EC114A492ED05F070027201F /* SwiftyJSON */; };
EC3785B02F2F55650008B483 /* SwiftScaffolding in Frameworks */ = {isa = PBXBuildFile; productRef = EC3785AF2F2F55650008B483 /* SwiftScaffolding */; };
EC971F4B2F2CB23E006CFFF0 /* SwiftScaffolding in Frameworks */ = {isa = PBXBuildFile; productRef = EC971F4A2F2CB23E006CFFF0 /* SwiftScaffolding */; };
EC996B442EBEE80F0094E524 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC996B3C2EBEE80F0094E524 /* Core.framework */; };
EC996B462EBEE80F0094E524 /* Core.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EC996B3C2EBEE80F0094E524 /* Core.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
ECE995B32ED6BE2E00E86582 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = ECE995B22ED6BE2E00E86582 /* ZIPFoundation */; };
Expand Down Expand Up @@ -116,6 +118,8 @@
buildActionMask = 2147483647;
files = (
ECF707982ED8291F001F0C71 /* SwiftyJSON in Frameworks */,
EC3785B02F2F55650008B483 /* SwiftScaffolding in Frameworks */,
EC971F4B2F2CB23E006CFFF0 /* SwiftScaffolding in Frameworks */,
EC996B442EBEE80F0094E524 /* Core.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -237,6 +241,8 @@
name = PCL.Mac;
packageProductDependencies = (
EC114A472ED05F020027201F /* SwiftyJSON */,
EC971F4A2F2CB23E006CFFF0 /* SwiftScaffolding */,
EC3785AF2F2F55650008B483 /* SwiftScaffolding */,
);
productName = PCL.Mac;
productReference = ECC26BC32EBEDD67002B7E3E /* PCL.Mac.app */;
Expand Down Expand Up @@ -278,6 +284,7 @@
packageReferences = (
EC114A452ED05EF50027201F /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
ECE995B12ED6BE2E00E86582 /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
EC3785AE2F2F55650008B483 /* XCRemoteSwiftPackageReference "SwiftScaffolding" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = ECC26BC42EBEDD67002B7E3E /* Products */;
Expand Down Expand Up @@ -701,6 +708,14 @@
minimumVersion = 5.0.2;
};
};
EC3785AE2F2F55650008B483 /* XCRemoteSwiftPackageReference "SwiftScaffolding" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/CeciliaStudio/SwiftScaffolding";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.1.1;
};
};
ECE995B12ED6BE2E00E86582 /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/weichsel/ZIPFoundation";
Expand All @@ -727,6 +742,15 @@
package = EC114A452ED05EF50027201F /* XCRemoteSwiftPackageReference "SwiftyJSON" */;
productName = SwiftyJSON;
};
EC3785AF2F2F55650008B483 /* SwiftScaffolding */ = {
isa = XCSwiftPackageProductDependency;
package = EC3785AE2F2F55650008B483 /* XCRemoteSwiftPackageReference "SwiftScaffolding" */;
productName = SwiftScaffolding;
};
EC971F4A2F2CB23E006CFFF0 /* SwiftScaffolding */ = {
isa = XCSwiftPackageProductDependency;
productName = SwiftScaffolding;
};
ECE995B22ED6BE2E00E86582 /* ZIPFoundation */ = {
isa = XCSwiftPackageProductDependency;
package = ECE995B12ED6BE2E00E86582 /* XCRemoteSwiftPackageReference "ZIPFoundation" */;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 13 additions & 4 deletions PCL.Mac/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,29 @@
import Foundation
import AppKit
import Core
import SwiftScaffolding

class AppDelegate: NSObject, NSApplicationDelegate {
private var window: AppWindow!

private func executeTask(_ name: String, _ start: @escaping () throws -> Void) {
private func executeTask(_ name: String, silent: Bool = false, _ start: @escaping () throws -> Void) {
do {
try start()
log("\(name)成功")
if !silent {
log("\(name)成功")
}
} catch {
err("\(name)失败:\(error.localizedDescription)")
}
}

private func executeAsyncTask(_ name: String, _ start: @escaping () async throws -> Void) {
private func executeAsyncTask(_ name: String, silent: Bool = false, _ start: @escaping () async throws -> Void) {
Task {
do {
try await start()
log("\(name)成功")
if !silent {
log("\(name)成功")
}
} catch {
err("\(name)失败:\(error.localizedDescription)")
}
Expand All @@ -36,6 +41,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
URLConstants.createDirectories()
LogManager.shared.enableLogging()
log("正在启动 PCL.Mac.Refactor \(Metadata.appVersion)")
executeTask("开启 SwiftScaffolding 日志", silent: true) {
try SwiftScaffolding.Logger.enableLogging(url: URLConstants.logsDirectoryURL.appending(path: "swift-scaffolding.log"))
}
_ = LauncherConfig.shared
executeTask("加载版本缓存") {
try VersionCache.load()
Expand Down Expand Up @@ -81,5 +89,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
executeTask("保存启动器配置") {
try LauncherConfig.save()
}
EasyTierManager.shared.easyTier.terminate()
}
}
9 changes: 9 additions & 0 deletions PCL.Mac/App/AppRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ enum AppRoute: Identifiable, Hashable, Equatable {
// 下载页面的子页面
case minecraftDownload, downloadPage2, downloadPage3

// 联机页面的子页面
case multiplayerSub, multiplayerSettings

// 更多页面的子页面
case about

Expand Down Expand Up @@ -54,6 +57,10 @@ class AppRouter: ObservableObject {
InstanceListPage(repository: repository)
case .noInstanceRepository:
NoInstanceRepositoryPage()
case .multiplayerSub:
MultiplayerPage()
case .multiplayerSettings:
MultiplayerSettingsPage()
case .about:
AboutPage()
default:
Expand All @@ -67,6 +74,7 @@ class AppRouter: ObservableObject {
case .launch: LaunchSidebar()
case .instanceList, .noInstanceRepository: InstanceListSidebar()
case .minecraftDownload, .downloadPage2, .downloadPage3: DownloadSidebar()
case .multiplayer, .multiplayerSub, .multiplayerSettings: MultiplayerSidebar()
case .more, .about: MoreSidebar()
case .tasks: TasksSidebar()
default: EmptySidebar()
Expand Down Expand Up @@ -107,6 +115,7 @@ class AppRouter: ObservableObject {
// 各根页面的默认子页面
if newRoot == .download { append(.minecraftDownload) }
if newRoot == .more { append(.about) }
if newRoot == .multiplayer { append(.multiplayerSub) }
}

func append(_ route: AppRoute) {
Expand Down
1 change: 1 addition & 0 deletions PCL.Mac/App/AppWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class AppWindow: NSWindow {
.environmentObject(InstanceViewModel())
.environmentObject(MinecraftDownloadPageViewModel())
.environmentObject(InstanceListViewModel())
.environmentObject(MultiplayerViewModel())
)

self.setFrameAutosaveName("AppWindow")
Expand Down
4 changes: 4 additions & 0 deletions PCL.Mac/App/LauncherConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class LauncherConfig: Codable {
public var currentInstance: String?
public var accounts: [Account] = []
public var currentAccountId: UUID?
public var multiplayerDisclaimerAgreed: Bool = false

public init() {}

Expand All @@ -58,6 +59,7 @@ class LauncherConfig: Codable {
self.currentAccountId = accounts[0].id
}
}
self.multiplayerDisclaimerAgreed = try container.decodeIfPresent(Bool.self, forKey: .multiplayerDisclaimerAgreed) ?? false
}

public func encode(to encoder: any Encoder) throws {
Expand All @@ -67,6 +69,7 @@ class LauncherConfig: Codable {
try container.encode(currentInstance, forKey: .currentInstance)
try container.encode(accounts.map(AccountWrapper.init(_:)), forKey: .accounts)
try container.encode(currentAccountId, forKey: .currentAccountId)
try container.encode(multiplayerDisclaimerAgreed, forKey: .multiplayerDisclaimerAgreed)
}

public static func save(_ config: LauncherConfig = .shared, to url: URL = URLConstants.configURL) throws {
Expand All @@ -80,5 +83,6 @@ class LauncherConfig: Codable {
case currentInstance
case accounts
case currentAccountId
case multiplayerDisclaimerAgreed
}
}
25 changes: 25 additions & 0 deletions PCL.Mac/Assets.xcassets/Icons/IconCopy.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"images" : [
{
"filename" : "IconCopy.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
4 changes: 4 additions & 0 deletions PCL.Mac/Assets.xcassets/Icons/IconCopy.imageset/IconCopy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions PCL.Mac/Assets.xcassets/Icons/IconExit.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"images" : [
{
"filename" : "IconExit.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
4 changes: 4 additions & 0 deletions PCL.Mac/Assets.xcassets/Icons/IconExit.imageset/IconExit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions PCL.Mac/Components/MyCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ struct MyCard<Content: View>: View {
private let title: String
private let foldable: Bool
private let titled: Bool
private let limitHeight: Bool
private let content: () -> Content

init(_ title: String, foldable: Bool = true, titled: Bool = true, @ViewBuilder _ content: @escaping () -> Content) {
init(_ title: String, foldable: Bool = true, titled: Bool = true, limitHeight: Bool = true, @ViewBuilder _ content: @escaping () -> Content) {
self.title = title
self.foldable = foldable && titled
self.titled = titled
self.limitHeight = limitHeight
self.content = content
}

Expand Down Expand Up @@ -93,7 +95,8 @@ struct MyCard<Content: View>: View {
}
}
}
.frame(height: internalContentHeight, alignment: .top)
.frame(height: limitHeight ? internalContentHeight : nil, alignment: .top)
.frame(maxHeight: limitHeight ? nil : .infinity)
.clipped()
.opacity(showContent ? 1 : 0)
}
Expand Down
Loading