refactor: config to versioned schema and auto-load matching tweaks

This commit is contained in:
Sunny Young 2025-12-14 01:00:56 +08:00
parent ebdcb7bbd2
commit 7ce0a4ea25
8 changed files with 323 additions and 62 deletions

View File

@ -23,8 +23,8 @@ struct Command {
try await Command.execute(command: "defaults read \(app.appendingPathComponent("Contents/Info.plist").path) CFBundleVersion")
}
static func patch(app: URL, config: Config) async throws {
try Patcher.patch(binary: app.appendingPathComponent("Contents/MacOS/WeChat"), config: config)
static func patch(app: URL, targets: [Target]) async throws {
try Patcher.patch(binary: app.appendingPathComponent("Contents/MacOS/WeChat"), entries: targets.flatMap { $0.entries })
}
static func resign(app: URL) async throws {

View File

@ -17,12 +17,11 @@ struct Patcher {
case noArchMatched
}
static func patch(binary: URL, config: Config) throws {
static func patch(binary: URL, entries: [Target.Entry]) throws {
guard FileManager.default.fileExists(atPath: binary.path) else {
throw Error.invalidFile
}
let entries = config.targets.flatMap { $0.entries }
guard !entries.isEmpty else { throw Error.noArchMatched }
let fh = try FileHandle(forUpdating: binary)

View File

@ -1,29 +1,28 @@
//
// Config.swift
// Target.swift
// WeChatTweak
//
// Created by Sunny Young on 2025/12/5.
//
import Foundation
import MachO
struct Config: Decodable {
enum Arch: String, Decodable {
case arm64
case x86_64
struct Target: Decodable {
struct Entry: Decodable {
enum Arch: String, Decodable {
case arm64
case x86_64
var cpu: UInt32 {
switch self {
case .arm64:
return UInt32(CPU_TYPE_ARM64)
case .x86_64:
return UInt32(CPU_TYPE_X86_64)
var cpu: UInt32 {
switch self {
case .arm64:
return UInt32(CPU_TYPE_ARM64)
case .x86_64:
return UInt32(CPU_TYPE_X86_64)
}
}
}
}
struct Entry: Decodable {
let arch: Arch
let addr: UInt64
let asm: Data
@ -62,38 +61,8 @@ struct Config: Decodable {
}
}
struct Target: Decodable {
let identifier: String
let entries: [Entry]
private enum CodingKeys: CodingKey {
case identifier
case entries
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.identifier = try container.decode(String.self, forKey: .identifier)
self.entries = try container.decode([Entry].self, forKey: .entries)
}
}
let version: String
let targets: [Target]
static func load(url: URL) async throws -> [Config] {
if url.isFileURL {
return try JSONDecoder().decode(
[Config].self,
from: Data(contentsOf: url)
)
} else {
return try JSONDecoder().decode(
[Config].self,
from: try await URLSession.shared.data(from: url).0
)
}
}
let identifier: String
let entries: [Entry]
}
private extension Data {

View File

@ -20,7 +20,7 @@ extension Tweak {
print("------ Current version ------")
print(try await Command.version(app: options.app) ?? "unknown")
print("------ Supported versions ------")
try await Config.load(url: options.config).forEach({ print($0.version) })
try await Tweak.versions().forEach { print($0) }
Darwin.exit(EXIT_SUCCESS)
}
}
@ -36,25 +36,21 @@ extension Tweak {
mutating func run() async throws {
print("------ Version ------")
let version = try await Command.version(app: options.app)
print("WeChat version: \(version ?? "unknown")")
print("------ Config ------")
guard let config = (try await Config.load(url: options.config)).first(where: { $0.version == version }) else {
guard let version = try await Command.version(app: self.options.app), let targets = try await Tweak.load(version: version) else {
throw Error.unsupportedVersion
}
print("Matched config: \(config)")
print("\(version)")
print("------ Patch ------")
try await Command.patch(
app: options.app,
config: config
app: self.options.app,
targets: targets
)
print("Done!")
print("------ Resign ------")
try await Command.resign(
app: options.app
app: self.options.app
)
print("Done!")
@ -101,7 +97,7 @@ struct Tweak: AsyncParsableCommand {
@Option(
name: .shortAndLong,
help: "Local path or Remote URL of config.json",
help: "Local path or Remote URL of config (default: derived from app version)",
transform: {
if FileManager.default.fileExists(atPath: $0) {
return URL(fileURLWithPath: $0)
@ -113,7 +109,7 @@ struct Tweak: AsyncParsableCommand {
}
}
)
var config: URL = URL(string:"https://raw.githubusercontent.com/sunnyyoung/WeChatTweak/refs/heads/master/config.json")!
var config: URL? = nil
}
static let configuration = CommandConfiguration(
@ -131,6 +127,35 @@ struct Tweak: AsyncParsableCommand {
}
}
extension Tweak {
static func load(url: URL) async throws -> [Target] {
let data = try await {
if url.isFileURL {
return try Data(contentsOf: url)
} else {
return try await URLSession.shared.data(from: url).0
}
}()
return try JSONDecoder().decode([Target].self, from: data)
}
static func load(version: String) async throws -> [Target]? {
guard try await Tweak.versions().contains(version) else {
return nil
}
return try await Self.load(url: URL(string: "https://raw.githubusercontent.com/sunnyyoung/WeChatTweak/master/Versions/\(version).json")!)
}
static func versions() async throws -> [String] {
let data = try await URLSession.shared.data(
for: URLRequest(
url: URL(string: "https://api.github.com/repos/sunnyyoung/WeChatTweak/contents/Versions")!
)
)
return (try JSONSerialization.jsonObject(with: data.0) as? [[String: Any]])?.compactMap { ($0["name"] as? NSString)?.deletingPathExtension } ?? []
}
}
Task {
await Tweak.main()
}

82
Versions/31927.json Normal file
View File

@ -0,0 +1,82 @@
[
{
"identifier": "revoke",
"entries": [
{
"arch": "arm64",
"addr": "103dba3d0",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "startUpdater",
"entries": [
{
"arch": "arm64",
"addr": "1001ea41c",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "startBackgroundUpdatesCheck",
"entries": [
{
"arch": "arm64",
"addr": "1001ed3b4",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "checkForUpdates",
"entries": [
{
"arch": "arm64",
"addr": "1001ecf9c",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "enableAutoUpdate",
"entries": [
{
"arch": "arm64",
"addr": "1001ed920",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "automaticallyDownloadsUpdates",
"entries": [
{
"arch": "arm64",
"addr": "1001f6ee8",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "canCheckForUpdate",
"entries": [
{
"arch": "arm64",
"addr": "1001f6ef8",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "multiInstance",
"entries": [
{
"arch": "arm64",
"addr": "1001e1c10",
"asm": "20008052C0035FD6"
}
]
}
]

22
Versions/31960.json Normal file
View File

@ -0,0 +1,22 @@
[
{
"identifier": "revoke",
"entries": [
{
"arch": "arm64",
"addr": "10408a408",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "multiInstance",
"entries": [
{
"arch": "arm64",
"addr": "1001e4a38",
"asm": "20008052C0035FD6"
}
]
}
]

82
Versions/32281.json Normal file
View File

@ -0,0 +1,82 @@
[
{
"identifier": "revoke",
"entries": [
{
"arch": "arm64",
"addr": "103db33e0",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "startUpdater",
"entries": [
{
"arch": "arm64",
"addr": "1001e9ed0",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "startBackgroundUpdatesCheck",
"entries": [
{
"arch": "arm64",
"addr": "1001ecb10",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "checkForUpdates",
"entries": [
{
"arch": "arm64",
"addr": "1001ec73c",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "enableAutoUpdate",
"entries": [
{
"arch": "arm64",
"addr": "1001ecfc0",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "automaticallyDownloadsUpdates",
"entries": [
{
"arch": "arm64",
"addr": "1001f59e0",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "canCheckForUpdate",
"entries": [
{
"arch": "arm64",
"addr": "1001f59e0",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "multiInstance",
"entries": [
{
"arch": "arm64",
"addr": "1001e1a74",
"asm": "20008052C0035FD6"
}
]
}
]

82
Versions/32288.json Normal file
View File

@ -0,0 +1,82 @@
[
{
"identifier": "revoke",
"entries": [
{
"arch": "arm64",
"addr": "103db34c0",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "startUpdater",
"entries": [
{
"arch": "arm64",
"addr": "1001e9ed0",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "startBackgroundUpdatesCheck",
"entries": [
{
"arch": "arm64",
"addr": "1001ecb10",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "checkForUpdates",
"entries": [
{
"arch": "arm64",
"addr": "1001ec73c",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "enableAutoUpdate",
"entries": [
{
"arch": "arm64",
"addr": "1001ecfc0",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "automaticallyDownloadsUpdates",
"entries": [
{
"arch": "arm64",
"addr": "1001f59e0",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "canCheckForUpdate",
"entries": [
{
"arch": "arm64",
"addr": "1001f59e0",
"asm": "00008052C0035FD6"
}
]
},
{
"identifier": "multiInstance",
"entries": [
{
"arch": "arm64",
"addr": "1001e1a74",
"asm": "20008052C0035FD6"
}
]
}
]