Files
Leeksov 4647310322 GLEGram 12.5 — Initial public release
Based on Swiftgram 12.5 (Telegram iOS 12.5).
All GLEGram features ported and organized in GLEGram/ folder.

Features: Ghost Mode, Saved Deleted Messages, Content Protection Bypass,
Font Replacement, Fake Profile, Chat Export, Plugin System, and more.

See CHANGELOG_12.5.md for full details.
2026-04-06 09:48:12 +03:00

176 lines
9.1 KiB
Swift
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// MARK: Swiftgram Extract .dylib from .deb packages (Cydia-style tweaks)
import Foundation
import Compression
/// Result of installing a .deb: package name/version (if parsed) and list of installed .dylib filenames.
public struct DebInstallResult {
public let packageName: String?
public let packageVersion: String?
public let installedDylibs: [String]
}
/// Extracts .dylib files from a .deb and installs them into TweakLoader's directory.
public enum DebExtractor {
private static let arMagic = "!<arch>\n"
private static let arMagicData = Data(arMagic.utf8)
/// Install .deb: extract data archive, find all .dylib, copy to Tweaks directory.
/// Supports data.tar.gz and data.tar.lzma. Returns installed dylib filenames or throws.
public static func installDeb(from url: URL, tweaksDirectory: URL) throws -> DebInstallResult {
let data = try Data(contentsOf: url)
let (controlName, controlVersion) = parseControl(from: data)
let dataTar = try extractDataTar(from: data)
let (tempDir, dylibEntries) = try listDylibsInTar(data: dataTar)
defer { try? FileManager.default.removeItem(at: tempDir) }
let fileManager = FileManager.default
try fileManager.createDirectory(at: tweaksDirectory, withIntermediateDirectories: true)
var installed: [String] = []
for entry in dylibEntries {
let name = (entry.path as NSString).lastPathComponent
guard name.lowercased().hasSuffix(".dylib") else { continue }
let dest = tweaksDirectory.appendingPathComponent(name)
if fileManager.fileExists(atPath: dest.path) { try? fileManager.removeItem(at: dest) }
try fileManager.copyItem(at: entry.url, to: dest)
installed.append(name)
}
if installed.isEmpty {
throw NSError(domain: "DebExtractor", code: 2, userInfo: [NSLocalizedDescriptionKey: "No .dylib files found in the .deb package"])
}
return DebInstallResult(packageName: controlName, packageVersion: controlVersion, installedDylibs: installed)
}
/// Parse ar archive and return raw content of member whose name starts with `prefix`.
private static func readArMember(data: Data, namePrefix: String) -> Data? {
guard data.count >= 8, data.prefix(8).elementsEqual(arMagicData) else { return nil }
var offset = 8
while offset + 60 <= data.count {
let header = data.subdata(in: offset ..< offset + 60)
guard let name = String(data: header.prefix(16), encoding: .ascii)?.trimmingCharacters(in: CharacterSet.whitespaces.union(CharacterSet(charactersIn: "\0"))),
let sizeStr = String(data: header.subdata(in: 48 ..< 58), encoding: .ascii)?.trimmingCharacters(in: .whitespaces),
let size = Int(sizeStr, radix: 10), size >= 0 else {
break
}
offset += 60
if name == "/" || name.isEmpty { offset += size; if size % 2 != 0 { offset += 1 }; continue }
if name.hasPrefix(namePrefix) {
guard offset + size <= data.count else { return nil }
return data.subdata(in: offset ..< offset + size)
}
offset += size
if offset % 2 != 0 { offset += 1 }
}
return nil
}
/// Parse control.tar.gz to get Package and Version (optional).
private static func parseControl(from debData: Data) -> (name: String?, version: String?) {
guard let controlTar = readArMember(data: debData, namePrefix: "control.tar") else { return (nil, nil) }
let decompressed: Data
if controlTar.prefix(2) == Data([0x1f, 0x8b]) {
guard let d = decompressGzip(controlTar) else { return (nil, nil) }
decompressed = d
} else {
decompressed = controlTar
}
guard let controlFile = readFileFromTar(data: decompressed, nameSuffix: "control") else { return (nil, nil) }
guard let str = String(data: controlFile, encoding: .utf8) else { return (nil, nil) }
var name: String?
var version: String?
for line in str.components(separatedBy: .newlines) {
if line.hasPrefix("Package:") { name = line.dropFirst(8).trimmingCharacters(in: .whitespaces) }
if line.hasPrefix("Version:") { version = line.dropFirst(8).trimmingCharacters(in: .whitespaces) }
}
return (name, version)
}
/// Extract data.tar.* from .deb and decompress to raw tar.
private static func extractDataTar(from debData: Data) throws -> Data {
guard let dataMember = readArMember(data: debData, namePrefix: "data.tar") else {
throw NSError(domain: "DebExtractor", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid .deb: no data.tar found"])
}
if dataMember.prefix(2) == Data([0x1f, 0x8b]) {
guard let d = decompressGzip(dataMember) else {
throw NSError(domain: "DebExtractor", code: 3, userInfo: [NSLocalizedDescriptionKey: "Failed to decompress data.tar.gz"])
}
return d
}
if dataMember.prefix(3).elementsEqual(Data([0x5d, 0x00, 0x00])) || dataMember.prefix(1) == Data([0x5d]) {
guard let d = decompressLzma(dataMember) else {
throw NSError(domain: "DebExtractor", code: 4, userInfo: [NSLocalizedDescriptionKey: "Failed to decompress data.tar.lzma"])
}
return d
}
return dataMember
}
/// List .dylib entries in tar; extract each to a temp file and return (tempDir, entries). Caller must remove tempDir after copying.
private static func listDylibsInTar(data: Data) throws -> (tempDir: URL, entries: [(path: String, url: URL)]) {
var results: [(path: String, url: URL)] = []
let tmpDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
try FileManager.default.createDirectory(at: tmpDir, withIntermediateDirectories: true)
var offset = 0
while offset + 512 <= data.count {
let header = data.subdata(in: offset ..< offset + 512)
if header.prefix(257).allSatisfy({ $0 == 0 }) { break }
guard let name = String(data: header.prefix(100), encoding: .ascii)?.trimmingCharacters(in: CharacterSet(charactersIn: "\0")) else {
offset += 512
continue
}
let sizeStr = String(data: header.subdata(in: 124 ..< 136), encoding: .ascii)?.trimmingCharacters(in: .whitespaces) ?? "0"
let size = Int(sizeStr, radix: 8) ?? 0
offset += 512
let contentStart = offset
offset += (size + 511) / 512 * 512
guard name.hasSuffix(".dylib"), size > 0, contentStart + size <= data.count else { continue }
let content = data.subdata(in: contentStart ..< contentStart + size)
let base = (name as NSString).lastPathComponent
let tmpFile = tmpDir.appendingPathComponent(base)
try content.write(to: tmpFile)
results.append((name, tmpFile))
}
return (tmpDir, results)
}
/// Read first file from tar that has given name suffix (e.g. "control").
private static func readFileFromTar(data: Data, nameSuffix: String) -> Data? {
var offset = 0
while offset + 512 <= data.count {
let header = data.subdata(in: offset ..< offset + 512)
if header.prefix(257).allSatisfy({ $0 == 0 }) { break }
guard let name = String(data: header.prefix(100), encoding: .ascii)?.trimmingCharacters(in: CharacterSet(charactersIn: "\0")),
name.hasSuffix(nameSuffix) else {
let sizeStr = String(data: header.subdata(in: 124 ..< 136), encoding: .ascii)?.trimmingCharacters(in: .whitespaces) ?? "0"
let size = Int(sizeStr, radix: 8) ?? 0
offset += 512 + (size + 511) / 512 * 512
continue
}
let sizeStr = String(data: header.subdata(in: 124 ..< 136), encoding: .ascii)?.trimmingCharacters(in: .whitespaces) ?? "0"
let size = Int(sizeStr, radix: 8) ?? 0
offset += 512
guard offset + size <= data.count else { return nil }
return data.subdata(in: offset ..< offset + size)
}
return nil
}
private static func decompressGzip(_ data: Data) -> Data? {
return decompress(data, algorithm: COMPRESSION_ZLIB)
}
/// LZMA (e.g. data.tar.lzma).
private static func decompressLzma(_ data: Data) -> Data? {
return decompress(data, algorithm: COMPRESSION_LZMA)
}
private static func decompress(_ data: Data, algorithm: compression_algorithm) -> Data? {
let destSize = 16 * 1024 * 1024
let dest = UnsafeMutablePointer<UInt8>.allocate(capacity: destSize)
defer { dest.deallocate() }
let decoded = data.withUnsafeBytes { (src: UnsafeRawBufferPointer) -> Int in
compression_decode_buffer(dest, destSize, src.bindMemory(to: UInt8.self).baseAddress!, data.count, nil, algorithm)
}
guard decoded > 0 else { return nil }
return Data(bytes: dest, count: decoded)
}
}