Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 5 additions & 20 deletions PCL.Mac.Core/Minecraft/Launch/MinecraftLauncher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class MinecraftLauncher {
"library_directory": librariesURL.path,

"auth_player_name": options.profile.name,
"version_name": manifest.id,
"version_name": options.runningDirectory.lastPathComponent,
"game_directory": runningDirectory.path,
"assets_root": librariesURL.deletingLastPathComponent().appending(path: "assets").path,
"assets_index_name": manifest.assetIndex.id,
Expand All @@ -54,21 +54,14 @@ public class MinecraftLauncher {
arguments.append(contentsOf: manifest.jvmArguments.flatMap { $0.rules.allSatisfy { $0.test(with: options) } ? $0.value : [] })
arguments.append(manifest.mainClass)
arguments.append(contentsOf: manifest.gameArguments.flatMap { $0.rules.allSatisfy { $0.test(with: options) } ? $0.value : [] })
arguments = arguments.map(replaceWithValue(_:))
arguments = arguments.map { Utils.replace($0, withValues: values) }
process.arguments = arguments

// accessToken 打码
// arguments 不会再被使用了,可以直接修改
if let accessTokenIndex: Int = arguments.firstIndex(of: "--accessToken"),
accessTokenIndex + 1 < arguments.count {
arguments[accessTokenIndex + 1] = "🥚"
}

let pipe: Pipe = .init()
process.standardOutput = pipe
process.standardError = pipe

log("正在使用以下参数启动 Minecraft:\(arguments)")
log("正在使用以下参数启动 Minecraft:\(arguments.map { $0 == options.accessToken ? "🥚" : $0 })")
try process.run()
Self.gameLogQueue.async {
FileManager.default.createFile(atPath: self.logURL.path, contents: nil)
Expand All @@ -92,20 +85,12 @@ public class MinecraftLauncher {

private func buildClasspath() -> String {
var urls: [URL] = []
for library in manifest.libraries {
if library.isRulesSatisfied, let artifact = library.artifact {
for library in manifest.getLibraries() {
if let artifact = library.artifact {
urls.append(librariesURL.appending(path: artifact.path))
}
}
urls.append(runningDirectory.appending(path: "\(runningDirectory.lastPathComponent).jar"))
return urls.map(\.path).joined(separator: ":")
}

private func replaceWithValue(_ string: String) -> String {
var s: String = string
for key in values.keys {
s = s.replacingOccurrences(of: "${\(key)}", with: values[key]!)
}
return s
}
}
26 changes: 16 additions & 10 deletions PCL.Mac.Core/Minecraft/MinecraftInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
import Foundation
import SwiftyJSON

public class MinecraftInstance {
public class MinecraftInstance: Equatable {
private static let configFileName: String = ".clconfig.json"
public let runningDirectory: URL
public let version: MinecraftVersion
public let manifest: ClientManifest
public let config: Config
public let modLoader: ModLoader?

public var name: String { runningDirectory.lastPathComponent }
public var manifestURL: URL { runningDirectory.appending(path: "\(name).json") }
Expand All @@ -26,11 +27,13 @@ public class MinecraftInstance {
/// - version: 实例的 Minecraft 版本。
/// - manifest: 客户端清单。
/// - config: 实例配置。
public init(runningDirectory: URL, version: MinecraftVersion, manifest: ClientManifest, config: Config) {
/// - modLoader: 实例安装的模组加载器。
public init(runningDirectory: URL, version: MinecraftVersion, manifest: ClientManifest, config: Config, modLoader: ModLoader?) {
self.runningDirectory = runningDirectory
self.version = version
self.manifest = manifest
self.config = config
self.modLoader = modLoader
VersionCache.add(version: version, for: self)
if config.javaURL == nil {
setJava(url: searchJava().map(\.executableURL))
Expand Down Expand Up @@ -114,16 +117,13 @@ public class MinecraftInstance {
/// - version: (可选)缓存的版本号。
/// - Returns: 实例对象。
public static func load(from runningDirectory: URL) throws -> MinecraftInstance {
if FileManager.default.fileExists(atPath: runningDirectory.appending(path: ".incomplete").path) {
throw MinecraftError.incomplete
}
// 加载客户端清单
let manifestURL: URL = runningDirectory.appending(path: "\(runningDirectory.lastPathComponent).json")
guard FileManager.default.fileExists(atPath: manifestURL.path) else { throw MinecraftError.missingManifest }
let manifest: ClientManifest
do {
manifest = try JSONDecoder.shared.decode(ClientManifest.self, from: Data(contentsOf: manifestURL))
} catch {
err("加载客户端清单失败:\(error)")
throw MinecraftError.unknownManifestFormat
}
let (manifest, modLoader): (ClientManifest, ModLoader?) = try ClientManifest.load(at: manifestURL)
// 获取版本
let version: MinecraftVersion
if let cachedVersion = VersionCache.version(of: manifestURL) {
Expand All @@ -135,6 +135,8 @@ public class MinecraftInstance {
let json: JSON = try? JSON(data: ArchiveUtils.getEntry(url: jarURL, path: "version.json")) {
log("成功解析 version.json")
version = .init(json["id"].stringValue)
} else if let clVersion: String = manifest.version {
version = .init(clVersion)
} else {
warn("\(jarURL.lastPathComponent)!/version.json 不存在或解析失败,使用客户端清单中的 id 作为版本号")
version = .init(manifest.id)
Expand All @@ -156,11 +158,15 @@ public class MinecraftInstance {
runningDirectory: runningDirectory,
version: version,
manifest: manifest,
config: config ?? .init()
config: config ?? .init(),
modLoader: modLoader
)
return instance
}

public static func == (lhs: MinecraftInstance, rhs: MinecraftInstance) -> Bool {
lhs.runningDirectory == rhs.runningDirectory
}

public class Config: Codable {
public var jvmHeapSize: UInt64
Expand Down
9 changes: 9 additions & 0 deletions PCL.Mac.Core/Minecraft/MinecraftRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ public class MinecraftRepository: ObservableObject, Codable, Hashable, Equatable
err("加载实例失败:不支持的客户端清单格式。")
errorInstances.append(.init(name: content.lastPathComponent, message: "不支持的客户端清单格式。"))
continue
} catch MinecraftError.incomplete {
log("实例未完成安装,正在尝试自动删除")
do {
try FileManager.default.removeItem(at: content)
} catch {
err("删除失败:\(error.localizedDescription)")
errorInstances.append(.init(name: content.lastPathComponent, message: "该实例未完成安装,且自动删除失败。"))
}
continue
} catch {
err("加载实例失败:\(error.localizedDescription)")
continue
Expand Down
19 changes: 19 additions & 0 deletions PCL.Mac.Core/Minecraft/ModLoader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// ModLoader.swift
// PCL.Mac
//
// Created by 温迪 on 2026/2/11.
//

import Foundation

public enum ModLoader: Int, CustomStringConvertible {
case fabric, forge

public var description: String {
switch self {
case .fabric: "Fabric"
case .forge: "Forge"
}
}
}
Loading