Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
10 changes: 4 additions & 6 deletions PCL.Mac.Core/Minecraft/MinecraftInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public class MinecraftInstance {
self.manifest = manifest
self.config = config
VersionCache.add(version: version, for: self)
if config.javaURL == nil {
setJava(url: searchJava().map(\.executableURL))
}
}

/// 设置 JVM Heap Size 并保存。
Expand Down Expand Up @@ -116,6 +119,7 @@ public class MinecraftInstance {
do {
manifest = try JSONDecoder.shared.decode(ClientManifest.self, from: Data(contentsOf: manifestURL))
} catch {
err("加载客户端清单失败:\(error)")
throw MinecraftError.unknownManifestFormat
}
// 获取版本
Expand Down Expand Up @@ -152,12 +156,6 @@ public class MinecraftInstance {
manifest: manifest,
config: config ?? .init()
)
if !FileManager.default.fileExists(atPath: configURL.path) {
instance.saveConfig()
}
if instance.config.javaURL == nil {
instance.setJava(url: instance.searchJava()?.executableURL)
}
return instance
}

Expand Down
29 changes: 19 additions & 10 deletions PCL.Mac.Core/Minecraft/MinecraftRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class MinecraftRepository: ObservableObject, Codable, Hashable, Equatable
@Published public var name: String
@Published public var url: URL
@Published public var instances: [MinecraftInstance]?
@Published public var errorInstances: [ErrorInstance]?

public lazy var assetsURL: URL = { url.appending(path: "assets") }()
public lazy var librariesURL: URL = { url.appending(path: "libraries") }()
Expand All @@ -34,22 +35,20 @@ public class MinecraftRepository: ObservableObject, Codable, Hashable, Equatable

/// 加载该仓库中的所有实例。
/// 只会在读取目录失败时抛出错误。
@discardableResult
public func load() throws -> [MinecraftInstance] {
let instances = try getInstanceList()
public func load() throws {
let (instances, errorInstances) = try getInstanceList()
self.instances = instances
return instances
self.errorInstances = errorInstances
}

/// 异步加载该仓库中的所有实例。
/// 只会在读取目录失败时抛出错误。
@discardableResult
public func loadAsync() async throws -> [MinecraftInstance] {
let instances: [MinecraftInstance] = try getInstanceList()
public func loadAsync() async throws {
let (instances, errorInstances) = try getInstanceList()
await MainActor.run {
self.instances = instances
self.errorInstances = errorInstances
}
return instances
}

/// 从仓库中加载实例。
Expand All @@ -60,22 +59,27 @@ public class MinecraftRepository: ObservableObject, Codable, Hashable, Equatable
}


private func getInstanceList() throws -> [MinecraftInstance] {
private func getInstanceList() throws -> ([MinecraftInstance], [ErrorInstance]) {
try createDirectories()
var instances: [MinecraftInstance] = []
var errorInstances: [ErrorInstance] = []
let contents: [URL] = try FileManager.default.contentsOfDirectory(at: versionsURL, includingPropertiesForKeys: [.isDirectoryKey])
for content in contents where try content.resourceValues(forKeys: [.isDirectoryKey]).isDirectory ?? false {
let instance: MinecraftInstance
do {
log("正在加载实例 \(content.lastPathComponent)")
instance = try MinecraftInstance.load(from: content)
} catch MinecraftError.unknownManifestFormat {
err("加载实例失败:不支持的客户端清单格式。")
errorInstances.append(.init(name: content.lastPathComponent, message: "不支持的客户端清单格式。"))
continue
} catch {
err("加载实例失败:\(error.localizedDescription)")
continue
}
instances.append(instance)
}
return instances
return (instances, errorInstances)
}

public static func == (lhs: MinecraftRepository, rhs: MinecraftRepository) -> Bool {
Expand All @@ -100,4 +104,9 @@ public class MinecraftRepository: ObservableObject, Codable, Hashable, Equatable
try container.encode(self.name, forKey: .name)
try container.encode(self.url, forKey: .url)
}

public struct ErrorInstance {
public let name: String
public let message: String
}
}
99 changes: 89 additions & 10 deletions PCL.Mac.Core/Models/ClientManifest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,78 @@ public class ClientManifest: Decodable {
public let mainClass: String
public let type: String

public let inheritsFrom: String?

private enum CodingKeys: String, CodingKey {
case arguments, assetIndex, downloads, id, javaVersion, libraries, logging, mainClass, type
case minecraftArguments
case inheritsFrom
}

private enum ArgumentsCodingKeys: String, CodingKey {
case game, jvm
}

public init(
gameArguments: [Argument],
jvmArguments: [Argument],
assetIndex: AssetIndex,
downloads: Downloads,
id: String,
javaVersion: JavaVersion,
libraries: [Library],
logging: Logging,
mainClass: String,
type: String,
inheritsFrom: String?
) {
self.gameArguments = gameArguments
self.jvmArguments = jvmArguments
self.assetIndex = assetIndex
self.downloads = downloads
self.id = id
self.javaVersion = javaVersion
self.libraries = libraries
self.logging = logging
self.mainClass = mainClass
self.type = type
self.inheritsFrom = inheritsFrom
}

public required init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let argumentsContainer = try container.nestedContainer(keyedBy: ArgumentsCodingKeys.self, forKey: .arguments)
self.gameArguments = try argumentsContainer.decode([Argument].self, forKey: .game)
self.jvmArguments = try argumentsContainer.decode([Argument].self, forKey: .jvm)
if container.contains(.minecraftArguments) { // 1.12-
self.gameArguments = try container.decode(String.self, forKey: .minecraftArguments).split(separator: " ").map { .init(value: [String($0)], rules: []) }
self.jvmArguments = [
"-XX:+UnlockExperimentalVMOptions", "-XX:+UseG1GC", "-XX:-UseAdaptiveSizePolicy", "-XX:-OmitStackTraceInFastThrow",
"-Djava.library.path=${natives_directory}",
"-Dorg.lwjgl.system.SharedLibraryExtractPath=${natives_directory}",
"-Dio.netty.native.workdir=${natives_directory}",
"-Djna.tmpdir=${natives_directory}",
"-cp", "${classpath}"
].map { .init(value: [$0], rules: []) }
} else {
let argumentsContainer = try container.nestedContainer(keyedBy: ArgumentsCodingKeys.self, forKey: .arguments)
self.gameArguments = try argumentsContainer.decode([Argument].self, forKey: .game)
self.jvmArguments = try argumentsContainer.decode([Argument].self, forKey: .jvm)
}
self.assetIndex = try container.decode(AssetIndex.self, forKey: .assetIndex)
self.downloads = try container.decode(Downloads.self, forKey: .downloads)
self.id = try container.decode(String.self, forKey: .id)
self.javaVersion = try container.decode(JavaVersion.self, forKey: .javaVersion)
self.javaVersion = try container.decodeIfPresent(JavaVersion.self, forKey: .javaVersion) ?? .init(component: "jre-legacy", majorVersion: 8)
self.libraries = try container.decode([Library].self, forKey: .libraries)
self.logging = try container.decode(Logging.self, forKey: .logging)
self.logging = try container.decodeIfPresent(Logging.self, forKey: .logging) ?? .init(
argument: "-Dlog4j.configurationFile=${path}",
file: .init(
id: "client-1.12.xml",
url: URL(string: "https://piston-data.mojang.com/v1/objects/bd65e7d2e3c237be76cfbef4c2405033d7f91521/client-1.12.xml")!,
size: 888,
sha1: "bd65e7d2e3c237be76cfbef4c2405033d7f91521"
)
)
self.mainClass = try container.decode(String.self, forKey: .mainClass)
self.type = try container.decode(String.self, forKey: .type)
self.inheritsFrom = try container.decodeIfPresent(String.self, forKey: .inheritsFrom)
}

public class Argument: Decodable {
Expand All @@ -67,6 +118,11 @@ public class ClientManifest: Decodable {
self.rules = try container.decode([ArgumentRule].self, forKey: .rules)
}
}

public init(value: [String], rules: [ArgumentRule]) {
self.value = value
self.rules = rules
}
}

public class Artifact: Decodable {
Expand Down Expand Up @@ -94,7 +150,7 @@ public class ClientManifest: Decodable {
public class Downloads: Decodable {
public let client: Download
public let clientMappings: Download?
public let server: Download
public let server: Download?
public let serverMappings: Download?

private enum CodingKeys: String, CodingKey {
Expand Down Expand Up @@ -133,13 +189,20 @@ public class ClientManifest: Decodable {
}()
public lazy var isRulesSatisfied: Bool = { rules.allSatisfy { $0.test() } }()

public init(name: String, artifact: Artifact?, rules: [Rule], isNativeLibrary: Bool) {
self.name = name
self.artifact = artifact
self.rules = rules
self.isNativesLibrary = isNativeLibrary
}

public required init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.isNativesLibrary = container.contains(.natives)
let downloadsContainer = try? container.nestedContainer(keyedBy: DownloadsCodingKeys.self, forKey: .downloads)
if !isNativesLibrary {
self.artifact = try downloadsContainer.unwrap().decode(Artifact.self, forKey: .artifact)
self.artifact = try downloadsContainer.unwrap("该支持库没有 artifact。").decode(Artifact.self, forKey: .artifact)
} else {
let natives: [String: String] = try container.decode([String: String].self, forKey: .natives)
if let key = natives["osx"] {
Expand All @@ -160,6 +223,11 @@ public class ClientManifest: Decodable {
private enum CodingKeys: String, CodingKey { case client }
private enum ClientCodingKeys: String, CodingKey { case argument, file }

public init(argument: String, file: File) {
self.argument = argument
self.file = file
}

public required init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self).nestedContainer(keyedBy: ClientCodingKeys.self, forKey: .client)
self.argument = try container.decode(String.self, forKey: .argument)
Expand Down Expand Up @@ -217,9 +285,10 @@ public class ClientManifest: Decodable {
/// - Parameter options: 生成参数时使用的 `LaunchOptions`。
/// - Returns: 一个布尔值,表示是否通过。
public func test(with options: LaunchOptions) -> Bool {
guard super.test() else { return false }
for (name, value) in features {
if name == "is_demo_user" && value != options.demo {
return false
return !allow
}
if [
"has_custom_resolution",
Expand All @@ -228,16 +297,21 @@ public class ClientManifest: Decodable {
"is_quick_play_multiplayer",
"is_quick_play_realms"
].contains(name) && value { // not implemented
return false
return !allow
}
}
return true
return allow
}
}

public class JavaVersion: Decodable {
public let component: String
public let majorVersion: Int

public init(component: String, majorVersion: Int) {
self.component = component
self.majorVersion = majorVersion
}
}

/// 获取所有可用的普通依赖库。
Expand All @@ -251,4 +325,9 @@ public class ClientManifest: Decodable {
public func getNatives() -> [Library] {
return libraries.filter { $0.isNativesLibrary && $0.isRulesSatisfied }
}

/// 创建一个新清单,继承本清单的所有属性,并使用指定的 libraries。
public func setLibraries(to libraries: [Library]) -> ClientManifest {
return .init(gameArguments: gameArguments, jvmArguments: jvmArguments, assetIndex: assetIndex, downloads: downloads, id: id, javaVersion: javaVersion, libraries: libraries, logging: logging, mainClass: mainClass, type: type, inheritsFrom: inheritsFrom)
}
}
3 changes: 1 addition & 2 deletions PCL.Mac.Core/Models/VersionManifest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ public struct VersionManifest: Decodable {
/// - Parameter id: 版本号。
/// - Returns: 在 `versions` 中的顺序。
public func ordinal(of id: String) -> Int {
guard let index = versions.firstIndex(where: { $0.id == id }) else { return -1 }
return versions.count - index
return versions.firstIndex(where: { $0.id == id }) ?? -1
}

/// 获取版本号对应的 `Version` 对象。
Expand Down
6 changes: 4 additions & 2 deletions PCL.Mac.Core/Services/JavaSearcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public enum JavaSearcher {
runtimes.append(runtime)
} catch {
err("加载 Java 失败:\(error.localizedDescription)")
debug("homeDirectory:\(homeDirectory.path)")
}
}
return runtimes
Expand All @@ -52,13 +53,14 @@ public enum JavaSearcher {
throw JavaError.failedToParseReleaseFile
}
let release: [String: String] = parseProperties(releaseContent)
guard let javaVersion = release["JAVA_VERSION"],
let implementor = release["IMPLEMENTOR"] else {
guard let javaVersion = release["JAVA_VERSION"] else {
throw JavaError.failedToParseReleaseFile
}
guard let versionMajor: Int = parseVersionNumber(javaVersion) else {
throw JavaError.failedToParseVersionNumber(version: javaVersion)
}
let implementor: String = release["IMPLEMENTOR"] ?? "Unknown"

// Java 类型判断
var type: JavaRuntime.JavaType?
var executableURL: URL?
Expand Down
Loading