From 8bb8139aaf531c60736591de175bb4f4ad8e35bb Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 4 Feb 2023 09:48:38 +0800 Subject: [PATCH 01/54] refactor button handler --- PlayTools/Controls/PlayAction.swift | 39 ++----------------------- PlayTools/Controls/PlayInput.swift | 42 +++++++++++++++++++++++---- PlayTools/Controls/PlayMice.swift | 44 ++++++++--------------------- 3 files changed, 49 insertions(+), 76 deletions(-) diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index a346578e..c2b6de93 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -19,16 +19,6 @@ extension GCKeyboard { class ButtonAction: Action { func invalidate() { Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id) - if let gcKey = GCKeyboard.coalesced?.keyboardInput?.button(forKeyCode: keyCode) { - gcKey.pressedChangedHandler = nil - - } else if let gcControllerElement = GCController.current?.extendedGamepad?.elements[keyName] { - - if let gcControllerButton = gcControllerElement as? GCControllerButtonInput { - gcControllerButton.pressedChangedHandler = nil - } - - } } let keyCode: GCKeyCode @@ -36,37 +26,12 @@ class ButtonAction: Action { let point: CGPoint var id: Int? - private func getChangedHandler(handler: ((T1, Float, Bool) -> Void)?) -> (T1, Float, Bool) -> Void { - return { button, value, pressed in - if !PlayInput.cmdPressed() { - self.update(pressed: pressed) - } - if let previous = handler { - previous(button, value, pressed) - } - } - } - init(keyCode: GCKeyCode, keyName: String, point: CGPoint) { self.keyCode = keyCode self.keyName = keyName self.point = point - if PlayMice.shared.setMiceButtons(keyCode.rawValue, action: self) { - // No more work to do for mouse buttons - } else if let gcKey = GCKeyboard.coalesced!.keyboardInput!.button(forKeyCode: keyCode) { - let handler = gcKey.pressedChangedHandler - gcKey.pressedChangedHandler = getChangedHandler(handler: handler) - - } else if let gcControllerElement = GCController.current?.extendedGamepad?.elements[keyName] { - - if let gcControllerButton = gcControllerElement as? GCControllerButtonInput { - let handler = gcControllerButton.pressedChangedHandler - gcControllerButton.pressedChangedHandler = getChangedHandler(handler: handler) - } - - } else { - Toast.showOver(msg: "failed to map button at point \(point)") - } + // TODO: set both key names in draggable button, so as to depracate key code + PlayInput.registerButton(key: KeyCodeNames.keyCodes[keyCode.rawValue]!, handler: self.update) } convenience init(data: Button) { diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 0698c746..26937322 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -10,11 +10,41 @@ class PlayInput { static private var lCmdPressed = false static private var rCmdPressed = false + static public var buttonHandlers: [String: [(Bool) -> Void]] = [:] + func invalidate() { PlayMice.shared.stop() for action in self.actions { action.invalidate() } + PlayInput.buttonHandlers.removeAll(keepingCapacity: true) + GCKeyboard.coalesced!.keyboardInput!.keyChangedHandler = nil + GCController.current?.extendedGamepad?.valueChangedHandler = nil + } + + static public func registerButton(key: String, handler: @escaping (Bool) -> Void) { + if PlayInput.buttonHandlers[key] == nil { + PlayInput.buttonHandlers[key] = [] + } + PlayInput.buttonHandlers[key]!.append(handler) + } + + func keyboardHandler(_: GCKeyboardInput, _: GCControllerButtonInput, keyCode: GCKeyCode, pressed: Bool) { + if PlayInput.cmdPressed() { return } + guard let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[keyCode.rawValue]!] else { return } + for handler in handlers { + handler(pressed) + } + } + + func controllerButtonHandler(_: GCExtendedGamepad, element: GCControllerElement) { + guard let buttonElement = element as? GCControllerButtonInput else { return } + // TODO: handle analog input here too + let name: String = element.aliases.first! + guard let handlers = PlayInput.buttonHandlers[name] else { return } + for handler in handlers { + handler(buttonElement.isPressed) + } } func parseKeymap() { @@ -56,21 +86,21 @@ class PlayInput { } } if let controller = GCController.current?.extendedGamepad { + // TODO: direction pad is analog controller.valueChangedHandler = { _, element in // This is the index of controller buttons, which is String, not Int let alias: String! = element.aliases.first EditorController.shared.setKey(alias) } } - } else { - GCKeyboard.coalesced!.keyboardInput!.keyChangedHandler = nil - GCController.current?.extendedGamepad?.valueChangedHandler = nil } } func setup() { parseKeymap() + GCKeyboard.coalesced!.keyboardInput!.keyChangedHandler = keyboardHandler + GCController.current?.extendedGamepad?.valueChangedHandler = controllerButtonHandler for mouse in GCMouse.mice() { if settings.mouseMapping { mouse.mouseInput?.mouseMovedHandler = PlayMice.shared.handleMouseMoved @@ -118,7 +148,7 @@ class PlayInput { return screen.window?.rootViewController } - func setupShortcuts() { + func setupHotkeys() { if let keyboard = GCKeyboard.coalesced?.keyboardInput { keyboard.button(forKeyCode: .leftGUI)?.pressedChangedHandler = { _, _, pressed in PlayInput.lCmdPressed = pressed @@ -144,7 +174,7 @@ class PlayInput { let main = OperationQueue.main centre.addObserver(forName: NSNotification.Name.GCKeyboardDidConnect, object: nil, queue: main) { _ in - self.setupShortcuts() + self.setupHotkeys() if !mode.visible { self.setup() } @@ -162,7 +192,7 @@ class PlayInput { } } - setupShortcuts() + setupHotkeys() // Fix beep sound AKInterface.shared! diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 5fec34b6..a15f6d76 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -142,42 +142,28 @@ public class PlayMice { } public func stop() { - mouseActions.keys.forEach { key in - mouseActions[key] = [] - } for mouse in GCMouse.mice() { mouse.mouseInput?.mouseMovedHandler = { _, _, _ in} } } - func setMiceButtons(_ keyId: Int, action: ButtonAction) -> Bool { - if (-3 ... -1).contains(keyId) { - setMiceButton(keyId, action: action) - return true - } - return false - } - - var mouseActions: [Int: [ButtonAction]] = [2: [], 8: [], 33554432: []] + // TODO: get rid of this shit + var buttonIndex: [Int: Int] = [2: -1, 8: -2, 33554432: -3] private func setupMouseButton(_up: Int, _down: Int) { AKInterface.shared!.setupMouseButton(_up, _down, dontIgnore(_:_:_:)) } - private func dontIgnore(_ actionIndex: Int, _ state: Bool, _ isEventWindow: Bool) -> Bool { + private func dontIgnore(_ actionIndex: Int, _ pressed: Bool, _ isEventWindow: Bool) -> Bool { if EditorController.shared.editorMode { - if state { - if actionIndex == 8 { - EditorController.shared.setKey(-2) - } else if actionIndex == 33554432 { - EditorController.shared.setKey(-3) - } + if pressed && actionIndex != 2 { + EditorController.shared.setKey(buttonIndex[actionIndex]!) } return true } if self.acceptMouseEvents { let curPos = self.cursorPos() - if state { + if pressed { if !self.fakedMousePressed // For traffic light buttons when not fullscreen && curPos.y > 0 @@ -197,23 +183,15 @@ public class PlayMice { return true } if !mode.visible { - self.mouseActions[actionIndex]!.forEach({ buttonAction in - buttonAction.update(pressed: state) - }) + if let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[buttonIndex[actionIndex]!]!] { + for handler in handlers { + handler(pressed) + } + } return false } return true } - - private func setMiceButton(_ keyId: Int, action: ButtonAction) { - switch keyId { - case -1: mouseActions[2]!.append(action) - case -2: mouseActions[8]!.append(action) - case -3: mouseActions[33554432]!.append(action) - default: - mouseActions[2]!.append(action) - } - } } class CameraAction: Action { From 3904831cad7774323d34de38c6960c40127e06c7 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Tue, 7 Feb 2023 01:19:56 +0800 Subject: [PATCH 02/54] refactor controller input handling --- .../Controls/PTFakeTouch/PTFakeMetaTouch.m | 2 +- PlayTools/Controls/PlayAction.swift | 51 ++---- PlayTools/Controls/PlayInput.swift | 48 ++++-- PlayTools/Controls/PlayMice.swift | 160 ++++++++++-------- PlayTools/Keymap/ControlModel.swift | 2 + PlayTools/Keymap/KeyCodeNames.swift | 1 + PlayTools/Keymap/Keymapping.swift | 3 +- 7 files changed, 150 insertions(+), 117 deletions(-) diff --git a/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m b/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m index cfcd3c5b..b3e76150 100644 --- a/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m +++ b/PlayTools/Controls/PTFakeTouch/PTFakeMetaTouch.m @@ -65,7 +65,7 @@ + (NSInteger)fakeTouchId: (NSInteger)pointId AtPoint: (CGPoint)point withTouchPh touch = toStationarify; toStationarify = NULL; if(touch.phase == UITouchPhaseBegan) { - [touch setPhaseAndUpdateTimestamp:UITouchPhaseStationary]; + [touch setPhaseAndUpdateTimestamp:UITouchPhaseMoved]; } } // respect the semantics of touch phase, allocate new touch on touch began. diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index c2b6de93..9db0181f 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -30,8 +30,10 @@ class ButtonAction: Action { self.keyCode = keyCode self.keyName = keyName self.point = point + let code = keyCode.rawValue // TODO: set both key names in draggable button, so as to depracate key code - PlayInput.registerButton(key: KeyCodeNames.keyCodes[keyCode.rawValue]!, handler: self.update) + PlayInput.registerButton(key: code == KeyCodeNames.defaultCode ? keyName: KeyCodeNames.keyCodes[code]!, + handler: self.update) } convenience init(data: Button) { @@ -59,7 +61,6 @@ class DraggableButtonAction: ButtonAction { override init(keyCode: GCKeyCode, keyName: String, point: CGPoint) { self.releasePoint = point super.init(keyCode: keyCode, keyName: keyName, point: point) - _ = PlayMice.shared.setupThumbstickChangedHandler(name: keyName) } override func update(pressed: Bool) { @@ -101,10 +102,10 @@ class ContinuousJoystickAction: Action { self.key = data.keyName position = center self.sensitivity = data.transform.size.absoluteSize / 4 - if PlayMice.shared.setupThumbstickChangedHandler(name: key) { - PlayMice.shared.joystickHandler[key] = thumbstickUpdate + if key == PlayMice.elementName { + PlayMice.shared.joystickHandler[key] = self.mouseUpdate } else { - PlayMice.shared.joystickHandler[key] = mouseUpdate + PlayMice.shared.joystickHandler[key] = self.thumbstickUpdate } } @@ -151,16 +152,8 @@ class JoystickAction: Action { self.keys = keys self.center = center self.shift = shift / 2 - if let keyboard = GCKeyboard.coalesced?.keyboardInput { - for key in keys { - let handler = keyboard.button(forKeyCode: key)?.pressedChangedHandler - keyboard.button(forKeyCode: key)?.pressedChangedHandler = { button, value, pressed in - Toucher.touchQueue.async(execute: self.update) - if let previous = handler { - previous(button, value, pressed) - } - } - } + for key in keys { + PlayInput.registerButton(key: KeyCodeNames.keyCodes[key.rawValue]!, handler: self.update) } } @@ -181,28 +174,19 @@ class JoystickAction: Action { func invalidate() { Toucher.touchcam(point: center, phase: UITouch.Phase.ended, tid: &id) self.moving = false - if let keyboard = GCKeyboard.coalesced?.keyboardInput { - for key in keys { - keyboard.button(forKeyCode: key)?.pressedChangedHandler = nil - } - } } - func update() { - if mode.visible { - return - } + func update(_: Bool) { var touch = center - var start = center if GCKeyboard.pressed(key: keys[0]) { - touch.y -= shift / 3 + touch.y -= shift / 2 } else if GCKeyboard.pressed(key: keys[1]) { - touch.y += shift / 3 + touch.y += shift / 2 } if GCKeyboard.pressed(key: keys[2]) { - touch.x -= shift / 3 + touch.x -= shift / 2 } else if GCKeyboard.pressed(key: keys[3]) { - touch.x += shift / 3 + touch.x += shift / 2 } if moving { if touch.equalTo(center) { @@ -213,15 +197,8 @@ class JoystickAction: Action { } } else { if !touch.equalTo(center) { - start.x += (touch.x - start.x) / 8 - start.y += (touch.y - start.y) / 8 moving = true - Toucher.touchcam(point: start, phase: UITouch.Phase.began, tid: &id) - Toucher.touchQueue.asyncAfter(deadline: .now() + 0.04) { - if self.moving { - Toucher.touchcam(point: touch, phase: UITouch.Phase.moved, tid: &self.id) - } // end if - } // end closure + Toucher.touchcam(point: touch, phase: UITouch.Phase.began, tid: &id) } // end if } // end else } diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 26937322..cbc36df5 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -29,21 +29,30 @@ class PlayInput { PlayInput.buttonHandlers[key]!.append(handler) } - func keyboardHandler(_: GCKeyboardInput, _: GCControllerButtonInput, keyCode: GCKeyCode, pressed: Bool) { + func keyboardHandler(_: GCKeyboardInput, _: GCControllerButtonInput, _ keyCode: GCKeyCode, _ pressed: Bool) { if PlayInput.cmdPressed() { return } - guard let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[keyCode.rawValue]!] else { return } + guard let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[keyCode.rawValue]!] else { + // TODO: usage hint of disabling keymapping is planned to be added here + return + } for handler in handlers { handler(pressed) } } - func controllerButtonHandler(_: GCExtendedGamepad, element: GCControllerElement) { - guard let buttonElement = element as? GCControllerButtonInput else { return } - // TODO: handle analog input here too + func controllerButtonHandler(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { let name: String = element.aliases.first! - guard let handlers = PlayInput.buttonHandlers[name] else { return } - for handler in handlers { - handler(buttonElement.isPressed) + if let buttonElement = element as? GCControllerButtonInput { +// Toast.showOver(msg: "recognised controller button: \(name)") + guard let handlers = PlayInput.buttonHandlers[name] else { return } + Toast.showOver(msg: name + ": \(buttonElement.isPressed)") + for handler in handlers { + handler(buttonElement.isPressed) + } + } else if let dpadElement = element as? GCControllerDirectionPad { + PlayMice.shared.handleControllerDirectionPad(profile, dpadElement) + } else { + Toast.showOver(msg: "unrecognised controller element input happens") } } @@ -86,10 +95,25 @@ class PlayInput { } } if let controller = GCController.current?.extendedGamepad { - // TODO: direction pad is analog controller.valueChangedHandler = { _, element in // This is the index of controller buttons, which is String, not Int - let alias: String! = element.aliases.first + var alias: String = element.aliases.first! + if alias == "Direction Pad" { + guard let dpadElement = element as? GCControllerDirectionPad else { + Toast.showOver(msg: "cannot map direction pad: element type not recognizable") + return + } + if dpadElement.xAxis.value > 0 { + alias = dpadElement.right.aliases.first! + } else if dpadElement.xAxis.value < 0 { + alias = dpadElement.left.aliases.first! + } + if dpadElement.yAxis.value > 0 { + alias = dpadElement.down.aliases.first! + } else if dpadElement.yAxis.value < 0 { + alias = dpadElement.up.aliases.first! + } + } EditorController.shared.setKey(alias) } } @@ -162,6 +186,7 @@ class PlayInput { keyboard.button(forKeyCode: .rightAlt)?.pressedChangedHandler = { _, _, pressed in self.swapMode(pressed) } + // TODO: set a timeout to display usage guide of Option and Keymapping menu in turn } } @@ -190,6 +215,9 @@ class PlayInput { if !mode.visible { self.setup() } + if EditorController.shared.editorMode { + self.toggleEditor(show: true) + } } setupHotkeys() diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index a15f6d76..8fabe3a1 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -28,7 +28,10 @@ public class PlayMice { var fakedMouseTouchPointId: Int? var fakedMousePressed: Bool {fakedMouseTouchPointId != nil} - private var thumbstickVelocity: CGVector = CGVector.zero + private var directionPadXValue: Float = 0, + directionPadYValue: Float = 0, + thumbstickCursorControl: [String: (((CGFloat, CGFloat) -> Void)?, CGFloat, CGFloat) -> Void] + = ["Left Thumbstick": ThumbstickCursorControl().update, "Right Thumbstick": ThumbstickCursorControl().update] public var draggableHandler: [String: (CGFloat, CGFloat) -> Void] = [:], cameraMoveHandler: [String: (CGFloat, CGFloat) -> Void] = [:], cameraScaleHandler: [String: (CGFloat, CGFloat) -> Void] = [:], @@ -58,10 +61,6 @@ public class PlayMice { return point } - static private func isVectorSignificant(_ vector: CGVector) -> Bool { - return vector.dx.magnitude + vector.dy.magnitude > 0.2 - } - public func setupScrollWheelHandler() { AKInterface.shared!.setupScrollWheel({deltaX, deltaY in if let cameraScale = self.cameraScaleHandler[PlayMice.elementName] { @@ -73,53 +72,31 @@ public class PlayMice { }) } - public func setupThumbstickChangedHandler(name: String) -> Bool { - if let thumbstick = GCController.current?.extendedGamepad?.elements[name] as? GCControllerDirectionPad { - thumbstick.valueChangedHandler = { _, deltaX, deltaY in - if !PlayMice.isVectorSignificant(self.thumbstickVelocity) { - if let closure = self.thumbstickPoll(name) { - DispatchQueue.main.async(execute: closure) - } - } - self.thumbstickVelocity.dx = CGFloat(deltaX * 6) - self.thumbstickVelocity.dy = CGFloat(deltaY * 6) -// Toast.showOver(msg: "thumbstick") - if let joystickUpdate = self.joystickHandler[name] { - joystickUpdate(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) - } + public func handleControllerDirectionPad(_ profile: GCExtendedGamepad, _ dpad: GCControllerDirectionPad) { + let name = dpad.aliases.first! + let xAxis = dpad.xAxis, yAxis = dpad.yAxis + if name == "Direction Pad" { + if (xAxis.value > 0) != (directionPadXValue > 0) { + PlayInput.shared.controllerButtonHandler(profile, dpad.right) } - return true - } - return false - } - - private func thumbstickPoll(_ name: String) -> (() -> Void)? { -// DispatchQueue.main.async { -// Toast.showOver(msg: "polling") -// } - let draggableUpdate = self.draggableHandler[name] - let cameraUpdate = self.cameraMoveHandler[name] - if draggableUpdate == nil && cameraUpdate == nil { - return nil - } - return { - if PlayMice.isVectorSignificant(self.thumbstickVelocity) { - var captured = false - if let draggableUpdate = self.draggableHandler[name] { - draggableUpdate(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) - captured = true - } - if !captured { - if let cameraUpdate = self.cameraMoveHandler[name] { - cameraUpdate(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) - } - } - if let closure = self.thumbstickPoll(name) { - DispatchQueue.main.asyncAfter( - deadline: DispatchTime.now() + 0.017, execute: closure) - } + if (xAxis.value < 0) != (directionPadXValue < 0) { + PlayInput.shared.controllerButtonHandler(profile, dpad.left) + } + if (yAxis.value > 0) != (directionPadYValue > 0) { + PlayInput.shared.controllerButtonHandler(profile, dpad.up) + } + if (yAxis.value < 0) != (directionPadYValue < 0) { + PlayInput.shared.controllerButtonHandler(profile, dpad.down) } + directionPadXValue = xAxis.value + directionPadYValue = yAxis.value + return } + let deltaX = xAxis.value, deltaY = yAxis.value + let cgDx = CGFloat(deltaX) + let cgDy = CGFloat(deltaY) + thumbstickCursorControl[name]!(draggableHandler[name] ?? cameraMoveHandler[name], cgDx * 6, cgDy * 6) + joystickHandler[name]?(cgDx, cgDy) } public func handleFakeMouseMoved(_: GCMouseInput, deltaX: Float, deltaY: Float) { @@ -148,7 +125,7 @@ public class PlayMice { } // TODO: get rid of this shit - var buttonIndex: [Int: Int] = [2: -1, 8: -2, 33554432: -3] + let buttonIndex: [Int: Int] = [2: -1, 8: -2, 33554432: -3] private func setupMouseButton(_up: Int, _down: Int) { AKInterface.shared!.setupMouseButton(_up, _down, dontIgnore(_:_:_:)) @@ -194,8 +171,46 @@ public class PlayMice { } } +class ThumbstickCursorControl { + private var thumbstickVelocity: CGVector = CGVector.zero, + thumbstickPolling: Bool = false, + eventHandler: ((CGFloat, CGFloat) -> Void)! + + static private func isVectorSignificant(_ vector: CGVector) -> Bool { + return vector.dx.magnitude + vector.dy.magnitude > 0.2 + } + + public func update(handler: ((CGFloat, CGFloat) -> Void)?, velocityX: CGFloat, velocityY: CGFloat) { + guard let hdlr = handler else { + if thumbstickPolling { + self.thumbstickVelocity.dx = 0 + self.thumbstickVelocity.dy = 0 + } + return + } + self.eventHandler = hdlr + self.thumbstickVelocity.dx = velocityX + self.thumbstickVelocity.dy = velocityY + if !thumbstickPolling { + DispatchQueue.main.async(execute: self.thumbstickPoll) + self.thumbstickPolling = true + } + } + + private func thumbstickPoll() { + if !ThumbstickCursorControl.isVectorSignificant(self.thumbstickVelocity) { + self.thumbstickPolling = false + return + } + self.eventHandler(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) + DispatchQueue.main.asyncAfter( + deadline: DispatchTime.now() + 0.017, execute: self.thumbstickPoll) + } +} + class CameraAction: Action { var swipeMove, swipeScale1, swipeScale2: SwipeAction + static var swipeDrag = SwipeAction() var key: String! var center: CGPoint var distance1: CGFloat = 100, distance2: CGFloat = 100 @@ -207,7 +222,6 @@ class CameraAction: Action { swipeMove = SwipeAction() swipeScale1 = SwipeAction() swipeScale2 = SwipeAction() - _ = PlayMice.shared.setupThumbstickChangedHandler(name: key) PlayMice.shared.cameraMoveHandler[key] = self.moveUpdated PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = self.scaleUpdated } @@ -234,22 +248,32 @@ class CameraAction: Action { // Event handlers SHOULD be SMALL // DO NOT check things like mode.visible in an event handler // change the handler itself instead - func dragUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { - swipeMove.move(from: PlayMice.shared.cursorPos, deltaX: deltaX * 4, deltaY: -deltaY * 4) + static func dragUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { + swipeDrag.move(from: PlayMice.shared.cursorPos, deltaX: deltaX * 4, deltaY: -deltaY * 4) } func invalidate() { PlayMice.shared.cameraMoveHandler.removeValue(forKey: key) - PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = self.dragUpdated + PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = CameraAction.dragUpdated + swipeMove.invalidate() + swipeScale1.invalidate() + swipeScale2.invalidate() } } class SwipeAction: Action { var location: CGPoint = CGPoint.zero var id: Int? + let timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main) init() { // in rare cases the cooldown reset task is lost by the dispatch queue self.cooldown = false + // TODO: camera mode switch: Flexibility v.s. Precision + // TODO: find reasonable ways to promote fake touch priority + timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.3, leeway: DispatchTimeInterval.never) + timer.setEventHandler(qos: DispatchQoS.background, handler: self.checkEnded) + timer.activate() + timer.suspend() } func delay(_ delay: Double, closure: @escaping () -> Void) { @@ -261,23 +285,21 @@ class SwipeAction: Action { var counter = 0 // if should wait before beginning next touch var cooldown = false - // in how many tests has this been identified as stationary - var stationaryCount = 0 - let stationaryThreshold = 2 + var lastCounter = 0 func checkEnded() { - // if been stationary for enough time - if self.stationaryCount < self.stationaryThreshold || (self.stationaryCount < 20 - self.counter) { - self.stationaryCount += 1 - self.delay(0.04, closure: self.checkEnded) - return + if self.counter == self.lastCounter { + if self.counter < 24 { + counter += 24 + } else { + timer.suspend() + self.doLiftOff() + } } - self.doLiftOff() + self.lastCounter = self.counter } public func move(from: () -> CGPoint, deltaX: CGFloat, deltaY: CGFloat) { - // count touch duration - counter += 1 if id == nil { if cooldown { return @@ -285,12 +307,13 @@ class SwipeAction: Action { counter = 0 location = from() Toucher.touchcam(point: location, phase: UITouch.Phase.began, tid: &id) - delay(0.01, closure: checkEnded) + timer.resume() } + // count touch duration + counter += 1 self.location.x += deltaX self.location.y -= deltaY Toucher.touchcam(point: self.location, phase: UITouch.Phase.moved, tid: &id) - stationaryCount = 0 } public func doLiftOff() { @@ -308,6 +331,7 @@ class SwipeAction: Action { } func invalidate() { - // pass + timer.cancel() + self.doLiftOff() } } diff --git a/PlayTools/Keymap/ControlModel.swift b/PlayTools/Keymap/ControlModel.swift index b5ea7dc4..28fc7f42 100644 --- a/PlayTools/Keymap/ControlModel.swift +++ b/PlayTools/Keymap/ControlModel.swift @@ -1,5 +1,7 @@ import GameController +// Data structure definition should match those in +// https://github.com/PlayCover/PlayCover/blob/develop/PlayCover/Model/Keymapping.swift class ControlData { var keyCodes: [Int] var keyName: String diff --git a/PlayTools/Keymap/KeyCodeNames.swift b/PlayTools/Keymap/KeyCodeNames.swift index 8df24d67..5c2c888e 100644 --- a/PlayTools/Keymap/KeyCodeNames.swift +++ b/PlayTools/Keymap/KeyCodeNames.swift @@ -1,3 +1,4 @@ +// Should match https://github.com/PlayCover/PlayCover/blob/develop/PlayCover/Model/KeyCodeNames.swift exactly class KeyCodeNames { public static let defaultCode = -10 diff --git a/PlayTools/Keymap/Keymapping.swift b/PlayTools/Keymap/Keymapping.swift index 5fb866de..af3fb26b 100644 --- a/PlayTools/Keymap/Keymapping.swift +++ b/PlayTools/Keymap/Keymapping.swift @@ -63,7 +63,8 @@ class Keymapping { } } } - +// Data structure definition should match those in +// https://github.com/PlayCover/PlayCover/blob/develop/PlayCover/Model/Keymapping.swift struct KeyModelTransform: Codable { var size: CGFloat var xCoord: CGFloat From 96740a89beaa9072469eaa23d81c770957528355 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Fri, 10 Feb 2023 17:27:07 +0800 Subject: [PATCH 03/54] switch to NSEvent --- AKPlugin.swift | 53 +++++++++- PlayTools/Controls/PlayAction.swift | 54 ++++++---- PlayTools/Controls/PlayInput.swift | 66 +++++++----- PlayTools/Controls/PlayMice.swift | 18 ++-- PlayTools/Keymap/KeyCodeNames.swift | 158 +++++++++++++++++++++++++++- Plugin.swift | 3 +- 6 files changed, 288 insertions(+), 64 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 43003749..277903c9 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -51,12 +51,40 @@ class AKPlugin: NSObject, Plugin { NSApplication.shared.terminate(self) } - func eliminateRedundantKeyPressEvents(_ dontIgnore: @escaping() -> Bool) { + private var modifierFlag: UInt = 0 + private let flagMap: [UInt: UInt16] = [ + NSEvent.ModifierFlags.capsLock.rawValue >> 16: 57, + NSEvent.ModifierFlags.shift.rawValue >> 16: 56 + ] + func setupKeyboard(_ onChanged: @escaping(UInt16, Bool) -> Bool) { NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: { event in - if dontIgnore() { - return event + if event.isARepeat { + return nil } - return nil + let consumed = onChanged(event.keyCode, true) + if consumed { + return nil + } + return event + }) + NSEvent.addLocalMonitorForEvents(matching: .keyUp, handler: { event in + let consumed = onChanged(event.keyCode, false) + if consumed { + return nil + } + return event + }) + NSEvent.addLocalMonitorForEvents(matching: .flagsChanged, handler: { event in + let changed = (event.modifierFlags.rawValue ^ self.modifierFlag) + self.modifierFlag = event.modifierFlags.rawValue + // ignore lower 16 bit + guard let virtualCode = self.flagMap[changed >> 16] else {return event} + let pressed = (changed & event.modifierFlags.rawValue) > 0 + let consumed = onChanged(virtualCode, pressed) + if consumed { + return nil + } + return event }) } @@ -78,7 +106,22 @@ class AKPlugin: NSObject, Plugin { func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) { NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask.scrollWheel, handler: { event in - let consumed = onMoved(event.scrollingDeltaX, event.scrollingDeltaY) + var deltaX = event.scrollingDeltaX, deltaY = event.scrollingDeltaY + if !event.hasPreciseScrollingDeltas { + deltaX *= 16 + deltaY *= 16 + } + let consumed = onMoved(deltaX, deltaY) + if consumed { + return nil + } + return event + }) + } + + func setupMouseMove(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) { + NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask.mouseMoved, handler: { event in + let consumed = onMoved(event.deltaX, event.deltaY) if consumed { return nil } diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index 9db0181f..d58f2dbe 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -21,23 +21,26 @@ class ButtonAction: Action { Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id) } - let keyCode: GCKeyCode + let keyCode: Int let keyName: String let point: CGPoint var id: Int? - init(keyCode: GCKeyCode, keyName: String, point: CGPoint) { + init(keyCode: Int, keyName: String, point: CGPoint) { self.keyCode = keyCode self.keyName = keyName self.point = point - let code = keyCode.rawValue + let code = keyCode + guard let codeName = KeyCodeNames.keyCodes[code] else { + Toast.showOver(msg: keyName+"(\(keyCode)) cannot be mapped") + return + } // TODO: set both key names in draggable button, so as to depracate key code - PlayInput.registerButton(key: code == KeyCodeNames.defaultCode ? keyName: KeyCodeNames.keyCodes[code]!, - handler: self.update) + PlayInput.registerButton(key: code == KeyCodeNames.defaultCode ? keyName: codeName, handler: self.update) } convenience init(data: Button) { - let keyCode = GCKeyCode(rawValue: data.keyCode) + let keyCode = data.keyCode self.init( keyCode: keyCode, keyName: data.keyName, @@ -58,7 +61,7 @@ class ButtonAction: Action { class DraggableButtonAction: ButtonAction { var releasePoint: CGPoint - override init(keyCode: GCKeyCode, keyName: String, point: CGPoint) { + override init(keyCode: Int, keyName: String, point: CGPoint) { self.releasePoint = point super.init(keyCode: keyCode, keyName: keyName, point: point) } @@ -76,7 +79,6 @@ class DraggableButtonAction: ButtonAction { override func invalidate() { PlayMice.shared.draggableHandler.removeValue(forKey: keyName) - PlayMice.shared.stop() super.invalidate() } @@ -142,28 +144,31 @@ class ContinuousJoystickAction: Action { } class JoystickAction: Action { - let keys: [GCKeyCode] + let keys: [Int] let center: CGPoint let shift: CGFloat var id: Int? var moving = false + private var keyPressed = [Bool](repeating: false, count: 4) - init(keys: [GCKeyCode], center: CGPoint, shift: CGFloat) { + init(keys: [Int], center: CGPoint, shift: CGFloat) { self.keys = keys self.center = center self.shift = shift / 2 - for key in keys { - PlayInput.registerButton(key: KeyCodeNames.keyCodes[key.rawValue]!, handler: self.update) + for index in 0.. (Bool) -> Void { + return { pressed in + self.keyPressed[index] = pressed + self.update() + } + } + + func update() { var touch = center - if GCKeyboard.pressed(key: keys[0]) { + if keyPressed[0] { touch.y -= shift / 2 - } else if GCKeyboard.pressed(key: keys[1]) { + } else if keyPressed[1] { touch.y += shift / 2 } - if GCKeyboard.pressed(key: keys[2]) { + if keyPressed[2] { touch.x -= shift / 2 - } else if GCKeyboard.pressed(key: keys[3]) { + } else if keyPressed[3] { touch.x += shift / 2 } if moving { diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index cbc36df5..af6d82e5 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -6,19 +6,17 @@ class PlayInput { static let shared = PlayInput() var actions = [Action]() var timeoutForBind = true - + static private var whichShift: UInt16 = 60 static private var lCmdPressed = false static private var rCmdPressed = false static public var buttonHandlers: [String: [(Bool) -> Void]] = [:] func invalidate() { - PlayMice.shared.stop() for action in self.actions { action.invalidate() } PlayInput.buttonHandlers.removeAll(keepingCapacity: true) - GCKeyboard.coalesced!.keyboardInput!.keyChangedHandler = nil GCController.current?.extendedGamepad?.valueChangedHandler = nil } @@ -29,10 +27,12 @@ class PlayInput { PlayInput.buttonHandlers[key]!.append(handler) } - func keyboardHandler(_: GCKeyboardInput, _: GCControllerButtonInput, _ keyCode: GCKeyCode, _ pressed: Bool) { - if PlayInput.cmdPressed() { return } - guard let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[keyCode.rawValue]!] else { - // TODO: usage hint of disabling keymapping is planned to be added here + func keyboardHandler(_ keyCode: UInt16, _ pressed: Bool) { + guard let name = KeyCodeNames.virtualCodes[keyCode] else { + Toast.showOver(msg: "keycode \(keyCode) is not recognized") + return + } + guard let handlers = PlayInput.buttonHandlers[name] else { return } for handler in handlers { @@ -90,6 +90,10 @@ class PlayInput { if !PlayInput.cmdPressed() && !PlayInput.FORBIDDEN.contains(keyCode) && self.isSafeToBind(keyboard) { + if KeyCodeNames.keyCodes[keyCode.rawValue] == nil { + Toast.showOver(msg: "This key cannot be mapped") + return + } EditorController.shared.setKey(keyCode.rawValue) } } @@ -122,17 +126,7 @@ class PlayInput { func setup() { parseKeymap() - - GCKeyboard.coalesced!.keyboardInput!.keyChangedHandler = keyboardHandler GCController.current?.extendedGamepad?.valueChangedHandler = controllerButtonHandler - for mouse in GCMouse.mice() { - if settings.mouseMapping { - mouse.mouseInput?.mouseMovedHandler = PlayMice.shared.handleMouseMoved - } else { - mouse.mouseInput?.mouseMovedHandler = PlayMice.shared.handleFakeMouseMoved - } - } - } static public func cmdPressed() -> Bool { @@ -222,12 +216,36 @@ class PlayInput { setupHotkeys() - // Fix beep sound - AKInterface.shared! - .eliminateRedundantKeyPressEvents(self.dontIgnore) - } - - func dontIgnore() -> Bool { - (mode.visible && !EditorController.shared.editorMode) || PlayInput.cmdPressed() + AKInterface.shared!.setupKeyboard {keycode, pressed in + let consumed = !mode.visible && !PlayInput.cmdPressed() + if !consumed { + return false + } + var code = keycode + if code == 56 { + if pressed { + if GCKeyboard.pressed(key: GCKeyCode(rawValue: 229)) { + // Actually right shift + code = 60 + } + PlayInput.whichShift = code + } else { + code = PlayInput.whichShift + } + } + self.keyboardHandler(code, pressed) + return consumed + } + AKInterface.shared!.setupMouseMove {deltaX, deltaY in + if mode.visible { + return false + } + if settings.mouseMapping { + PlayMice.shared.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) + } else { + PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) + } + return true + } } } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 8fabe3a1..39fd182a 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -99,16 +99,16 @@ public class PlayMice { joystickHandler[name]?(cgDx, cgDy) } - public func handleFakeMouseMoved(_: GCMouseInput, deltaX: Float, deltaY: Float) { + public func handleFakeMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { if self.fakedMousePressed { Toucher.touchcam(point: self.cursorPos(), phase: UITouch.Phase.moved, tid: &fakedMouseTouchPointId) } } - public func handleMouseMoved(_: GCMouseInput, deltaX: Float, deltaY: Float) { + public func handleMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { let sensy = CGFloat(PlaySettings.shared.sensitivity) - let cgDx = CGFloat(deltaX) * sensy, - cgDy = CGFloat(deltaY) * sensy + let cgDx = deltaX * sensy * 0.5, + cgDy = -deltaY * sensy * 0.5 let name = PlayMice.elementName if let draggableUpdate = self.draggableHandler[name] { draggableUpdate(cgDx, cgDy) @@ -118,12 +118,6 @@ public class PlayMice { self.joystickHandler[name]?(cgDx, cgDy) } - public func stop() { - for mouse in GCMouse.mice() { - mouse.mouseInput?.mouseMovedHandler = { _, _, _ in} - } - } - // TODO: get rid of this shit let buttonIndex: [Int: Int] = [2: -1, 8: -2, 33554432: -3] @@ -270,7 +264,7 @@ class SwipeAction: Action { self.cooldown = false // TODO: camera mode switch: Flexibility v.s. Precision // TODO: find reasonable ways to promote fake touch priority - timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.3, leeway: DispatchTimeInterval.never) + timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.1, leeway: DispatchTimeInterval.never) timer.setEventHandler(qos: DispatchQoS.background, handler: self.checkEnded) timer.activate() timer.suspend() @@ -290,7 +284,7 @@ class SwipeAction: Action { func checkEnded() { if self.counter == self.lastCounter { if self.counter < 24 { - counter += 24 + counter += 12 } else { timer.suspend() self.doLiftOff() diff --git a/PlayTools/Keymap/KeyCodeNames.swift b/PlayTools/Keymap/KeyCodeNames.swift index 5c2c888e..dec53138 100644 --- a/PlayTools/Keymap/KeyCodeNames.swift +++ b/PlayTools/Keymap/KeyCodeNames.swift @@ -47,7 +47,7 @@ class KeyCodeNames { 67: "F10", 68: "F11", 69: "F12", - 100: "§", +// 100: "§", 30: "1", 31: "2", 32: "3", @@ -96,4 +96,160 @@ class KeyCodeNames { 55: ".", 56: "/" ] + public static let virtualCodes: [UInt16: String] = [ + 0: "A", + 11: "B", + 8: "C", + 2: "D", + 14: "E", + 3: "F", + 5: "G", + 4: "H", + 34: "I", + 38: "J", + 40: "K", + 37: "L", + 46: "M", + 45: "N", + 31: "O", + 35: "P", + 12: "Q", + 15: "R", + 1: "S", + 17: "T", + 32: "U", + 9: "V", + 13: "W", + 7: "X", + 16: "Y", + 6: "Z", + 18: "1", + 19: "2", + 20: "3", + 21: "4", + 23: "5", + 22: "6", + 26: "7", + 28: "8", + 25: "9", + 29: "0", + 36: "Enter", + 53: "Esc", + 51: "Del", + 48: "Tab", + 49: "Spc", + 27: "-", + 24: "=", + 33: "[", + 30: "]", + 42: "\\", + 41: ";", + 39: "'", + 50: "`", + 43: ",", + 47: ".", + 44: "/", + 57: "Caps", + 122: "F1", + 120: "F2", + 99: "F3", + 118: "F4", + 96: "F5", + 97: "F6", + 98: "F7", + 100: "F8", + 101: "F9", + 109: "F10", + 103: "F11", + 111: "F12", + 124: "Right", + 123: "Left", + 125: "Down", + 126: "Up", +// §: "§", + 56: "Lshft", + 58: "LOpt", + 55: "LCmd", + 60: "Rshft", + 61: "ROpt", + 54: "RCmd", +] +private static let toVirtual = [ + 41: 53, // Esc + 44: 49, + 225: 0x38, // "Lshft", + 57: 0x39, // "Caps", + 43: 48, + 227: 0x37, // "LCmd", + 226: 0x3A, // "LOpt", + 231: 0x36, // "RCmd", + 230: 0x3D, // "ROpt", + 40: 36, + 42: 51, + 229: 0x3C, // "Rshft", + 80: 123, + 79: 124, + 82: 126, + 81: 125, + 58: 122, // "F1", + 59: 120, // "F2", + 60: 99, // "F3", + 61: 118, // "F4", + 62: 96, // "F5", + 63: 97, // "F6", + 64: 98, // "F7", + 65: 100, // "F8", + 66: 101, + 67: 109, + 68: 0x67, // "F11", + 69: 111, // "F12", +// 100: "§", + 30: 18, // "1", + 31: 19, // "2", + 32: 20, // "3", + 33: 21, // "4", + 34: 23, // "5", + 35: 22, // "6", + 36: 26, // "7", + 37: 28, // "8", + 38: 25, // "9", + 39: 29, // -"0", + 45: 27, // "-", + 46: 24, // "=", + 20: 12, // "Q", + 26: 13, // "W", + 8: 14, // "E", + 21: 15, // "R", + 23: 17, // u"T", + 28: 16, // "Y", + 24: 32, // "U", + 12: 34, // "I", + 18: 31, // "O", + 19: 35, // "P", + 47: 33, // "[", + 48: 30, // "]", + 4: 0, // "A", + 22: 1, // "S", + 7: 2, // "D", + 9: 3, // "F", + 10: 5, // "G", + 11: 4, // "H", + 13: 38, // "J", + 14: 40, // "K", + 15: 37, // "L", + 51: 41, // ";", + 52: 39, // "'", + 49: 42, // "\\", + 29: 6, // "Z", + 53: 50, // "`", + 27: 7, // "X", + 6: 8, // "C", + 25: 9, // "V", + 5: 11, // "B", + 17: 45, // "N", + 16: 46, // "M", + 54: 43, // ",", + 55: 47, // ".", + 56: 44, // "/" + ] } diff --git a/Plugin.swift b/Plugin.swift index 6c2a7066..056fa36f 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -21,9 +21,10 @@ public protocol Plugin: NSObjectProtocol { func hideCursor() func unhideCursor() func terminateApplication() - func eliminateRedundantKeyPressEvents(_ dontIgnore: @escaping() -> Bool) + func setupKeyboard(_ onChanged: @escaping(UInt16, Bool) -> Bool) func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) + func setupMouseMove(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? func setMenuBarVisible(_ value: Bool) } From 12e7111d6ef2b0010be8ee53dcc4e2377637eea6 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Fri, 10 Feb 2023 22:50:16 +0800 Subject: [PATCH 04/54] warp mouse cursor to center --- AKPlugin.swift | 16 ++++++---------- PlayTools/Controls/PlayAction.swift | 7 ------- PlayTools/Controls/PlayInput.swift | 26 +++++++------------------- PlayTools/Controls/PlayMice.swift | 2 +- PlayTools/Keymap/KeyCodeNames.swift | 6 +++--- 5 files changed, 17 insertions(+), 40 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 277903c9..fb380595 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -40,6 +40,8 @@ class AKPlugin: NSObject, Plugin { func hideCursor() { NSCursor.hide() CGAssociateMouseAndMouseCursorPosition(0) + let frame = windowFrame + CGWarpMouseCursorPosition(CGPoint(x: frame.width / 2, y: frame.height / 2)) } func unhideCursor() { @@ -52,10 +54,6 @@ class AKPlugin: NSObject, Plugin { } private var modifierFlag: UInt = 0 - private let flagMap: [UInt: UInt16] = [ - NSEvent.ModifierFlags.capsLock.rawValue >> 16: 57, - NSEvent.ModifierFlags.shift.rawValue >> 16: 56 - ] func setupKeyboard(_ onChanged: @escaping(UInt16, Bool) -> Bool) { NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: { event in if event.isARepeat { @@ -75,12 +73,9 @@ class AKPlugin: NSObject, Plugin { return event }) NSEvent.addLocalMonitorForEvents(matching: .flagsChanged, handler: { event in - let changed = (event.modifierFlags.rawValue ^ self.modifierFlag) + let pressed = self.modifierFlag < event.modifierFlags.rawValue self.modifierFlag = event.modifierFlags.rawValue - // ignore lower 16 bit - guard let virtualCode = self.flagMap[changed >> 16] else {return event} - let pressed = (changed & event.modifierFlags.rawValue) > 0 - let consumed = onChanged(virtualCode, pressed) + let consumed = onChanged(event.keyCode, pressed) if consumed { return nil } @@ -120,7 +115,8 @@ class AKPlugin: NSObject, Plugin { } func setupMouseMove(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) { - NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask.mouseMoved, handler: { event in + let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged, .mouseMoved] + NSEvent.addLocalMonitorForEvents(matching: mask, handler: { event in let consumed = onMoved(event.deltaX, event.deltaY) if consumed { return nil diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index d58f2dbe..a42e5ddc 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -4,18 +4,11 @@ // import Foundation -import GameController protocol Action { func invalidate() } -extension GCKeyboard { - static func pressed(key: GCKeyCode) -> Bool { - return GCKeyboard.coalesced?.keyboardInput?.button(forKeyCode: key)?.isPressed ?? false - } -} - class ButtonAction: Action { func invalidate() { Toucher.touchcam(point: point, phase: UITouch.Phase.ended, tid: &id) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index af6d82e5..501dcec3 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -6,7 +6,7 @@ class PlayInput { static let shared = PlayInput() var actions = [Action]() var timeoutForBind = true - static private var whichShift: UInt16 = 60 + static private var lCmdPressed = false static private var rCmdPressed = false @@ -89,12 +89,11 @@ class PlayInput { keyboard.keyChangedHandler = { _, _, keyCode, _ in if !PlayInput.cmdPressed() && !PlayInput.FORBIDDEN.contains(keyCode) - && self.isSafeToBind(keyboard) { - if KeyCodeNames.keyCodes[keyCode.rawValue] == nil { - Toast.showOver(msg: "This key cannot be mapped") - return - } + && self.isSafeToBind(keyboard) + && KeyCodeNames.keyCodes[keyCode.rawValue] != nil { EditorController.shared.setKey(keyCode.rawValue) + } else { + Toast.showOver(msg: "This key (\(keyCode.rawValue)) cannot be mapped") } } } @@ -126,6 +125,7 @@ class PlayInput { func setup() { parseKeymap() + GCKeyboard.coalesced?.keyboardInput?.keyChangedHandler = nil GCController.current?.extendedGamepad?.valueChangedHandler = controllerButtonHandler } @@ -221,19 +221,7 @@ class PlayInput { if !consumed { return false } - var code = keycode - if code == 56 { - if pressed { - if GCKeyboard.pressed(key: GCKeyCode(rawValue: 229)) { - // Actually right shift - code = 60 - } - PlayInput.whichShift = code - } else { - code = PlayInput.whichShift - } - } - self.keyboardHandler(code, pressed) + self.keyboardHandler(keycode, pressed) return consumed } AKInterface.shared!.setupMouseMove {deltaX, deltaY in diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 39fd182a..b7b0430a 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -284,7 +284,7 @@ class SwipeAction: Action { func checkEnded() { if self.counter == self.lastCounter { if self.counter < 24 { - counter += 12 + counter += 24 } else { timer.suspend() self.doLiftOff() diff --git a/PlayTools/Keymap/KeyCodeNames.swift b/PlayTools/Keymap/KeyCodeNames.swift index dec53138..73227d9b 100644 --- a/PlayTools/Keymap/KeyCodeNames.swift +++ b/PlayTools/Keymap/KeyCodeNames.swift @@ -96,7 +96,7 @@ class KeyCodeNames { 55: ".", 56: "/" ] - public static let virtualCodes: [UInt16: String] = [ +public static let virtualCodes: [UInt16: String] = [ 0: "A", 11: "B", 8: "C", @@ -172,7 +172,7 @@ class KeyCodeNames { 55: "LCmd", 60: "Rshft", 61: "ROpt", - 54: "RCmd", + 54: "RCmd" ] private static let toVirtual = [ 41: 53, // Esc @@ -250,6 +250,6 @@ private static let toVirtual = [ 16: 46, // "M", 54: 43, // ",", 55: 47, // ".", - 56: 44, // "/" + 56: 44 // "/" ] } From e389b588eb4d19c80fafeaf3ec147e8f0e161ef0 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sun, 12 Feb 2023 03:39:55 +0800 Subject: [PATCH 05/54] add Control key to key code map --- AKPlugin.swift | 33 +++++++++++++++-------------- PlayTools/Controls/PlayAction.swift | 5 +---- PlayTools/Controls/PlayInput.swift | 21 +++++++++--------- PlayTools/Controls/PlayMice.swift | 4 ++-- PlayTools/Keymap/KeyCodeNames.swift | 14 ++++++++---- Plugin.swift | 4 ++-- 6 files changed, 43 insertions(+), 38 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index fb380595..6b6a5cb8 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -40,8 +40,12 @@ class AKPlugin: NSObject, Plugin { func hideCursor() { NSCursor.hide() CGAssociateMouseAndMouseCursorPosition(0) + warpCursor() + } + + func warpCursor() { let frame = windowFrame - CGWarpMouseCursorPosition(CGPoint(x: frame.width / 2, y: frame.height / 2)) + CGWarpMouseCursorPosition(CGPoint(x: frame.midX, y: frame.midY)) } func unhideCursor() { @@ -54,19 +58,19 @@ class AKPlugin: NSObject, Plugin { } private var modifierFlag: UInt = 0 - func setupKeyboard(_ onChanged: @escaping(UInt16, Bool) -> Bool) { + func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) { NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: { event in if event.isARepeat { return nil } - let consumed = onChanged(event.keyCode, true) + let consumed = keyboard(event.keyCode, true) if consumed { return nil } return event }) NSEvent.addLocalMonitorForEvents(matching: .keyUp, handler: { event in - let consumed = onChanged(event.keyCode, false) + let consumed = keyboard(event.keyCode, false) if consumed { return nil } @@ -75,7 +79,15 @@ class AKPlugin: NSObject, Plugin { NSEvent.addLocalMonitorForEvents(matching: .flagsChanged, handler: { event in let pressed = self.modifierFlag < event.modifierFlags.rawValue self.modifierFlag = event.modifierFlags.rawValue - let consumed = onChanged(event.keyCode, pressed) + let consumed = keyboard(event.keyCode, pressed) + if consumed { + return nil + } + return event + }) + let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged, .mouseMoved] + NSEvent.addLocalMonitorForEvents(matching: mask, handler: { event in + let consumed = mouseMoved(event.deltaX, event.deltaY) if consumed { return nil } @@ -114,17 +126,6 @@ class AKPlugin: NSObject, Plugin { }) } - func setupMouseMove(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) { - let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged, .mouseMoved] - NSEvent.addLocalMonitorForEvents(matching: mask, handler: { event in - let consumed = onMoved(event.deltaX, event.deltaY) - if consumed { - return nil - } - return event - }) - } - func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? { NSWorkspace.shared.urlForApplication(withBundleIdentifier: value) } diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index a42e5ddc..855f82c5 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -24,10 +24,7 @@ class ButtonAction: Action { self.keyName = keyName self.point = point let code = keyCode - guard let codeName = KeyCodeNames.keyCodes[code] else { - Toast.showOver(msg: keyName+"(\(keyCode)) cannot be mapped") - return - } + let codeName = KeyCodeNames.keyCodes[code] ?? "Btn" // TODO: set both key names in draggable button, so as to depracate key code PlayInput.registerButton(key: code == KeyCodeNames.defaultCode ? keyName: codeName, handler: self.update) } diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 501dcec3..4f21956c 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -28,10 +28,7 @@ class PlayInput { } func keyboardHandler(_ keyCode: UInt16, _ pressed: Bool) { - guard let name = KeyCodeNames.virtualCodes[keyCode] else { - Toast.showOver(msg: "keycode \(keyCode) is not recognized") - return - } + let name = KeyCodeNames.virtualCodes[keyCode] ?? "Btn" guard let handlers = PlayInput.buttonHandlers[name] else { return } @@ -92,8 +89,6 @@ class PlayInput { && self.isSafeToBind(keyboard) && KeyCodeNames.keyCodes[keyCode.rawValue] != nil { EditorController.shared.setKey(keyCode.rawValue) - } else { - Toast.showOver(msg: "This key (\(keyCode.rawValue)) cannot be mapped") } } } @@ -214,17 +209,23 @@ class PlayInput { } } + centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, + queue: OperationQueue.main + ) { _ in + if !mode.visible && settings.mouseMapping { + AKInterface.shared!.warpCursor() + } + } setupHotkeys() - AKInterface.shared!.setupKeyboard {keycode, pressed in + AKInterface.shared!.initialize(keyboard: {keycode, pressed in let consumed = !mode.visible && !PlayInput.cmdPressed() if !consumed { return false } self.keyboardHandler(keycode, pressed) return consumed - } - AKInterface.shared!.setupMouseMove {deltaX, deltaY in + }, mouseMoved: {deltaX, deltaY in if mode.visible { return false } @@ -234,6 +235,6 @@ class PlayInput { PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) } return true - } + }) } } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index b7b0430a..3cb3328e 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -283,8 +283,8 @@ class SwipeAction: Action { func checkEnded() { if self.counter == self.lastCounter { - if self.counter < 24 { - counter += 24 + if self.counter < 12 { + counter += 12 } else { timer.suspend() self.doLiftOff() diff --git a/PlayTools/Keymap/KeyCodeNames.swift b/PlayTools/Keymap/KeyCodeNames.swift index 73227d9b..4e62696b 100644 --- a/PlayTools/Keymap/KeyCodeNames.swift +++ b/PlayTools/Keymap/KeyCodeNames.swift @@ -26,6 +26,8 @@ class KeyCodeNames { 43: "Tab", 227: "LCmd", 226: "LOpt", + 224: "LCtrl", + 228: "RCtrl", 231: "RCmd", 230: "ROpt", 40: "Enter", @@ -172,9 +174,12 @@ public static let virtualCodes: [UInt16: String] = [ 55: "LCmd", 60: "Rshft", 61: "ROpt", - 54: "RCmd" + 54: "RCmd", + 59: "LCtrl", + 62: "RCtrl" ] -private static let toVirtual = [ +} +let mapGCKeyCodeRawValuetoNSEventVirtualCode = [ 41: 53, // Esc 44: 49, 225: 0x38, // "Lshft", @@ -250,6 +255,7 @@ private static let toVirtual = [ 16: 46, // "M", 54: 43, // ",", 55: 47, // ".", - 56: 44 // "/" + 56: 44, // "/" + 224: 59, // "LCtrl", + 228: 62 // "RCtrl", ] -} diff --git a/Plugin.swift b/Plugin.swift index 056fa36f..33193fba 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -19,12 +19,12 @@ public protocol Plugin: NSObjectProtocol { var isFullscreen: Bool { get } func hideCursor() + func warpCursor() func unhideCursor() func terminateApplication() - func setupKeyboard(_ onChanged: @escaping(UInt16, Bool) -> Bool) + func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) - func setupMouseMove(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? func setMenuBarVisible(_ value: Bool) } From 0a264d6e42fafdf643eb3b5242fa360dfdb7f20d Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Mon, 13 Feb 2023 21:25:18 +0800 Subject: [PATCH 06/54] switch option key handling to NSEvent --- AKPlugin.swift | 8 +++++++- PlayTools/Controls/PlayInput.swift | 21 ++++++--------------- Plugin.swift | 3 ++- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 6b6a5cb8..0e17c448 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -58,7 +58,8 @@ class AKPlugin: NSObject, Plugin { } private var modifierFlag: UInt = 0 - func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) { + func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, + swapMode: @escaping() -> Void) { NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: { event in if event.isARepeat { return nil @@ -78,7 +79,12 @@ class AKPlugin: NSObject, Plugin { }) NSEvent.addLocalMonitorForEvents(matching: .flagsChanged, handler: { event in let pressed = self.modifierFlag < event.modifierFlags.rawValue + let changed = self.modifierFlag ^ event.modifierFlags.rawValue self.modifierFlag = event.modifierFlags.rawValue + if pressed && NSEvent.ModifierFlags(rawValue: changed).contains(.option) { + swapMode() + return nil + } let consumed = keyboard(event.keyCode, pressed) if consumed { return nil diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 4f21956c..e541c285 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -145,16 +145,14 @@ class PlayInput { .printScreen ] - private func swapMode(_ pressed: Bool) { + private func swapMode() { if !settings.mouseMapping { return } - if pressed { - if !mode.visible { - self.invalidate() - } - mode.show(!mode.visible) + if !mode.visible { + self.invalidate() } + mode.show(!mode.visible) } var root: UIViewController? { @@ -169,12 +167,6 @@ class PlayInput { keyboard.button(forKeyCode: .rightGUI)?.pressedChangedHandler = { _, _, pressed in PlayInput.rCmdPressed = pressed } - keyboard.button(forKeyCode: .leftAlt)?.pressedChangedHandler = { _, _, pressed in - self.swapMode(pressed) - } - keyboard.button(forKeyCode: .rightAlt)?.pressedChangedHandler = { _, _, pressed in - self.swapMode(pressed) - } // TODO: set a timeout to display usage guide of Option and Keymapping menu in turn } } @@ -210,8 +202,7 @@ class PlayInput { } centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, - queue: OperationQueue.main - ) { _ in + queue: main) { _ in if !mode.visible && settings.mouseMapping { AKInterface.shared!.warpCursor() } @@ -235,6 +226,6 @@ class PlayInput { PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) } return true - }) + }, swapMode: self.swapMode) } } diff --git a/Plugin.swift b/Plugin.swift index 33193fba..877c9db4 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -22,7 +22,8 @@ public protocol Plugin: NSObjectProtocol { func warpCursor() func unhideCursor() func terminateApplication() - func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) + func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, + swapMode: @escaping() -> Void) func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? From 10e48d538ca212cb1ba9a30fe678d8d78aa9d3b4 Mon Sep 17 00:00:00 2001 From: OHaiiBuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sun, 22 Jan 2023 03:26:26 +0700 Subject: [PATCH 07/54] PlayChain keychain emulation support (#65) * feat: Apple Keychain Emulation (beta) * feat: Make PlayChain optional * fix: Store Keychains in PlayCover directories * fix: remove more logging when debug isn't enabled * fix: trailing space after keychainFolder * fix: cleanup logging functions * fix: use debugLogger from PlayKeychain instead of redefining it * fix: correctly return NotFound for non supported types * fix: Nicer keychainFolder * Revert "fix: Nicer keychainFolder" This reverts commit 992e860320661b5904fabbe474f715fbb300f626 due to it storing the keychain inside the app container instead of PlayCover's * fix: team IDs and logging * fix: indentations * fix: tighter checks for Update operations * fix: remove Package.resolved --- PlayTools.xcodeproj/project.pbxproj | 20 ++- PlayTools/MysticRunes/PlayedApple.swift | 167 ++++++++++++++++++++++++ PlayTools/PlayLoader.m | 64 +++++++++ PlayTools/PlaySettings.swift | 13 +- 4 files changed, 255 insertions(+), 9 deletions(-) create mode 100644 PlayTools/MysticRunes/PlayedApple.swift diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index eba2ce4f..e25d9dc2 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 2847AE48298EFC0F00B0F983 /* PlayScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2847AE47298EFC0F00B0F983 /* PlayScreen.swift */; }; 6E76639B28D0FAE700DE4AF9 /* Plugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E76639A28D0FAE700DE4AF9 /* Plugin.swift */; }; 6E76639C28D0FAE700DE4AF9 /* Plugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E76639A28D0FAE700DE4AF9 /* Plugin.swift */; }; 6E7663A128D0FB5300DE4AF9 /* AKPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7663A028D0FB5300DE4AF9 /* AKPlugin.swift */; }; @@ -15,6 +14,7 @@ 6E84A14528D0F94E00BF7495 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CBA287ABFD5000BEE9D /* UIKit.framework */; }; 6E84A15028D0F97500BF7495 /* AKInterface.bundle in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; AA71970B287A44D200623C15 /* PlayLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = AA719702287A44D200623C15 /* PlayLoader.m */; }; + AA71970C287A44D200623C15 /* PlayScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719703287A44D200623C15 /* PlayScreen.swift */; }; AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719704287A44D200623C15 /* PlaySettings.swift */; }; AA71970E287A44D200623C15 /* PlayLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719705287A44D200623C15 /* PlayLoader.h */; }; AA71970F287A44D200623C15 /* PlayCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719706287A44D200623C15 /* PlayCover.swift */; }; @@ -45,6 +45,7 @@ AA719870287A81A000623C15 /* UIEvent+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719842287A81A000623C15 /* UIEvent+Private.h */; }; AA719872287A81A000623C15 /* UITouch+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719844287A81A000623C15 /* UITouch+Private.h */; }; AA818CB9287ABFB1000BEE9D /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CB8287ABFB1000BEE9D /* IOKit.framework */; }; + ABCECEE629750BA600746595 /* PlayedApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCECEE529750BA600746595 /* PlayedApple.swift */; }; B127172228817AB90025112B /* SwordRPC in Frameworks */ = {isa = PBXBuildFile; productRef = B127172128817AB90025112B /* SwordRPC */; }; B127172528817C040025112B /* DiscordIPC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B127172428817C040025112B /* DiscordIPC.swift */; }; B1271729288284BE0025112B /* DiscordActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1271728288284BE0025112B /* DiscordActivity.swift */; }; @@ -66,7 +67,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 2847AE47298EFC0F00B0F983 /* PlayScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayScreen.swift; sourceTree = ""; }; 6E76639628D0FA6200DE4AF9 /* AKInterface-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AKInterface-Bridging-Header.h"; sourceTree = ""; }; 6E76639A28D0FAE700DE4AF9 /* Plugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Plugin.swift; sourceTree = ""; }; 6E7663A028D0FB5300DE4AF9 /* AKPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AKPlugin.swift; sourceTree = ""; }; @@ -74,6 +74,7 @@ 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AKInterface.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; AA7196D8287A447700623C15 /* PlayTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlayTools.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AA719702287A44D200623C15 /* PlayLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlayLoader.m; sourceTree = ""; }; + AA719703287A44D200623C15 /* PlayScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayScreen.swift; sourceTree = ""; }; AA719704287A44D200623C15 /* PlaySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaySettings.swift; sourceTree = ""; }; AA719705287A44D200623C15 /* PlayLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlayLoader.h; sourceTree = ""; }; AA719706287A44D200623C15 /* PlayCover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayCover.swift; sourceTree = ""; }; @@ -107,6 +108,7 @@ AA719844287A81A000623C15 /* UITouch+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITouch+Private.h"; sourceTree = ""; }; AA818CB8287ABFB1000BEE9D /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/Library/Frameworks/IOKit.framework; sourceTree = DEVELOPER_DIR; }; AA818CBA287ABFD5000BEE9D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/iOSSupport/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + ABCECEE529750BA600746595 /* PlayedApple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayedApple.swift; sourceTree = ""; }; B127172428817C040025112B /* DiscordIPC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscordIPC.swift; sourceTree = ""; }; B1271728288284BE0025112B /* DiscordActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscordActivity.swift; sourceTree = ""; }; B1E8CF8928BBE2AB004340D3 /* Keymapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keymapping.swift; sourceTree = ""; }; @@ -165,6 +167,7 @@ AA7196DA287A447700623C15 /* PlayTools */ = { isa = PBXGroup; children = ( + ABCECEE729750BB100746595 /* MysticRunes */, B127172328817AC70025112B /* DiscordActivity */, AA719721287A480C00623C15 /* Controls */, AA719799287A481500623C15 /* Keymap */, @@ -172,11 +175,11 @@ AA719791287A481500623C15 /* Utils */, AA71970A287A44D200623C15 /* Info.plist */, AA719706287A44D200623C15 /* PlayCover.swift */, + AA719703287A44D200623C15 /* PlayScreen.swift */, AA719704287A44D200623C15 /* PlaySettings.swift */, AA719705287A44D200623C15 /* PlayLoader.h */, AA719702287A44D200623C15 /* PlayLoader.m */, AA719708287A44D200623C15 /* PlayTools.h */, - 2847AE47298EFC0F00B0F983 /* PlayScreen.swift */, ); path = PlayTools; sourceTree = ""; @@ -262,6 +265,14 @@ name = Frameworks; sourceTree = ""; }; + ABCECEE729750BB100746595 /* MysticRunes */ = { + isa = PBXGroup; + children = ( + ABCECEE529750BA600746595 /* PlayedApple.swift */, + ); + path = MysticRunes; + sourceTree = ""; + }; B127172328817AC70025112B /* DiscordActivity */ = { isa = PBXGroup; children = ( @@ -434,7 +445,6 @@ AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */, AA719759287A480D00623C15 /* PlayAction.swift in Sources */, 6E7663A528D0FEBE00DE4AF9 /* AKPluginLoader.swift in Sources */, - 2847AE48298EFC0F00B0F983 /* PlayScreen.swift in Sources */, AA7197A2287A481500623C15 /* CircleMenu.swift in Sources */, AA71978B287A480D00623C15 /* MenuController.swift in Sources */, AA7197AA287A481500623C15 /* Toast.swift in Sources */, @@ -447,6 +457,8 @@ AA7197A3287A481500623C15 /* CircleMenuButton.swift in Sources */, AA7197A9287A481500623C15 /* PlayInfo.swift in Sources */, AA71986A287A81A000623C15 /* PTFakeMetaTouch.m in Sources */, + AA71970C287A44D200623C15 /* PlayScreen.swift in Sources */, + ABCECEE629750BA600746595 /* PlayedApple.swift in Sources */, AA71986C287A81A000623C15 /* NSObject+Swizzle.m in Sources */, AA71970F287A44D200623C15 /* PlayCover.swift in Sources */, B127172528817C040025112B /* DiscordIPC.swift in Sources */, diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift new file mode 100644 index 00000000..f7c13379 --- /dev/null +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -0,0 +1,167 @@ +// +// PlayWeRuinedIt.swift +// PlayTools +// +// Created by Venti on 16/01/2023. +// + +import Foundation +import Security + +// Implementation for PlayKeychain +// World's Most Advanced Keychain Replacement Solution:tm: +// This is a joke, don't take it seriously + +public class PlayKeychain: NSObject { + static let shared = PlayKeychain() + + private static func getKeychainDirectory() -> URL? { + let bundleID = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? "" + let keychainFolder = URL(fileURLWithPath: "/Users/\(NSUserName())/Library/Containers/io.playcover.PlayCover") + .appendingPathComponent("PlayChain") + .appendingPathComponent(bundleID) + + // Create the keychain folder if it doesn't exist + if !FileManager.default.fileExists(atPath: keychainFolder.path) { + do { + try FileManager.default.createDirectory(at: keychainFolder, + withIntermediateDirectories: true, + attributes: nil) + } catch { + debugLogger("Failed to create keychain folder") + } + } + + return keychainFolder + } + + private static func keychainPath(_ attributes: NSDictionary) -> URL { + let keychainFolder = getKeychainDirectory() + // Generate a key path based on the key attributes + let accountName = attributes[kSecAttrAccount as String] as? String ?? "" + let serviceName = attributes[kSecAttrService as String] as? String ?? "" + let classType = attributes[kSecClass as String] as? String ?? "" + return keychainFolder! + .appendingPathComponent("\(serviceName)-\(accountName)-\(classType).plist") + } + + @objc public static func debugLogger(_ logContent: String) { + if PlaySettings.shared.settingsData.playChainDebugging { + NSLog("PC-DEBUG: \(logContent)") + } + } + // Emulates SecItemAdd, SecItemUpdate, SecItemDelete and SecItemCopyMatching + // Store the entire dictionary as a plist + // SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) + @objc static public func add(_ attributes: NSDictionary, result: UnsafeMutablePointer?) -> OSStatus { + let keychainPath = keychainPath(attributes) + // Check if the keychain file already exists + if FileManager.default.fileExists(atPath: keychainPath.path) { + debugLogger("Keychain file already exists") + return errSecDuplicateItem + } + // Write the dictionary to the keychain file + do { + try attributes.write(to: keychainPath) + debugLogger("Wrote keychain file to \(keychainPath)") + } catch { + debugLogger("Failed to write keychain file") + return errSecIO + } + // Place v_data in the result + if let v_data = attributes["v_data"] { + result?.pointee = v_data as CFTypeRef + } + return errSecSuccess + } + + // SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate) + @objc static public func update(_ query: NSDictionary, attributesToUpdate: NSDictionary) -> OSStatus { + // Get the path to the keychain file + let keychainPath = keychainPath(query) + // Read the dictionary from the keychain file + let keychainDict = NSDictionary(contentsOf: keychainPath) + debugLogger("Read keychain file from \(keychainPath)") + // Check if the file exist + if keychainDict == nil { + debugLogger("Keychain file not found at \(keychainPath)") + return errSecItemNotFound + } + // Reconstruct the dictionary (subscripting won't work as assignment is not allowed) + let newKeychainDict = NSMutableDictionary() + for (key, value) in keychainDict! { + newKeychainDict.setValue(value, forKey: key as! String) // swiftlint:disable:this force_cast + } + // Update the dictionary + for (key, value) in attributesToUpdate { + newKeychainDict.setValue(value, forKey: key as! String) // swiftlint:disable:this force_cast + } + // Write the dictionary to the keychain file + do { + try newKeychainDict.write(to: keychainPath) + debugLogger("Wrote keychain file to \(keychainPath)") + } catch { + debugLogger("Failed to write keychain file") + return errSecIO + } + + return errSecSuccess + } + + // SecItemDelete(CFDictionaryRef query) + @objc static public func delete(_ query: NSDictionary) -> OSStatus { + // Get the path to the keychain file + let keychainPath = keychainPath(query) + // Delete the keychain file + do { + try FileManager.default.removeItem(at: keychainPath) + debugLogger("Deleted keychain file at \(keychainPath)") + } catch { + debugLogger("Failed to delete keychain file") + return errSecIO + } + return errSecSuccess + } + + // SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) + @objc static public func copyMatching(_ query: NSDictionary, result: UnsafeMutablePointer?) + -> OSStatus { + // Get the path to the keychain file + let keychainPath = keychainPath(query) + // Read the dictionary from the keychain file + let keychainDict = NSDictionary(contentsOf: keychainPath) + // Check the `r_Attributes` key. If it is set to 1 in the query + // DROP, NOT IMPLEMENTED + let classType = query[kSecClass as String] as? String ?? "" + if query["r_Attributes"] as? Int == 1 { + return errSecItemNotFound + } + // If the keychain file doesn't exist, return errSecItemNotFound + if keychainDict == nil { + debugLogger("Keychain file not found at \(keychainPath)") + return errSecItemNotFound + } + // Return v_Data if it exists + if let vData = keychainDict!["v_Data"] { + debugLogger("Read keychain file from \(keychainPath)") + // Check the class type, if it is a key we need to return the data + // as SecKeyRef, otherwise we can return it as a CFTypeRef + if classType == "keys" { + // kSecAttrKeyType is stored as `type` in the dictionary + // kSecAttrKeyClass is stored as `kcls` in the dictionary + let keyAttributes = [ + kSecAttrKeyType: keychainDict!["type"] as! CFString, // swiftlint:disable:this force_cast + kSecAttrKeyClass: keychainDict!["kcls"] as! CFString // swiftlint:disable:this force_cast + ] + let keyData = vData as! Data // swiftlint:disable:this force_cast + let key = SecKeyCreateWithData(keyData as CFData, keyAttributes as CFDictionary, nil) + result?.pointee = key + return errSecSuccess + } + result?.pointee = vData as CFTypeRef + return errSecSuccess + } + + return errSecItemNotFound + } +} diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index 1994e1a2..e2ca2124 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -92,6 +92,70 @@ static int pt_sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void * DYLD_INTERPOSE(pt_sysctlbyname, sysctlbyname) DYLD_INTERPOSE(pt_sysctl, sysctl) +// Interpose Apple Keychain functions (SecItemCopyMatching, SecItemAdd, SecItemUpdate, SecItemDelete) +// This allows us to intercept keychain requests and return our own data + +// Use the implementations from PlayKeychain +static OSStatus pt_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) { + OSStatus retval; + if ([[PlaySettings shared] playChain]) { + retval = [PlayKeychain copyMatching:(__bridge NSDictionary * _Nonnull)(query) result:result]; + } else { + retval = SecItemCopyMatching(query, result); + } + if (result != NULL) { + [PlayKeychain debugLogger:[NSString stringWithFormat:@"SecItemCopyMatching: %@", query]]; + [PlayKeychain debugLogger:[NSString stringWithFormat:@"SecItemCopyMatching result: %@", *result]]; + } + return retval; +} + +static OSStatus pt_SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) { + OSStatus retval; + if ([[PlaySettings shared] playChain]) { + retval = [PlayKeychain add:(__bridge NSDictionary * _Nonnull)(attributes) result:result]; + } else { + retval = SecItemAdd(attributes, result); + } + if (result != NULL) { + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemAdd: %@", attributes]]; + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemAdd result: %@", *result]]; + } + return retval; +} + +static OSStatus pt_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate) { + OSStatus retval; + if ([[PlaySettings shared] playChain]) { + retval = [PlayKeychain update:(__bridge NSDictionary * _Nonnull)(query) attributesToUpdate:(__bridge NSDictionary * _Nonnull)(attributesToUpdate)]; + } else { + retval = SecItemUpdate(query, attributesToUpdate); + } + if (attributesToUpdate != NULL) { + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemUpdate: %@", query]]; + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemUpdate attributesToUpdate: %@", attributesToUpdate]]; + } + return retval; + +} + +static OSStatus pt_SecItemDelete(CFDictionaryRef query) { + OSStatus retval; + if ([[PlaySettings shared] playChain]) { + retval = [PlayKeychain delete:(__bridge NSDictionary * _Nonnull)(query)]; + } else { + retval = SecItemDelete(query); + } + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemDelete: %@", query]]; + return retval; +} + +DYLD_INTERPOSE(pt_SecItemCopyMatching, SecItemCopyMatching) +DYLD_INTERPOSE(pt_SecItemAdd, SecItemAdd) +DYLD_INTERPOSE(pt_SecItemUpdate, SecItemUpdate) +DYLD_INTERPOSE(pt_SecItemDelete, SecItemDelete) + + @implementation PlayLoader static void __attribute__((constructor)) initialize(void) { diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index 38b98510..0f95eab6 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -33,11 +33,9 @@ let settings = PlaySettings.shared lazy var sensitivity = settingsData.sensitivity / 100 - @objc lazy var windowSizeHeight = CGFloat(settingsData.windowHeight) + lazy var windowSizeHeight = CGFloat(settingsData.windowHeight) - @objc lazy var windowSizeWidth = CGFloat(settingsData.windowWidth) - - @objc lazy var inverseScreenValues = settingsData.inverseScreenValues + lazy var windowSizeWidth = CGFloat(settingsData.windowWidth) @objc lazy var adaptiveDisplay = settingsData.resolution == 0 ? false : true @@ -61,6 +59,10 @@ let settings = PlaySettings.shared return "J320xAP" } }() + + @objc lazy var playChain = settingsData.playChain + + @objc lazy var playChainDebugging = settingsData.playChainDebugging } struct AppSettingsData: Codable { @@ -78,5 +80,6 @@ struct AppSettingsData: Codable { var bypass = false var discordActivity = DiscordActivity() var version = "2.0.0" - var inverseScreenValues = false + var playChain = false + var playChainDebugging = false } From dd1518fafa45ad182ed6b7069c59277780401f85 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 7 Feb 2023 17:36:22 +0700 Subject: [PATCH 08/54] fix: Prevent ObjC object dereferencing if debugging is not enabled --- PlayTools/PlayLoader.m | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index e2ca2124..18164bbf 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -104,8 +104,10 @@ static OSStatus pt_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) retval = SecItemCopyMatching(query, result); } if (result != NULL) { - [PlayKeychain debugLogger:[NSString stringWithFormat:@"SecItemCopyMatching: %@", query]]; - [PlayKeychain debugLogger:[NSString stringWithFormat:@"SecItemCopyMatching result: %@", *result]]; + if ([[PlaySettings shared] playChainDebugging]) { + [PlayKeychain debugLogger:[NSString stringWithFormat:@"SecItemCopyMatching: %@", query]]; + [PlayKeychain debugLogger:[NSString stringWithFormat:@"SecItemCopyMatching result: %@", *result]]; + } } return retval; } @@ -118,8 +120,10 @@ static OSStatus pt_SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) { retval = SecItemAdd(attributes, result); } if (result != NULL) { - [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemAdd: %@", attributes]]; - [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemAdd result: %@", *result]]; + if ([[PlaySettings shared] playChainDebugging]) { + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemAdd: %@", attributes]]; + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemAdd result: %@", *result]]; + } } return retval; } @@ -132,8 +136,10 @@ static OSStatus pt_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attribut retval = SecItemUpdate(query, attributesToUpdate); } if (attributesToUpdate != NULL) { - [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemUpdate: %@", query]]; - [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemUpdate attributesToUpdate: %@", attributesToUpdate]]; + if ([[PlaySettings shared] playChainDebugging]) { + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemUpdate: %@", query]]; + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemUpdate attributesToUpdate: %@", attributesToUpdate]]; + } } return retval; @@ -146,7 +152,9 @@ static OSStatus pt_SecItemDelete(CFDictionaryRef query) { } else { retval = SecItemDelete(query); } - [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemDelete: %@", query]]; + if ([[PlaySettings shared] playChainDebugging]) { + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecItemDelete: %@", query]]; + } return retval; } From b70a41c83bc175190e1e045738eb30fa17e33212 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Mon, 13 Feb 2023 01:14:54 +0800 Subject: [PATCH 09/54] Add new toast UI and operation hint message (#82) * add new hint toast * limit toast number to four --- PlayTools/Controls/ControlMode.swift | 12 ++ PlayTools/Controls/PlayInput.swift | 141 +++++++++--------------- PlayTools/Keymap/EditorController.swift | 7 +- PlayTools/Utils/Toast.swift | 95 ++++++++++++++++ 4 files changed, 162 insertions(+), 93 deletions(-) diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index 35661efb..df298807 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -16,6 +16,8 @@ public class ControlMode { if !editor.editorMode { if show { if !visible { + NotificationCenter.default.post(name: NSNotification.Name.playtoolsKeymappingWillDisable, + object: nil, userInfo: [:]) if screen.fullscreen { screen.switchDock(true) } @@ -26,6 +28,8 @@ public class ControlMode { } } else { if visible { + NotificationCenter.default.post(name: NSNotification.Name.playtoolsKeymappingWillEnable, + object: nil, userInfo: [:]) if PlaySettings.shared.mouseMapping { AKInterface.shared!.hideCursor() } @@ -40,3 +44,11 @@ public class ControlMode { } } } + +extension NSNotification.Name { + public static let playtoolsKeymappingWillEnable: NSNotification.Name + = NSNotification.Name("playtools.keymappingWillEnable") + + public static let playtoolsKeymappingWillDisable: NSNotification.Name + = NSNotification.Name("playtools.keymappingWillDisable") +} diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index e541c285..6b031afd 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -10,47 +10,11 @@ class PlayInput { static private var lCmdPressed = false static private var rCmdPressed = false - static public var buttonHandlers: [String: [(Bool) -> Void]] = [:] - func invalidate() { + PlayMice.shared.stop() for action in self.actions { action.invalidate() } - PlayInput.buttonHandlers.removeAll(keepingCapacity: true) - GCController.current?.extendedGamepad?.valueChangedHandler = nil - } - - static public func registerButton(key: String, handler: @escaping (Bool) -> Void) { - if PlayInput.buttonHandlers[key] == nil { - PlayInput.buttonHandlers[key] = [] - } - PlayInput.buttonHandlers[key]!.append(handler) - } - - func keyboardHandler(_ keyCode: UInt16, _ pressed: Bool) { - let name = KeyCodeNames.virtualCodes[keyCode] ?? "Btn" - guard let handlers = PlayInput.buttonHandlers[name] else { - return - } - for handler in handlers { - handler(pressed) - } - } - - func controllerButtonHandler(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { - let name: String = element.aliases.first! - if let buttonElement = element as? GCControllerButtonInput { -// Toast.showOver(msg: "recognised controller button: \(name)") - guard let handlers = PlayInput.buttonHandlers[name] else { return } - Toast.showOver(msg: name + ": \(buttonElement.isPressed)") - for handler in handlers { - handler(buttonElement.isPressed) - } - } else if let dpadElement = element as? GCControllerDirectionPad { - PlayMice.shared.handleControllerDirectionPad(profile, dpadElement) - } else { - Toast.showOver(msg: "unrecognised controller element input happens") - } } func parseKeymap() { @@ -86,8 +50,7 @@ class PlayInput { keyboard.keyChangedHandler = { _, _, keyCode, _ in if !PlayInput.cmdPressed() && !PlayInput.FORBIDDEN.contains(keyCode) - && self.isSafeToBind(keyboard) - && KeyCodeNames.keyCodes[keyCode.rawValue] != nil { + && self.isSafeToBind(keyboard) { EditorController.shared.setKey(keyCode.rawValue) } } @@ -95,33 +58,27 @@ class PlayInput { if let controller = GCController.current?.extendedGamepad { controller.valueChangedHandler = { _, element in // This is the index of controller buttons, which is String, not Int - var alias: String = element.aliases.first! - if alias == "Direction Pad" { - guard let dpadElement = element as? GCControllerDirectionPad else { - Toast.showOver(msg: "cannot map direction pad: element type not recognizable") - return - } - if dpadElement.xAxis.value > 0 { - alias = dpadElement.right.aliases.first! - } else if dpadElement.xAxis.value < 0 { - alias = dpadElement.left.aliases.first! - } - if dpadElement.yAxis.value > 0 { - alias = dpadElement.down.aliases.first! - } else if dpadElement.yAxis.value < 0 { - alias = dpadElement.up.aliases.first! - } - } + let alias: String! = element.aliases.first EditorController.shared.setKey(alias) } } + } else { + GCKeyboard.coalesced!.keyboardInput!.keyChangedHandler = nil + GCController.current?.extendedGamepad?.valueChangedHandler = nil } } func setup() { parseKeymap() - GCKeyboard.coalesced?.keyboardInput?.keyChangedHandler = nil - GCController.current?.extendedGamepad?.valueChangedHandler = controllerButtonHandler + + for mouse in GCMouse.mice() { + if settings.mouseMapping { + mouse.mouseInput?.mouseMovedHandler = PlayMice.shared.handleMouseMoved + } else { + mouse.mouseInput?.mouseMovedHandler = PlayMice.shared.handleFakeMouseMoved + } + } + } static public func cmdPressed() -> Bool { @@ -145,21 +102,23 @@ class PlayInput { .printScreen ] - private func swapMode() { + private func swapMode(_ pressed: Bool) { if !settings.mouseMapping { return } - if !mode.visible { - self.invalidate() + if pressed { + if !mode.visible { + self.invalidate() + } + mode.show(!mode.visible) } - mode.show(!mode.visible) } var root: UIViewController? { return screen.window?.rootViewController } - func setupHotkeys() { + func setupShortcuts() { if let keyboard = GCKeyboard.coalesced?.keyboardInput { keyboard.button(forKeyCode: .leftGUI)?.pressedChangedHandler = { _, _, pressed in PlayInput.lCmdPressed = pressed @@ -167,7 +126,12 @@ class PlayInput { keyboard.button(forKeyCode: .rightGUI)?.pressedChangedHandler = { _, _, pressed in PlayInput.rCmdPressed = pressed } - // TODO: set a timeout to display usage guide of Option and Keymapping menu in turn + keyboard.button(forKeyCode: .leftAlt)?.pressedChangedHandler = { _, _, pressed in + self.swapMode(pressed) + } + keyboard.button(forKeyCode: .rightAlt)?.pressedChangedHandler = { _, _, pressed in + self.swapMode(pressed) + } } } @@ -180,7 +144,7 @@ class PlayInput { let main = OperationQueue.main centre.addObserver(forName: NSNotification.Name.GCKeyboardDidConnect, object: nil, queue: main) { _ in - self.setupHotkeys() + self.setupShortcuts() if !mode.visible { self.setup() } @@ -196,36 +160,31 @@ class PlayInput { if !mode.visible { self.setup() } - if EditorController.shared.editorMode { - self.toggleEditor(show: true) - } } - centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, - queue: main) { _ in - if !mode.visible && settings.mouseMapping { - AKInterface.shared!.warpCursor() + setupShortcuts() + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { + if !settings.mouseMapping || !mode.visible { + return + } + Toast.showHint(title: "Keymapping Disabled", text: ["Press ", "option ⌥", " to enable keymapping"], + notification: NSNotification.Name.playtoolsKeymappingWillEnable) + let center = NotificationCenter.default + var token: NSObjectProtocol? + token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token!) + Toast.showHint(title: "Keymapping Enabled", text: ["Press ", "option ⌥", " to disable keymapping"], + notification: NSNotification.Name.playtoolsKeymappingWillDisable) } } - setupHotkeys() - AKInterface.shared!.initialize(keyboard: {keycode, pressed in - let consumed = !mode.visible && !PlayInput.cmdPressed() - if !consumed { - return false - } - self.keyboardHandler(keycode, pressed) - return consumed - }, mouseMoved: {deltaX, deltaY in - if mode.visible { - return false - } - if settings.mouseMapping { - PlayMice.shared.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) - } else { - PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) - } - return true - }, swapMode: self.swapMode) + // Fix beep sound + AKInterface.shared! + .eliminateRedundantKeyPressEvents(self.dontIgnore) + } + + func dontIgnore() -> Bool { + (mode.visible && !EditorController.shared.editorMode) || PlayInput.cmdPressed() } } diff --git a/PlayTools/Keymap/EditorController.swift b/PlayTools/Keymap/EditorController.swift index 1fe47c66..c6f7075e 100644 --- a/PlayTools/Keymap/EditorController.swift +++ b/PlayTools/Keymap/EditorController.swift @@ -60,14 +60,17 @@ class EditorController { previousWindow?.makeKeyAndVisible() PlayInput.shared.toggleEditor(show: false) focusedControl = nil - Toast.showOver(msg: "Keymapping saved") + Toast.showHint(title: "Keymap Saved") } else { PlayInput.shared.toggleEditor(show: true) previousWindow = screen.keyWindow editorWindow = initWindow() editorWindow?.makeKeyAndVisible() showButtons() - Toast.showOver(msg: "Click to start keymmaping edit") + Toast.showHint(title: "Keymapping Editor", + text: ["Click a button to edit its position or key bind\n" + + "Click an empty area to open input menu"], + notification: NSNotification.Name.playtoolsKeymappingWillEnable) } // Toast.showOver(msg: "\(UIApplication.shared.windows.count)") lock.unlock() diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 4bf90244..44fba46a 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -12,6 +12,101 @@ class Toast { Toast.show(message: msg, parent: parent) } } + static var hintView: [UIView] = [] + + private static let gap: CGFloat = 40 + + public static func hideHint(hint: UIView) { + guard let id = hintView.firstIndex(of: hint) else {return} + for index in 0.. id { + hintView[index-1] = hintView[index] + } + } + hintView.removeLast() + UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseOut, animations: { + hint.alpha = 0.0 + }, completion: {_ in + hint.removeFromSuperview() + }) + } + + private static func getAttributedString(title: String, text: [String]) -> NSMutableAttributedString { + var heading = title + if !text.isEmpty { + heading += "\n" + } + let txt = NSMutableAttributedString(string: text.reduce(into: heading, { result, string in + result += string + })) + var messageLength = 0 + var highlight = false + for msg in text { + txt.addAttribute(.foregroundColor, value: highlight ? UIColor.cyan: UIColor.white, + range: NSRange(location: heading.count + messageLength, length: msg.count)) + highlight = !highlight + messageLength += msg.count + } + let style = NSMutableParagraphStyle() + style.alignment = .center + txt.addAttribute(.paragraphStyle, value: style, + range: NSRange(location: 0, length: heading.count + messageLength)) + txt.addAttribute(.font, value: UIFont.systemFont(ofSize: 28, weight: .bold), + range: NSRange(location: 0, length: heading.count)) + txt.addAttribute(.foregroundColor, value: UIColor.white, + range: NSRange(location: 0, length: heading.count)) + txt.addAttribute(.font, value: UIFont.systemFont(ofSize: 28), + range: NSRange(location: heading.count, length: messageLength)) + return txt + } + + public static func showHint(title: String, text: [String] = [], timeout: Double = 3, + notification: NSNotification.Name? = nil) { + let parent = screen.keyWindow! + + // Width and height here serve as an upper limit. + // Text would fill width first, then wrap, then fill height, then scroll + let messageLabel = UITextView(frame: CGRect(x: 0, y: 0, width: 800, height: 800)) + messageLabel.attributedText = getAttributedString(title: title, text: text) + messageLabel.backgroundColor = UIColor.black.withAlphaComponent(0.5) + messageLabel.alpha = 1.0 + messageLabel.clipsToBounds = true + messageLabel.isUserInteractionEnabled = false + messageLabel.frame.size = messageLabel.sizeThatFits(messageLabel.frame.size) + messageLabel.layer.cornerCurve = CALayerCornerCurve.continuous + messageLabel.layer.cornerRadius = messageLabel.frame.size.height / 4 + messageLabel.frame.size.width += messageLabel.layer.cornerRadius * 2 + messageLabel.center.x = parent.center.x + messageLabel.center.y = -messageLabel.frame.size.height / 2 + + hintView.append(messageLabel) + parent.addSubview(messageLabel) + + if hintView.count > 4 { + hideHint(hint: hintView.first!) + } + if let note = notification { + let center = NotificationCenter.default + var token: NSObjectProtocol? + token = center.addObserver(forName: note, object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token!) + hideHint(hint: messageLabel) + } + } else { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + timeout) { + hideHint(hint: messageLabel) + } + } + for view in hintView { + UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseIn, animations: { + view.layer.position.y += messageLabel.frame.size.height + gap + }) + } + } // swiftlint:disable function_body_length From edfc1fe27b3f7bb507862a8556a9e9f273da727e Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 7 Feb 2023 18:15:21 +0700 Subject: [PATCH 10/54] fix: void out unavailable VSS calls --- .../Controls/PTFakeTouch/NSObject+Swizzle.m | 93 +++---------------- 1 file changed, 12 insertions(+), 81 deletions(-) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index e6614c6c..99e0012b 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -54,23 +54,6 @@ - (BOOL) hook_prefersPointerLocked { return false; } -- (CGRect) hook_frameDefault { - return [PlayScreen frameDefault:[self hook_frameDefault]]; -} - -- (CGRect) hook_boundsDefault { - return [PlayScreen boundsDefault:[self hook_boundsDefault]]; -} - -- (CGRect) hook_nativeBoundsDefault { - return [PlayScreen nativeBoundsDefault:[self hook_nativeBoundsDefault]]; -} - -- (CGSize) hook_sizeDelfault { - return [PlayScreen sizeAspectRatioDefault:[self hook_sizeDelfault]]; -} - - - (CGRect) hook_frame { return [PlayScreen frame:[self hook_frame]]; } @@ -88,7 +71,6 @@ - (CGSize) hook_size { } - - (long long) hook_orientation { return 0; } @@ -101,16 +83,6 @@ - (double) hook_scale { return 2.0; } -- (double) get_default_height { - return [[UIScreen mainScreen] bounds].size.height; - -} -- (double) get_default_width { - return [[UIScreen mainScreen] bounds].size.width; - -} - - - (void) hook_setCurrentSubscription:(VSSubscription *)currentSubscription { // do nothing } @@ -152,62 +124,21 @@ -(CGRect) hook_frame { However, doing this would require generating @interface declarations (either with class-dump or by hand) which would add a lot of code and complexity. I'm not sure this trade-off is "worth it", at least at the time of writing. */ - @implementation PTSwizzleLoader + (void)load { - // This might need refactor soon - if(@available(iOS 16.3, *)) { - if ([[PlaySettings shared] adaptiveDisplay]) { - // This is an experimental fix - if ([[PlaySettings shared] inverseScreenValues]) { - // This lines set External Scene settings and other IOS10 Runtime services by swizzling - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; - [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; - - // Fixes Apple mess at MacOS 13.2 - [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBoundsDefault)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; - } else { - // This acutally runs when adaptiveDisplay is normally triggered - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; - [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; - - [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBounds)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; - } - } - else { - CGFloat newValueW = (CGFloat) [self get_default_width]; - [[PlaySettings shared] setValue:@(newValueW) forKey:@"windowSizeWidth"]; - - CGFloat newValueH = (CGFloat)[self get_default_height]; - [[PlaySettings shared] setValue:@(newValueH) forKey:@"windowSizeHeight"]; - if (![[PlaySettings shared] inverseScreenValues]) { - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; - [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; - } - [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBoundsDefault)]; - - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; - } - } - else { - if ([[PlaySettings shared] adaptiveDisplay]) { - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; - [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; - } + if ([[PlaySettings shared] adaptiveDisplay]) { + // This lines set external Scene (frame and those things) settings and other IOS10 Runtime services by swizzling + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; + [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; + + // This actually fixes Apple mess at MacOS 13.2 + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBounds)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; } - + [objc_getClass("_UIMenuBuilder") swizzleInstanceMethod:sel_getUid("initWithRootMenu:") withMethod:@selector(initWithRootMenuHook:)]; [objc_getClass("IOSViewController") swizzleInstanceMethod:@selector(prefersPointerLocked) withMethod:@selector(hook_prefersPointerLocked)]; From 29ebd3d1339794a32057f39af7b3fb3cd09a69cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Moreno?= <47700212+JoseMoreville@users.noreply.github.com> Date: Tue, 14 Feb 2023 19:02:12 -0700 Subject: [PATCH 11/54] Fix: Default app size now shows normally and other Adaptive fixes plus responsive fix on default (#77) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix for default screen * Code cleanup * setting @objc to scene control vars * PR to make default size work correctly (#1) * Reverting cleanup * Testing removing new scaling methods for default * Revert to new methods for testing * Setting orientation to 1 * 4 * commenting suspect stuff * 12 * sd * removing FBS * test * returning back to normal * Test * a * test * x * a * a * a * as * 1 * as * así * aa * as * Change names * testing something * testing 2 * more test * 12 * return * 123 * Testing defaults and auto fix * Testing c * is this ready? * test * 1 * returning adaptive * Delete PlayScreenDefault.swift * Update project.pbxproj Removing All references to PlayScreenDefault.swift * Adds fix for some apps with adaptive display enabled (#3) * testing changing to default bounds * Update project.pbxproj (#2) Removing All references to PlayScreenDefault.swift * test 2 * rest * test * as * asd * test * 123 * Adding inverseScreenValues to settings * Reduced code and added ternaries * added else when inverseScreenValues is false (might rework * Adds MacOSVersion to plist and a 'fix' for screen issue (#4) * testing changing to default bounds * Update project.pbxproj (#2) Removing All references to PlayScreenDefault.swift * test 2 * rest * test * as * asd * test * 123 * Adding inverseScreenValues to settings * Reduced code and added ternaries * added else when inverseScreenValues is false (might rework * Test to fix pre MacOS 13.2 * Check this out * Using float instead of string * v2 * test * lets see * adding print * NSLog(@"macOS version: %f", [[PlaySettings shared] macOSVersion]); * [[PlaySettings shared] macOSVersion] rounded * adding 00000 to every float * testing * return back to normal double * Forgot about floating point error * more than 13.1 * more * 13.199 * setting to latest macOS version * more floating error and macOS fixes * reduced lines and fix some errors * Update project.pbxproj * Update project.pbxproj * Update PlaySettings.swift * Enables responsiveness for some apps for default screen settings if toggle enabled (#5) * test disabling FBSDisplayMode * disabling FBS * Adding experimental fix toggle to fix responsiveness * adding an extra log * extra * moving the function to a better place * back to another place * added CheckResizabilityHasRun * Testing some stuff * name issue * added some comments to test * added fuction stop * True to yes * Changed yes to no * adding observer for resize * adding chess * test * back again * Removing extra check for a future commit * changed macos check to ios check * Remove macOSVersion --- PlayTools.xcodeproj/project.pbxproj | 8 +- .../Controls/PTFakeTouch/NSObject+Swizzle.m | 96 +++++++++++++++---- PlayTools/PlayLoader.m | 1 + PlayTools/PlaySettings.swift | 7 +- 4 files changed, 89 insertions(+), 23 deletions(-) diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index e25d9dc2..8c455222 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2847AE48298EFC0F00B0F983 /* PlayScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2847AE47298EFC0F00B0F983 /* PlayScreen.swift */; }; 6E76639B28D0FAE700DE4AF9 /* Plugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E76639A28D0FAE700DE4AF9 /* Plugin.swift */; }; 6E76639C28D0FAE700DE4AF9 /* Plugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E76639A28D0FAE700DE4AF9 /* Plugin.swift */; }; 6E7663A128D0FB5300DE4AF9 /* AKPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7663A028D0FB5300DE4AF9 /* AKPlugin.swift */; }; @@ -14,7 +15,6 @@ 6E84A14528D0F94E00BF7495 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CBA287ABFD5000BEE9D /* UIKit.framework */; }; 6E84A15028D0F97500BF7495 /* AKInterface.bundle in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; AA71970B287A44D200623C15 /* PlayLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = AA719702287A44D200623C15 /* PlayLoader.m */; }; - AA71970C287A44D200623C15 /* PlayScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719703287A44D200623C15 /* PlayScreen.swift */; }; AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719704287A44D200623C15 /* PlaySettings.swift */; }; AA71970E287A44D200623C15 /* PlayLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719705287A44D200623C15 /* PlayLoader.h */; }; AA71970F287A44D200623C15 /* PlayCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719706287A44D200623C15 /* PlayCover.swift */; }; @@ -67,6 +67,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 2847AE47298EFC0F00B0F983 /* PlayScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayScreen.swift; sourceTree = ""; }; 6E76639628D0FA6200DE4AF9 /* AKInterface-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AKInterface-Bridging-Header.h"; sourceTree = ""; }; 6E76639A28D0FAE700DE4AF9 /* Plugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Plugin.swift; sourceTree = ""; }; 6E7663A028D0FB5300DE4AF9 /* AKPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AKPlugin.swift; sourceTree = ""; }; @@ -74,7 +75,6 @@ 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AKInterface.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; AA7196D8287A447700623C15 /* PlayTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlayTools.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AA719702287A44D200623C15 /* PlayLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlayLoader.m; sourceTree = ""; }; - AA719703287A44D200623C15 /* PlayScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayScreen.swift; sourceTree = ""; }; AA719704287A44D200623C15 /* PlaySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaySettings.swift; sourceTree = ""; }; AA719705287A44D200623C15 /* PlayLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlayLoader.h; sourceTree = ""; }; AA719706287A44D200623C15 /* PlayCover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayCover.swift; sourceTree = ""; }; @@ -175,11 +175,11 @@ AA719791287A481500623C15 /* Utils */, AA71970A287A44D200623C15 /* Info.plist */, AA719706287A44D200623C15 /* PlayCover.swift */, - AA719703287A44D200623C15 /* PlayScreen.swift */, AA719704287A44D200623C15 /* PlaySettings.swift */, AA719705287A44D200623C15 /* PlayLoader.h */, AA719702287A44D200623C15 /* PlayLoader.m */, AA719708287A44D200623C15 /* PlayTools.h */, + 2847AE47298EFC0F00B0F983 /* PlayScreen.swift */, ); path = PlayTools; sourceTree = ""; @@ -445,6 +445,7 @@ AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */, AA719759287A480D00623C15 /* PlayAction.swift in Sources */, 6E7663A528D0FEBE00DE4AF9 /* AKPluginLoader.swift in Sources */, + 2847AE48298EFC0F00B0F983 /* PlayScreen.swift in Sources */, AA7197A2287A481500623C15 /* CircleMenu.swift in Sources */, AA71978B287A480D00623C15 /* MenuController.swift in Sources */, AA7197AA287A481500623C15 /* Toast.swift in Sources */, @@ -457,7 +458,6 @@ AA7197A3287A481500623C15 /* CircleMenuButton.swift in Sources */, AA7197A9287A481500623C15 /* PlayInfo.swift in Sources */, AA71986A287A81A000623C15 /* PTFakeMetaTouch.m in Sources */, - AA71970C287A44D200623C15 /* PlayScreen.swift in Sources */, ABCECEE629750BA600746595 /* PlayedApple.swift in Sources */, AA71986C287A81A000623C15 /* NSObject+Swizzle.m in Sources */, AA71970F287A44D200623C15 /* PlayCover.swift in Sources */, diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index 99e0012b..13b0de40 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -11,7 +11,6 @@ #import "UIKit/UIKit.h" #import #import "PTFakeMetaTouch.h" -#import __attribute__((visibility("hidden"))) @interface PTSwizzleLoader : NSObject @@ -54,6 +53,23 @@ - (BOOL) hook_prefersPointerLocked { return false; } +- (CGRect) hook_frameDefault { + return [PlayScreen frameDefault:[self hook_frameDefault]]; +} + +- (CGRect) hook_boundsDefault { + return [PlayScreen boundsDefault:[self hook_boundsDefault]]; +} + +- (CGRect) hook_nativeBoundsDefault { + return [PlayScreen nativeBoundsDefault:[self hook_nativeBoundsDefault]]; +} + +- (CGSize) hook_sizeDelfault { + return [PlayScreen sizeAspectRatioDefault:[self hook_sizeDelfault]]; +} + + - (CGRect) hook_frame { return [PlayScreen frame:[self hook_frame]]; } @@ -71,6 +87,7 @@ - (CGSize) hook_size { } + - (long long) hook_orientation { return 0; } @@ -83,11 +100,17 @@ - (double) hook_scale { return 2.0; } -- (void) hook_setCurrentSubscription:(VSSubscription *)currentSubscription { - // do nothing +- (double) get_default_height { + return [[UIScreen mainScreen] bounds].size.height; + +} +- (double) get_default_width { + return [[UIScreen mainScreen] bounds].size.width; + } + bool menuWasCreated = false; - (id) initWithRootMenuHook:(id)rootMenu { self = [self initWithRootMenuHook:rootMenu]; @@ -124,25 +147,64 @@ -(CGRect) hook_frame { However, doing this would require generating @interface declarations (either with class-dump or by hand) which would add a lot of code and complexity. I'm not sure this trade-off is "worth it", at least at the time of writing. */ + @implementation PTSwizzleLoader + (void)load { - if ([[PlaySettings shared] adaptiveDisplay]) { - // This lines set external Scene (frame and those things) settings and other IOS10 Runtime services by swizzling - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; - [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; - - // This actually fixes Apple mess at MacOS 13.2 - [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBounds)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; + // This might need refactor soon + if(@available(iOS 16.3, *)) { + if ([[PlaySettings shared] adaptiveDisplay]) { + // This is an experimental fix + if ([[PlaySettings shared] inverseScreenValues]) { + // This lines set External Scene settings and other IOS10 Runtime services by swizzling + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; + [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; + + // Fixes Apple mess at MacOS 13.2 + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBoundsDefault)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; + } else { + // This acutally runs when adaptiveDisplay is normally triggered + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; + [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; + + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBounds)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; + } + } + else { + CGFloat newValueW = (CGFloat) [self get_default_width]; + [[PlaySettings shared] setValue:@(newValueW) forKey:@"windowSizeWidth"]; + + CGFloat newValueH = (CGFloat)[self get_default_height]; + [[PlaySettings shared] setValue:@(newValueH) forKey:@"windowSizeHeight"]; + if (![[PlaySettings shared] inverseScreenValues]) { + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; + [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; + } + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBoundsDefault)]; + + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; + } + } + else { + if ([[PlaySettings shared] adaptiveDisplay]) { + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frame)]; + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_bounds)]; + [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_size)]; + } } - + [objc_getClass("_UIMenuBuilder") swizzleInstanceMethod:sel_getUid("initWithRootMenu:") withMethod:@selector(initWithRootMenuHook:)]; [objc_getClass("IOSViewController") swizzleInstanceMethod:@selector(prefersPointerLocked) withMethod:@selector(hook_prefersPointerLocked)]; - - [objc_getClass("VSSubscriptionRegistrationCenter") swizzleInstanceMethod:@selector(setCurrentSubscription:) withMethod:@selector(hook_setCurrentSubscription:)]; } @end diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index 18164bbf..92887d97 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -164,6 +164,7 @@ static OSStatus pt_SecItemDelete(CFDictionaryRef query) { DYLD_INTERPOSE(pt_SecItemDelete, SecItemDelete) + @implementation PlayLoader static void __attribute__((constructor)) initialize(void) { diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index 0f95eab6..f425bb87 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -33,9 +33,11 @@ let settings = PlaySettings.shared lazy var sensitivity = settingsData.sensitivity / 100 - lazy var windowSizeHeight = CGFloat(settingsData.windowHeight) + @objc lazy var windowSizeHeight = CGFloat(settingsData.windowHeight) - lazy var windowSizeWidth = CGFloat(settingsData.windowWidth) + @objc lazy var windowSizeWidth = CGFloat(settingsData.windowWidth) + + @objc lazy var inverseScreenValues = settingsData.inverseScreenValues @objc lazy var adaptiveDisplay = settingsData.resolution == 0 ? false : true @@ -82,4 +84,5 @@ struct AppSettingsData: Codable { var version = "2.0.0" var playChain = false var playChainDebugging = false + var inverseScreenValues = false } From dccf4fa779d9abe4533246d5c3593f06dbdc6559 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sun, 19 Feb 2023 17:04:57 +0800 Subject: [PATCH 12/54] not show hint every launch --- PlayTools/Controls/PlayInput.swift | 26 +++++++++++++++++++++++--- PlayTools/Utils/Toast.swift | 10 +++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 6b031afd..a91b61ea 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -164,18 +164,38 @@ class PlayInput { setupShortcuts() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { - if !settings.mouseMapping || !mode.visible { + self.parseKeymap() + if !settings.mouseMapping || !mode.visible || self.actions.count <= 0 { + return + } + let persistenceKeyname = "playtoolsKeymappingDisabledAt" + let lastUse = UserDefaults.standard.float(forKey: persistenceKeyname) + var thisUse = lastUse + if lastUse < 1 { + thisUse = 2 + } else { + thisUse = Float(Date.timeIntervalSinceReferenceDate) + } + var token2: NSObjectProtocol? + let center = NotificationCenter.default + token2 = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillDisable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token2!) + UserDefaults.standard.set(thisUse, forKey: persistenceKeyname) + } + if lastUse > Float(Date.now.addingTimeInterval(-86400*14).timeIntervalSinceReferenceDate) { return } Toast.showHint(title: "Keymapping Disabled", text: ["Press ", "option ⌥", " to enable keymapping"], + timeout: 10, notification: NSNotification.Name.playtoolsKeymappingWillEnable) - let center = NotificationCenter.default var token: NSObjectProtocol? token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, object: nil, queue: OperationQueue.main) { _ in center.removeObserver(token!) Toast.showHint(title: "Keymapping Enabled", text: ["Press ", "option ⌥", " to disable keymapping"], - notification: NSNotification.Name.playtoolsKeymappingWillDisable) + timeout: 10, + notification: NSNotification.Name.playtoolsKeymappingWillDisable) } } diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 44fba46a..aafc0ad1 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -64,7 +64,7 @@ class Toast { return txt } - public static func showHint(title: String, text: [String] = [], timeout: Double = 3, + public static func showHint(title: String, text: [String] = [], timeout: Double = -3, notification: NSNotification.Name? = nil) { let parent = screen.keyWindow! @@ -89,6 +89,7 @@ class Toast { if hintView.count > 4 { hideHint(hint: hintView.first!) } + var life = timeout if let note = notification { let center = NotificationCenter.default var token: NSObjectProtocol? @@ -96,8 +97,11 @@ class Toast { center.removeObserver(token!) hideHint(hint: messageLabel) } - } else { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + timeout) { + } else if life < 0 { + life = 3 + } + if life >= 0 { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + life) { hideHint(hint: messageLabel) } } From 93ab9e958fb8e3dacd1258522128f3b84a23a0f5 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Mon, 6 Mar 2023 11:30:02 +0700 Subject: [PATCH 13/54] feat: adding an alternate window fix mode for apps that are able to handle display by themselves --- .../Controls/PTFakeTouch/NSObject+Swizzle.m | 33 +++++++++++-------- PlayTools/PlaySettings.swift | 4 +++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index 13b0de40..3855c535 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -178,21 +178,26 @@ + (void)load { } } else { - CGFloat newValueW = (CGFloat) [self get_default_width]; - [[PlaySettings shared] setValue:@(newValueW) forKey:@"windowSizeWidth"]; - - CGFloat newValueH = (CGFloat)[self get_default_height]; - [[PlaySettings shared] setValue:@(newValueH) forKey:@"windowSizeHeight"]; - if (![[PlaySettings shared] inverseScreenValues]) { - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; - [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; - [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; + if ([[PlaySettings shared] windowFixMethod] == 1) { + // do nothing:tm: + } + else { + CGFloat newValueW = (CGFloat) [self get_default_width]; + [[PlaySettings shared] setValue:@(newValueW) forKey:@"windowSizeWidth"]; + + CGFloat newValueH = (CGFloat)[self get_default_height]; + [[PlaySettings shared] setValue:@(newValueH) forKey:@"windowSizeHeight"]; + if (![[PlaySettings shared] inverseScreenValues]) { + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(frame) withMethod:@selector(hook_frameDefault)]; + [objc_getClass("FBSSceneSettings") swizzleInstanceMethod:@selector(bounds) withMethod:@selector(hook_boundsDefault)]; + [objc_getClass("FBSDisplayMode") swizzleInstanceMethod:@selector(size) withMethod:@selector(hook_sizeDelfault)]; + } + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBoundsDefault)]; + + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; + [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; } - [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(orientation) withMethod:@selector(hook_orientation)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeBounds) withMethod:@selector(hook_nativeBoundsDefault)]; - - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(nativeScale) withMethod:@selector(hook_nativeScale)]; - [objc_getClass("UIScreen") swizzleInstanceMethod:@selector(scale) withMethod:@selector(hook_scale)]; } } else { diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index f425bb87..d2443171 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -65,6 +65,9 @@ let settings = PlaySettings.shared @objc lazy var playChain = settingsData.playChain @objc lazy var playChainDebugging = settingsData.playChainDebugging + + @objc lazy var windowFixMethod = settingsData.windowFixMethod + } struct AppSettingsData: Codable { @@ -85,4 +88,5 @@ struct AppSettingsData: Codable { var playChain = false var playChainDebugging = false var inverseScreenValues = false + var windowFixMethod = 0 } From e7774544ba7d50e036325389706e82f1015a3072 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Fri, 31 Mar 2023 13:00:33 +0700 Subject: [PATCH 14/54] feat: res scaler --- PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m | 6 ++++-- PlayTools/PlayScreen.swift | 5 +++-- PlayTools/PlaySettings.swift | 2 ++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index 3855c535..10dd4c6d 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -93,11 +93,13 @@ - (long long) hook_orientation { } - (double) hook_nativeScale { - return 2.0; + return [[PlaySettings shared] customScaler]; } - (double) hook_scale { - return 2.0; + // Return rounded value of [[PlaySettings shared] customScaler] + // Even though it is a double return, this will only accept .0 value or apps will crash + return round([[PlaySettings shared] customScaler]); } - (double) get_default_height { diff --git a/PlayTools/PlayScreen.swift b/PlayTools/PlayScreen.swift index 47bc4ff6..d443a57f 100644 --- a/PlayTools/PlayScreen.swift +++ b/PlayTools/PlayScreen.swift @@ -9,6 +9,7 @@ let screen = PlayScreen.shared let isInvertFixEnabled = PlaySettings.shared.inverseScreenValues && PlaySettings.shared.adaptiveDisplay let mainScreenWidth = !isInvertFixEnabled ? PlaySettings.shared.windowSizeWidth : PlaySettings.shared.windowSizeHeight let mainScreenHeight = !isInvertFixEnabled ? PlaySettings.shared.windowSizeHeight : PlaySettings.shared.windowSizeWidth +let customScaler = PlaySettings.shared.customScaler extension CGSize { func aspectRatio() -> CGFloat { @@ -93,7 +94,7 @@ public class PlayScreen: NSObject { } @objc public static func nativeBounds(_ rect: CGRect) -> CGRect { - return rect.toAspectRatio(2) + return rect.toAspectRatio(CGFloat((customScaler))) } @objc public static func width(_ size: Int) -> Int { @@ -172,7 +173,7 @@ public class PlayScreen: NSObject { } @objc public static func nativeBoundsDefault(_ rect: CGRect) -> CGRect { - return rect.toAspectRatioDefault(2) + return rect.toAspectRatioDefault(CGFloat((customScaler))) } @objc public static func sizeAspectRatioDefault(_ size: CGSize) -> CGSize { diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index d2443171..78d19bac 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -68,6 +68,7 @@ let settings = PlaySettings.shared @objc lazy var windowFixMethod = settingsData.windowFixMethod + @objc lazy var customScaler = settingsData.customScaler } struct AppSettingsData: Codable { @@ -79,6 +80,7 @@ struct AppSettingsData: Codable { var iosDeviceModel = "iPad13,8" var windowWidth = 1920 var windowHeight = 1080 + var customScaler = 2.0 var resolution = 2 var aspectRatio = 1 var notch = false From 71f5450d3b0201111609ab96b856c40bb0971bca Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sat, 8 Apr 2023 12:15:37 +0700 Subject: [PATCH 15/54] fix: C strings in Model ID/OEM ID --- PlayTools/PlayLoader.m | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index 92887d97..db7eec18 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -12,8 +12,9 @@ #import "NSObject+Swizzle.h" // Get device model from playcover .plist -#define DEVICE_MODEL ([[[PlaySettings shared] deviceModel] UTF8String]) -#define OEM_ID ([[[PlaySettings shared] oemID] UTF8String]) +// With a null terminator +#define DEVICE_MODEL [[[PlaySettings shared] deviceModel] cStringUsingEncoding:NSUTF8StringEncoding] +#define OEM_ID [[[PlaySettings shared] oemID] cStringUsingEncoding:NSUTF8StringEncoding] #define PLATFORM_IOS 2 // Define dyld_get_active_platform function for interpose @@ -60,22 +61,31 @@ static int pt_sysctl(int *name, u_int types, void *buf, size_t *size, void *arg0 static int pt_sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { if ((strcmp(name, "hw.machine") == 0) || (strcmp(name, "hw.product") == 0) || (strcmp(name, "hw.model") == 0)) { - if (oldp != NULL) { + if (oldp == NULL) { + int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); + *oldlenp = strlen(DEVICE_MODEL) + 1; + return ret; + } + else if (oldp != NULL) { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); const char *machine = DEVICE_MODEL; strncpy((char *)oldp, machine, strlen(machine)); - *oldlenp = strlen(machine); + *oldlenp = strlen(machine) + 1; return ret; } else { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); return ret; } } else if ((strcmp(name, "hw.target") == 0)) { - if (oldp != NULL) { + if (oldp == NULL) { + int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); + *oldlenp = strlen(OEM_ID) + 1; + return ret; + } else if (oldp != NULL) { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); const char *machine = OEM_ID; strncpy((char *)oldp, machine, strlen(machine)); - *oldlenp = strlen(machine); + *oldlenp = strlen(machine) + 1; return ret; } else { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); From 4860895a4994725b441d36dea84454155534c6ef Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Wed, 8 Mar 2023 11:12:44 +0700 Subject: [PATCH 16/54] feat: PlayShadow --- PlayTools.xcodeproj/project.pbxproj | 8 + .../Controls/PTFakeTouch/NSObject+Swizzle.m | 1 - PlayTools/MysticRunes/PlayShadow.h | 18 ++ PlayTools/MysticRunes/PlayShadow.m | 217 ++++++++++++++++++ 4 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 PlayTools/MysticRunes/PlayShadow.h create mode 100644 PlayTools/MysticRunes/PlayShadow.m diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index 8c455222..01f751fe 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -45,6 +45,8 @@ AA719870287A81A000623C15 /* UIEvent+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719842287A81A000623C15 /* UIEvent+Private.h */; }; AA719872287A81A000623C15 /* UITouch+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719844287A81A000623C15 /* UITouch+Private.h */; }; AA818CB9287ABFB1000BEE9D /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CB8287ABFB1000BEE9D /* IOKit.framework */; }; + AB7DA47529B85BFB0034ACB2 /* PlayShadow.m in Sources */ = {isa = PBXBuildFile; fileRef = AB7DA47429B85BFB0034ACB2 /* PlayShadow.m */; }; + AB7DA47729B8A78B0034ACB2 /* PlayShadow.h in Headers */ = {isa = PBXBuildFile; fileRef = AB7DA47629B8A78B0034ACB2 /* PlayShadow.h */; }; ABCECEE629750BA600746595 /* PlayedApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCECEE529750BA600746595 /* PlayedApple.swift */; }; B127172228817AB90025112B /* SwordRPC in Frameworks */ = {isa = PBXBuildFile; productRef = B127172128817AB90025112B /* SwordRPC */; }; B127172528817C040025112B /* DiscordIPC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B127172428817C040025112B /* DiscordIPC.swift */; }; @@ -108,6 +110,8 @@ AA719844287A81A000623C15 /* UITouch+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITouch+Private.h"; sourceTree = ""; }; AA818CB8287ABFB1000BEE9D /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/Library/Frameworks/IOKit.framework; sourceTree = DEVELOPER_DIR; }; AA818CBA287ABFD5000BEE9D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/iOSSupport/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + AB7DA47429B85BFB0034ACB2 /* PlayShadow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlayShadow.m; sourceTree = ""; }; + AB7DA47629B8A78B0034ACB2 /* PlayShadow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlayShadow.h; sourceTree = ""; }; ABCECEE529750BA600746595 /* PlayedApple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayedApple.swift; sourceTree = ""; }; B127172428817C040025112B /* DiscordIPC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscordIPC.swift; sourceTree = ""; }; B1271728288284BE0025112B /* DiscordActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscordActivity.swift; sourceTree = ""; }; @@ -269,6 +273,8 @@ isa = PBXGroup; children = ( ABCECEE529750BA600746595 /* PlayedApple.swift */, + AB7DA47429B85BFB0034ACB2 /* PlayShadow.m */, + AB7DA47629B8A78B0034ACB2 /* PlayShadow.h */, ); path = MysticRunes; sourceTree = ""; @@ -290,6 +296,7 @@ buildActionMask = 2147483647; files = ( AA71986E287A81A000623C15 /* NSObject+Swizzle.h in Headers */, + AB7DA47729B8A78B0034ACB2 /* PlayShadow.h in Headers */, AA719846287A81A000623C15 /* IOHIDEvent+KIF.h in Headers */, AA719862287A81A000623C15 /* UITouch-KIFAdditions.h in Headers */, AA71986B287A81A000623C15 /* UIApplication+Private.h in Headers */, @@ -465,6 +472,7 @@ AA719850287A81A000623C15 /* UITouch-KIFAdditions.m in Sources */, AA71985F287A81A000623C15 /* IOHIDEvent+KIF.m in Sources */, B1E8CF8A28BBE2AB004340D3 /* Keymapping.swift in Sources */, + AB7DA47529B85BFB0034ACB2 /* PlayShadow.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m index 10dd4c6d..9733eba3 100644 --- a/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m +++ b/PlayTools/Controls/PTFakeTouch/NSObject+Swizzle.m @@ -112,7 +112,6 @@ - (double) get_default_width { } - bool menuWasCreated = false; - (id) initWithRootMenuHook:(id)rootMenu { self = [self initWithRootMenuHook:rootMenu]; diff --git a/PlayTools/MysticRunes/PlayShadow.h b/PlayTools/MysticRunes/PlayShadow.h new file mode 100644 index 00000000..53965479 --- /dev/null +++ b/PlayTools/MysticRunes/PlayShadow.h @@ -0,0 +1,18 @@ +// +// PlayMask.h +// PlayTools +// +// Created by Venti on 08/03/2023. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSObject (Swizzle) + +- (void)swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector; + +@end + +NS_ASSUME_NONNULL_END diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m new file mode 100644 index 00000000..11d838b8 --- /dev/null +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -0,0 +1,217 @@ +// +// PlayMask.m +// PlayTools +// +// Created by Venti on 08/03/2023. +// + +#import +#import "NSObject+Swizzle.h" +#import + +__attribute__((visibility("hidden"))) +@interface PlayShadowLoader : NSObject +@end + +@implementation NSObject (Swizzle) + +- (void) swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector +{ + Class cls = [self class]; + // If current class doesn't exist selector, then get super + Method originalMethod = class_getInstanceMethod(cls, origSelector); + Method swizzledMethod = class_getInstanceMethod(cls, newSelector); + + // Add selector if it doesn't exist, implement append with method + if (class_addMethod(cls, + origSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)) ) { + // Replace class instance method, added if selector not exist + // For class cluster, it always adds new selector here + class_replaceMethod(cls, + newSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + + } else { + // SwizzleMethod maybe belongs to super + class_replaceMethod(cls, + newSelector, + class_replaceMethod(cls, + origSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)), + method_getTypeEncoding(originalMethod)); + } +} + +- (NSInteger) hook_deviceType { + return 1; +} + +- (BOOL) pm_return_false { + NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return false; +} + +- (BOOL) pm_return_true { + NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return true; +} + +- (BOOL) pm_return_yes { + NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return YES; +} + +- (BOOL) pm_return_no { + NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return NO; +} + +- (int) pm_return_0 { + NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return 0; +} + +- (int) pm_return_1 { + NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return 1; +} +@end + +@implementation PlayShadowLoader + ++ (void) load { + // Swizzle NSProcessInfo to troll every app that tries to detect macCatalyst + // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isMacCatalystApp) withMethod:@selector(pm_return_false)]; + [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isiOSAppOnMac) withMethod:@selector(pm_return_true)]; + + [objc_getClass("RNDeviceInfo") swizzleInstanceMethod:@selector(getDeviceType) withMethod:@selector(hook_deviceType)]; + + // Class: UIDevice + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_no)]; + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; + + // Class: JailbreakDetectionVC + [objc_getClass("JailbreakDetectionVC") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + + // Class: DTTJailbreakDetection + [objc_getClass("DTTJailbreakDetection") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + + // Class: ANSMetadata + [objc_getClass("ANSMetadata") swizzleInstanceMethod:@selector(computeIsJailbroken) withMethod:@selector(pm_return_no)]; + [objc_getClass("ANSMetadata") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + + // Class: AppsFlyerUtils + [objc_getClass("AppsFlyerUtils") swizzleInstanceMethod:@selector(isJailBreakon) withMethod:@selector(pm_return_no)]; + [objc_getClass("AppsFlyerUtils") swizzleInstanceMethod:@selector(a) withMethod:@selector(pm_return_false)]; + + // Class: jailBreak + [objc_getClass("jailBreak") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_false)]; + + // Class: GBDeviceInfo + [objc_getClass("GBDeviceInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + + // Class: CMARAppRestrictionsDelegate + [objc_getClass("CMARAppRestrictionsDelegate") swizzleInstanceMethod:@selector(isDeviceNonCompliant) withMethod:@selector(pm_return_false)]; + + // Class: ADYSecurityChecks + [objc_getClass("ADYSecurityChecks") swizzleInstanceMethod:@selector(isDeviceJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: UBReportMetadataDevice + [objc_getClass("UBReportMetadataDevice") swizzleInstanceMethod:@selector(is_rooted) withMethod:@selector(pm_return_null)]; + + // Class: UtilitySystem + [objc_getClass("UtilitySystem") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + + // Class: GemaltoConfiguration + [objc_getClass("GemaltoConfiguration") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + + // Class: CPWRDeviceInfo + [objc_getClass("CPWRDeviceInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: CPWRSessionInfo + [objc_getClass("CPWRSessionInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: KSSystemInfo + [objc_getClass("KSSystemInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: EMDSKPPConfiguration + [objc_getClass("EMDSKPPConfiguration") swizzleInstanceMethod:@selector(jailBroken) withMethod:@selector(pm_return_false)]; + + // Class: EnrollParameters + [objc_getClass("EnrollParameters") swizzleInstanceMethod:@selector(jailbroken) withMethod:@selector(pm_return_null)]; + + // Class: EMDskppConfigurationBuilder + [objc_getClass("EMDskppConfigurationBuilder") swizzleInstanceMethod:@selector(jailbreakStatus) withMethod:@selector(pm_return_false)]; + + // Class: FCRSystemMetadata + [objc_getClass("FCRSystemMetadata") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: v_VDMap + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(v_checkAModified) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_return_false)]; + + // Class: SDMUtils + [objc_getClass("SDMUtils") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; + + // Class: OneSignalJailbreakDetection + [objc_getClass("OneSignalJailbreakDetection") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + + // Class: DigiPassHandler + [objc_getClass("DigiPassHandler") swizzleInstanceMethod:@selector(rootedDeviceTestResult) withMethod:@selector(pm_return_no)]; + + // Class: AWMyDeviceGeneralInfo + [objc_getClass("AWMyDeviceGeneralInfo") swizzleInstanceMethod:@selector(isCompliant) withMethod:@selector(pm_return_true)]; + + // Class: DTXSessionInfo + [objc_getClass("DTXSessionInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: DTXDeviceInfo + [objc_getClass("DTXDeviceInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: JailbreakDetection + [objc_getClass("JailbreakDetection") swizzleInstanceMethod:@selector(jailbroken) withMethod:@selector(pm_return_false)]; + + // Class: jailBrokenJudge + [objc_getClass("jailBrokenJudge") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("jailBrokenJudge") swizzleInstanceMethod:@selector(isCydiaJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("jailBrokenJudge") swizzleInstanceMethod:@selector(isApplicationsJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("jailBrokenJudge") swizzleInstanceMethod:@selector(ischeckCydiaJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("jailBrokenJudge") swizzleInstanceMethod:@selector(isPathJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("jailBrokenJudge") swizzleInstanceMethod:@selector(boolIsjailbreak) withMethod:@selector(pm_return_false)]; + + // Class: FBAdBotDetector + [objc_getClass("FBAdBotDetector") swizzleInstanceMethod:@selector(isJailBrokenDevice) withMethod:@selector(pm_return_false)]; + + // Class: TNGDeviceTool + [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_file) withMethod:@selector(pm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_cydia) withMethod:@selector(pm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_appList) withMethod:@selector(pm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_env) withMethod:@selector(pm_return_false)]; + + // Class: DTDeviceInfo + [objc_getClass("DTDeviceInfo") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + + // Class: SecVIDeviceUtil + [objc_getClass("SecVIDeviceUtil") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + + // Class: RVPBridgeExtension4Jailbroken + [objc_getClass("RVPBridgeExtension4Jailbroken") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + + // Class: ZDetection + [objc_getClass("ZDetection") swizzleInstanceMethod:@selector(isRootedOrJailbroken) withMethod:@selector(pm_return_false)]; +} + +@end + From fa0662be43fd3b10d27977f5a3b8aa3242359bdb Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Fri, 10 Mar 2023 00:29:01 +0700 Subject: [PATCH 17/54] fix: make PlayShadow less noisy --- PlayTools/MysticRunes/PlayShadow.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 11d838b8..60b28ac4 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -51,32 +51,32 @@ - (NSInteger) hook_deviceType { } - (BOOL) pm_return_false { - NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return false; } - (BOOL) pm_return_true { - NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return true; } - (BOOL) pm_return_yes { - NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return YES; } - (BOOL) pm_return_no { - NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return NO; } - (int) pm_return_0 { - NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return 0; } - (int) pm_return_1 { - NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return 1; } @end @@ -86,7 +86,7 @@ @implementation PlayShadowLoader + (void) load { // Swizzle NSProcessInfo to troll every app that tries to detect macCatalyst // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isMacCatalystApp) withMethod:@selector(pm_return_false)]; - [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isiOSAppOnMac) withMethod:@selector(pm_return_true)]; + // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isiOSAppOnMac) withMethod:@selector(pm_return_true)]; [objc_getClass("RNDeviceInfo") swizzleInstanceMethod:@selector(getDeviceType) withMethod:@selector(hook_deviceType)]; From 85748b0db275e95d17e924748c531c2e7038279e Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sat, 11 Mar 2023 01:00:58 +0700 Subject: [PATCH 18/54] feat: swizzle some device info classes --- PlayTools/MysticRunes/PlayShadow.m | 6 ++++++ PlayTools/MysticRunes/PlayedApple.swift | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 60b28ac4..267c1b9e 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -79,6 +79,12 @@ - (int) pm_return_1 { // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return 1; } + +- (NSString *) pm_return_empty { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return @""; +} + @end @implementation PlayShadowLoader diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index f7c13379..7eae64d3 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -164,4 +164,4 @@ public class PlayKeychain: NSObject { return errSecItemNotFound } -} +} \ No newline at end of file From d41a1a48c02e2f5f1b2cf12dbaa54c94d7421336 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sat, 11 Mar 2023 09:15:56 +0700 Subject: [PATCH 19/54] feat: Togglable PlayShadow --- PlayTools/MysticRunes/PlayShadow.m | 9 +++++++++ PlayTools/PlaySettings.swift | 2 ++ 2 files changed, 11 insertions(+) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 267c1b9e..041e4de9 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -8,6 +8,7 @@ #import #import "NSObject+Swizzle.h" #import +#import __attribute__((visibility("hidden"))) @interface PlayShadowLoader : NSObject @@ -90,10 +91,18 @@ - (NSString *) pm_return_empty { @implementation PlayShadowLoader + (void) load { + // NSLog(@"PC-DEBUG: [PlayMask] Loaded"); + if ([[PlaySettings shared] bypass]) [self loadJailbreakBypass]; +} + ++ (void) loadJailbreakBypass { // Swizzle NSProcessInfo to troll every app that tries to detect macCatalyst // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isMacCatalystApp) withMethod:@selector(pm_return_false)]; // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isiOSAppOnMac) withMethod:@selector(pm_return_true)]; + // Some device info class + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(platform) withMethod:@selector(pm_return_empty)]; + [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(hwModel) withMethod:@selector(pm_return_empty)]; [objc_getClass("RNDeviceInfo") swizzleInstanceMethod:@selector(getDeviceType) withMethod:@selector(hook_deviceType)]; // Class: UIDevice diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index 78d19bac..cdbb79ff 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -33,6 +33,8 @@ let settings = PlaySettings.shared lazy var sensitivity = settingsData.sensitivity / 100 + @objc lazy var bypass = settingsData.bypass + @objc lazy var windowSizeHeight = CGFloat(settingsData.windowHeight) @objc lazy var windowSizeWidth = CGFloat(settingsData.windowWidth) From f5ff6aceaaeff044f984f6fc56b82f6440242ed6 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 09:54:30 +0700 Subject: [PATCH 20/54] fix: correcting types --- PlayTools/MysticRunes/PlayShadow.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 041e4de9..fdd2c6dc 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -51,12 +51,12 @@ - (NSInteger) hook_deviceType { return 1; } -- (BOOL) pm_return_false { +- (bool) pm_return_false { // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return false; } -- (BOOL) pm_return_true { +- (bool) pm_return_true { // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); return true; } From bbd350ee8f349d5cb4cce4132099052ff53e45fb Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 10:29:46 +0700 Subject: [PATCH 21/54] feat: PlayShadow debug logging --- PlayTools/MysticRunes/PlayShadow.m | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index fdd2c6dc..8ce3b5eb 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -86,16 +86,23 @@ - (NSString *) pm_return_empty { return @""; } +- (NSDictionary *) pm_return_empty_dictionary { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return @{}; +} + @end @implementation PlayShadowLoader + (void) load { - // NSLog(@"PC-DEBUG: [PlayMask] Loaded"); + [self debugLogger:@"PlayShadow is now loading"]; if ([[PlaySettings shared] bypass]) [self loadJailbreakBypass]; + // if ([[PlaySettings shared] bypass]) [self loadEnvironmentBypass]; # disabled as it might be too powerful } + (void) loadJailbreakBypass { + [self debugLogger:@"Jailbreak bypass loading"]; // Swizzle NSProcessInfo to troll every app that tries to detect macCatalyst // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isMacCatalystApp) withMethod:@selector(pm_return_false)]; // [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(isiOSAppOnMac) withMethod:@selector(pm_return_true)]; @@ -228,5 +235,15 @@ + (void) loadJailbreakBypass { [objc_getClass("ZDetection") swizzleInstanceMethod:@selector(isRootedOrJailbroken) withMethod:@selector(pm_return_false)]; } ++ (void) loadEnvironmentBypass { + [self debugLogger:@"Environment bypass loading"]; + // Completely nuke everything in the environment variables + [objc_getClass("NSProcessInfo") swizzleInstanceMethod:@selector(environment) withMethod:@selector(pm_return_empty_dictionary)]; +} + ++ (void) debugLogger: (NSString *) message { + NSLog(@"PC-DEBUG: %@", message); +} + @end From 9cd6b589f3832aae30a1bea969027c870c5ad95d Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 14:32:14 +0700 Subject: [PATCH 22/54] feat: App Tracking Transparently Denied (totally not cyberbullying) --- PlayTools/MysticRunes/PlayShadow.h | 4 +-- PlayTools/MysticRunes/PlayShadow.m | 42 +++++++++++++++++++++++++++--- PlayTools/PlayLoader.m | 2 -- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.h b/PlayTools/MysticRunes/PlayShadow.h index 53965479..b39a3be6 100644 --- a/PlayTools/MysticRunes/PlayShadow.h +++ b/PlayTools/MysticRunes/PlayShadow.h @@ -9,10 +9,10 @@ NS_ASSUME_NONNULL_BEGIN -@interface NSObject (Swizzle) +@interface NSObject (ShadowSwizzle) - (void)swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector; ++ (void)swizzleClassMethod: (SEL)origSelector withMethod: (SEL)newSelector @end - NS_ASSUME_NONNULL_END diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 8ce3b5eb..025e9f71 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -6,7 +6,6 @@ // #import -#import "NSObject+Swizzle.h" #import #import @@ -14,7 +13,7 @@ @interface PlayShadowLoader : NSObject @end -@implementation NSObject (Swizzle) +@implementation NSObject (ShadowSwizzle) - (void) swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector { @@ -47,6 +46,30 @@ - (void) swizzleInstanceMethod:(SEL)origSelector withMethod:(SEL)newSelector } } ++ (void) swizzleClassMethod:(SEL)origSelector withMethod:(SEL)newSelector { + Class cls = object_getClass((id)self); + Method originalMethod = class_getClassMethod(cls, origSelector); + Method swizzledMethod = class_getClassMethod(cls, newSelector); + + if (class_addMethod(cls, + origSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)) ) { + class_replaceMethod(cls, + newSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + } else { + class_replaceMethod(cls, + newSelector, + class_replaceMethod(cls, + origSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)), + method_getTypeEncoding(originalMethod)); + } +} + - (NSInteger) hook_deviceType { return 1; } @@ -91,6 +114,16 @@ - (NSDictionary *) pm_return_empty_dictionary { return @{}; } ++ (void) pm_return_2_with_completion_handler:(void (^)(NSInteger))completionHandler { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + completionHandler(2); +} + ++ (NSInteger) pm_return_2 { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return 2; +} + @end @implementation PlayShadowLoader @@ -99,6 +132,10 @@ + (void) load { [self debugLogger:@"PlayShadow is now loading"]; if ([[PlaySettings shared] bypass]) [self loadJailbreakBypass]; // if ([[PlaySettings shared] bypass]) [self loadEnvironmentBypass]; # disabled as it might be too powerful + + // Swizzle ATTrackingManager + [objc_getClass("ATTrackingManager") swizzleClassMethod:@selector(requestTrackingAuthorizationWithCompletionHandler:) withMethod:@selector(pm_return_2_with_completion_handler:)]; + [objc_getClass("ATTrackingManager") swizzleClassMethod:@selector(trackingAuthorizationStatus) withMethod:@selector(pm_return_2)]; } + (void) loadJailbreakBypass { @@ -246,4 +283,3 @@ + (void) debugLogger: (NSString *) message { } @end - diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index db7eec18..742dfed5 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -173,8 +173,6 @@ static OSStatus pt_SecItemDelete(CFDictionaryRef query) { DYLD_INTERPOSE(pt_SecItemUpdate, SecItemUpdate) DYLD_INTERPOSE(pt_SecItemDelete, SecItemDelete) - - @implementation PlayLoader static void __attribute__((constructor)) initialize(void) { From 92e0f3264669974fef71f38b09c4278acf32f5f0 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:21:03 +0700 Subject: [PATCH 23/54] b haxx --- PlayTools/MysticRunes/PlayShadow.m | 40 +++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 025e9f71..dc2c470a 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -70,7 +70,9 @@ + (void) swizzleClassMethod:(SEL)origSelector withMethod:(SEL)newSelector { } } -- (NSInteger) hook_deviceType { + + +- (NSInteger) pm_hook_deviceType { return 1; } @@ -124,6 +126,26 @@ + (NSInteger) pm_return_2 { return 2; } ++ (bool) pm_clsm_return_false { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return false; +} + ++ (bool) pm_clsm_return_true { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return true; +} + ++ (BOOL) pm_clsm_return_yes { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return YES; +} + ++ (BOOL) pm_clsm_return_no { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return NO; +} + @end @implementation PlayShadowLoader @@ -211,14 +233,14 @@ + (void) loadJailbreakBypass { [objc_getClass("FCRSystemMetadata") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; // Class: v_VDMap - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(v_checkAModified) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(v_checkAModified) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_return_false)]; // Class: SDMUtils [objc_getClass("SDMUtils") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; From 5958caca05771a14b50a8b7f919649c5f265bc14 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:31:12 +0700 Subject: [PATCH 24/54] b haxx --- PlayTools/MysticRunes/PlayShadow.m | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index dc2c470a..a885579d 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -233,14 +233,14 @@ + (void) loadJailbreakBypass { [objc_getClass("FCRSystemMetadata") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; // Class: v_VDMap - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(v_checkAModified) withMethod:@selector(pm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(v_checkAModified) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_clsm_return_false)]; // Class: SDMUtils [objc_getClass("SDMUtils") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; From b80812b83c5c12b0e0269ccd3dc7e9d38044e877 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:33:20 +0700 Subject: [PATCH 25/54] b haxx --- PlayTools/MysticRunes/PlayShadow.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index a885579d..872f495b 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -146,6 +146,11 @@ + (BOOL) pm_clsm_return_no { return NO; } ++ (int) pm_clsm_do_nothing_with_callback:(void (^)(int))callback { + // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); + return 0; +} + @end @implementation PlayShadowLoader @@ -242,6 +247,9 @@ + (void) loadJailbreakBypass { [objc_getClass("v_VDMap") swizzleClassMethod:@selector(v_checkAModified) withMethod:@selector(pm_clsm_return_false)]; [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_clsm_return_false)]; + // v_VPrivateUtility terminateApplicationAfter: + [objc_getClass("v_VPrivateUtility") swizzleClassMethod:@selector(terminateApplicationAfter:) withMethod:@selector(pm_clsm_do_nothing_with_callback:)]; + // Class: SDMUtils [objc_getClass("SDMUtils") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; From 8974c1e564cadbf56d526e540c2c728f06013d25 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:52:25 +0700 Subject: [PATCH 26/54] b haxx --- PlayTools/MysticRunes/PlayShadow.m | 58 +++++++++++++++--------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 872f495b..18dc1727 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -175,9 +175,9 @@ + (void) loadJailbreakBypass { [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(platform) withMethod:@selector(pm_return_empty)]; [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(hwModel) withMethod:@selector(pm_return_empty)]; [objc_getClass("RNDeviceInfo") swizzleInstanceMethod:@selector(getDeviceType) withMethod:@selector(hook_deviceType)]; - + // Class: UIDevice - [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + [objc_getClass("UIDevice") swizzleClassMethod:@selector(isJailbroken) withMethod:@selector(pm_clsm_return_no)]; [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_no)]; [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; @@ -185,18 +185,18 @@ + (void) loadJailbreakBypass { [objc_getClass("JailbreakDetectionVC") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; // Class: DTTJailbreakDetection - [objc_getClass("DTTJailbreakDetection") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + [objc_getClass("DTTJailbreakDetection") swizzleClassMethod:@selector(isJailbroken) withMethod:@selector(pm_clsm_return_no)]; // Class: ANSMetadata [objc_getClass("ANSMetadata") swizzleInstanceMethod:@selector(computeIsJailbroken) withMethod:@selector(pm_return_no)]; [objc_getClass("ANSMetadata") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; // Class: AppsFlyerUtils - [objc_getClass("AppsFlyerUtils") swizzleInstanceMethod:@selector(isJailBreakon) withMethod:@selector(pm_return_no)]; - [objc_getClass("AppsFlyerUtils") swizzleInstanceMethod:@selector(a) withMethod:@selector(pm_return_false)]; + [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(isJailBreakon) withMethod:@selector(pm_clsm_return_no)]; + [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(a) withMethod:@selector(pm_clsm_return_false)]; // Class: jailBreak - [objc_getClass("jailBreak") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("jailBreak") swizzleClassMethod:@selector(isJailBreak) withMethod:@selector(pm_clsm_return_false)]; // Class: GBDeviceInfo [objc_getClass("GBDeviceInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; @@ -205,16 +205,16 @@ + (void) loadJailbreakBypass { [objc_getClass("CMARAppRestrictionsDelegate") swizzleInstanceMethod:@selector(isDeviceNonCompliant) withMethod:@selector(pm_return_false)]; // Class: ADYSecurityChecks - [objc_getClass("ADYSecurityChecks") swizzleInstanceMethod:@selector(isDeviceJailbroken) withMethod:@selector(pm_return_false)]; + [objc_getClass("ADYSecurityChecks") swizzleClassMethod:@selector(isDeviceJailbroken) withMethod:@selector(pm_clsm_return_false)]; // Class: UBReportMetadataDevice [objc_getClass("UBReportMetadataDevice") swizzleInstanceMethod:@selector(is_rooted) withMethod:@selector(pm_return_null)]; // Class: UtilitySystem - [objc_getClass("UtilitySystem") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("UtilitySystem") swizzleClassMethod:@selector(isJailbreak) withMethod:@selector(pm_clsm_return_false)]; // Class: GemaltoConfiguration - [objc_getClass("GemaltoConfiguration") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("GemaltoConfiguration") swizzleClassMethod:@selector(isJailbreak) withMethod:@selector(pm_clsm_return_false)]; // Class: CPWRDeviceInfo [objc_getClass("CPWRDeviceInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; @@ -223,7 +223,7 @@ + (void) loadJailbreakBypass { [objc_getClass("CPWRSessionInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; // Class: KSSystemInfo - [objc_getClass("KSSystemInfo") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; + [objc_getClass("KSSystemInfo") swizzleClassMethod:@selector(isJailbroken) withMethod:@selector(pm_clsm_return_false)]; // Class: EMDSKPPConfiguration [objc_getClass("EMDSKPPConfiguration") swizzleInstanceMethod:@selector(jailBroken) withMethod:@selector(pm_return_false)]; @@ -238,23 +238,20 @@ + (void) loadJailbreakBypass { [objc_getClass("FCRSystemMetadata") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; // Class: v_VDMap - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(v_checkAModified) withMethod:@selector(pm_clsm_return_false)]; - [objc_getClass("v_VDMap") swizzleClassMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_clsm_return_false)]; - - // v_VPrivateUtility terminateApplicationAfter: - [objc_getClass("v_VPrivateUtility") swizzleClassMethod:@selector(terminateApplicationAfter:) withMethod:@selector(pm_clsm_do_nothing_with_callback:)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isJailbrokenDetected) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isJailBrokenDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isDFPHookedDetecedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isCodeInjectionDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isDebuggerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isAppSignerCheckDetectedByVOS) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(v_checkAModified) withMethod:@selector(pm_return_false)]; + [objc_getClass("v_VDMap") swizzleInstanceMethod:@selector(isRuntimeTamperingDetected) withMethod:@selector(pm_return_false)]; // Class: SDMUtils [objc_getClass("SDMUtils") swizzleInstanceMethod:@selector(isJailBroken) withMethod:@selector(pm_return_no)]; // Class: OneSignalJailbreakDetection - [objc_getClass("OneSignalJailbreakDetection") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_no)]; + [objc_getClass("OneSignalJailbreakDetection") swizzleClassMethod:@selector(isJailbroken) withMethod:@selector(pm_clsm_return_no)]; // Class: DigiPassHandler [objc_getClass("DigiPassHandler") swizzleInstanceMethod:@selector(rootedDeviceTestResult) withMethod:@selector(pm_return_no)]; @@ -283,23 +280,24 @@ + (void) loadJailbreakBypass { [objc_getClass("FBAdBotDetector") swizzleInstanceMethod:@selector(isJailBrokenDevice) withMethod:@selector(pm_return_false)]; // Class: TNGDeviceTool - [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak) withMethod:@selector(pm_return_false)]; - [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_file) withMethod:@selector(pm_return_false)]; - [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_cydia) withMethod:@selector(pm_return_false)]; - [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_appList) withMethod:@selector(pm_return_false)]; - [objc_getClass("TNGDeviceTool") swizzleInstanceMethod:@selector(isJailBreak_env) withMethod:@selector(pm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleClassMethod:@selector(isJailBreak) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleClassMethod:@selector(isJailBreak_file) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleClassMethod:@selector(isJailBreak_cydia) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleClassMethod:@selector(isJailBreak_appList) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("TNGDeviceTool") swizzleClassMethod:@selector(isJailBreak_env) withMethod:@selector(pm_clsm_return_false)]; // Class: DTDeviceInfo - [objc_getClass("DTDeviceInfo") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("DTDeviceInfo") swizzleClassMethod:@selector(isJailbreak) withMethod:@selector(pm_clsm_return_false)]; // Class: SecVIDeviceUtil - [objc_getClass("SecVIDeviceUtil") swizzleInstanceMethod:@selector(isJailbreak) withMethod:@selector(pm_return_false)]; + [objc_getClass("SecVIDeviceUtil") swizzleClassMethod:@selector(isJailbreak) withMethod:@selector(pm_clsm_return_false)]; // Class: RVPBridgeExtension4Jailbroken [objc_getClass("RVPBridgeExtension4Jailbroken") swizzleInstanceMethod:@selector(isJailbroken) withMethod:@selector(pm_return_false)]; // Class: ZDetection - [objc_getClass("ZDetection") swizzleInstanceMethod:@selector(isRootedOrJailbroken) withMethod:@selector(pm_return_false)]; + [objc_getClass("ZDetection") swizzleClassMethod:@selector(isRootedOrJailbroken) withMethod:@selector(pm_clsm_return_false)]; + } + (void) loadEnvironmentBypass { From 170052b42de7f439e144797fb60818349e5390f4 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 23:00:22 +0700 Subject: [PATCH 27/54] fix: correctly swizzle class instances --- PlayTools/MysticRunes/PlayShadow.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 18dc1727..977daf06 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -174,7 +174,7 @@ + (void) loadJailbreakBypass { // Some device info class [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(platform) withMethod:@selector(pm_return_empty)]; [objc_getClass("UIDevice") swizzleInstanceMethod:@selector(hwModel) withMethod:@selector(pm_return_empty)]; - [objc_getClass("RNDeviceInfo") swizzleInstanceMethod:@selector(getDeviceType) withMethod:@selector(hook_deviceType)]; + [objc_getClass("RNDeviceInfo") swizzleInstanceMethod:@selector(getDeviceType) withMethod:@selector(pm_hook_deviceType)]; // Class: UIDevice [objc_getClass("UIDevice") swizzleClassMethod:@selector(isJailbroken) withMethod:@selector(pm_clsm_return_no)]; @@ -193,7 +193,7 @@ + (void) loadJailbreakBypass { // Class: AppsFlyerUtils [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(isJailBreakon) withMethod:@selector(pm_clsm_return_no)]; - [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(a) withMethod:@selector(pm_clsm_return_false)]; + [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(isJailbrokenWithSkipAdvancedJailbreakValidation:) withMethod:@selector(pm_clsm_return_false)]; // Class: jailBreak [objc_getClass("jailBreak") swizzleClassMethod:@selector(isJailBreak) withMethod:@selector(pm_clsm_return_false)]; From 96964d26465c16eb74f8e687d3ee57ba988cab7f Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 23:07:00 +0700 Subject: [PATCH 28/54] chore: proper comments --- PlayTools/MysticRunes/PlayShadow.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index 977daf06..d41edb6e 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -70,7 +70,7 @@ + (void) swizzleClassMethod:(SEL)origSelector withMethod:(SEL)newSelector { } } - +// Instance methods - (NSInteger) pm_hook_deviceType { return 1; @@ -116,6 +116,8 @@ - (NSDictionary *) pm_return_empty_dictionary { return @{}; } +// Class methods + + (void) pm_return_2_with_completion_handler:(void (^)(NSInteger))completionHandler { // NSLog(@"PC-DEBUG: [PlayMask] Jailbreak Detection Attempted"); completionHandler(2); From 888749bca0b131365e7a17bc2e050d404d010af9 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 23:08:00 +0700 Subject: [PATCH 29/54] fix: some swizzle targets --- PlayTools/MysticRunes/PlayShadow.m | 1 + 1 file changed, 1 insertion(+) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index d41edb6e..b6ca3074 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -195,6 +195,7 @@ + (void) loadJailbreakBypass { // Class: AppsFlyerUtils [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(isJailBreakon) withMethod:@selector(pm_clsm_return_no)]; + [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(isJailbroken) withMethod:@selector(pm_clsm_return_no)]; [objc_getClass("AppsFlyerUtils") swizzleClassMethod:@selector(isJailbrokenWithSkipAdvancedJailbreakValidation:) withMethod:@selector(pm_clsm_return_false)]; // Class: jailBreak From a4c4ad2e19a147f35f37b60003db6d84bee6d818 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Mon, 3 Apr 2023 11:40:03 +0700 Subject: [PATCH 30/54] chore: match names --- PlayTools/MysticRunes/PlayShadow.h | 2 +- PlayTools/MysticRunes/PlayShadow.m | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.h b/PlayTools/MysticRunes/PlayShadow.h index b39a3be6..014602c9 100644 --- a/PlayTools/MysticRunes/PlayShadow.h +++ b/PlayTools/MysticRunes/PlayShadow.h @@ -1,5 +1,5 @@ // -// PlayMask.h +// PlayShadow.h // PlayTools // // Created by Venti on 08/03/2023. diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index b6ca3074..edf61ccb 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -1,5 +1,5 @@ // -// PlayMask.m +// PlayShadow.m // PlayTools // // Created by Venti on 08/03/2023. @@ -300,7 +300,6 @@ + (void) loadJailbreakBypass { // Class: ZDetection [objc_getClass("ZDetection") swizzleClassMethod:@selector(isRootedOrJailbroken) withMethod:@selector(pm_clsm_return_false)]; - } + (void) loadEnvironmentBypass { From 58f7e14d6d488406293a186cdd334a3624173c66 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sat, 11 Mar 2023 20:23:26 +0700 Subject: [PATCH 31/54] feat: attr for PlayChain --- PlayTools/MysticRunes/PlayedApple.swift | 95 +++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index 7eae64d3..c90b2a24 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -45,6 +45,87 @@ public class PlayKeychain: NSObject { .appendingPathComponent("\(serviceName)-\(accountName)-\(classType).plist") } + private static func createGenpAttr(_ query: NSDictionary, _ keychainPath: URL) -> NSDictionary? { + let currentTime = Date() + let keychainDict = NSDictionary(contentsOf: keychainPath) + + let attributes = NSMutableDictionary() + attributes[kSecAttrAccessControl as String] = + SecAccessControlCreateWithFlags(kCFAllocatorDefault, + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + .or, + nil) + attributes[kSecAttrAccount as String] = query["acct"] ?? keychainDict?["acct"] ?? "" + attributes[kSecAttrAccessGroup as String] = "*" + attributes[kSecAttrCreationDate as String] = String(describing: currentTime) + .replacingOccurrences(of: "UTC", with: "+0000") + attributes[kSecAttrModificationDate as String] = String(describing: currentTime) + .replacingOccurrences(of: "UTC", with: "+0000") + attributes["musr"] = Data() + attributes[kSecAttrPath as String] = keychainDict?["pdmn"] ?? "ak" + attributes["sha1"] = Data() + attributes[kSecAttrService as String] = query["svce"] ?? keychainDict?["svce"] ?? "" + attributes[kSecAttrSynchronizable as String] = query["sync"] ?? keychainDict?["sync"] ?? 0 + attributes["tomb"] = query["tomb"] ?? keychainDict?["tomb"] ?? 0 + return attributes + } + + private static func createKeysAttr(_ query: NSDictionary, _ keychainPath: URL) -> NSDictionary? { + let currentTime = Date() + let jan1st2001 = Date(timeIntervalSince1970: 978307200) + let keychainDict = NSDictionary(contentsOf: keychainPath) + + let attributes = NSMutableDictionary() + attributes["UUID"] = UUID().uuidString + attributes[kSecAttrAccessControl as String] = + SecAccessControlCreateWithFlags(kCFAllocatorDefault, + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + .or, + nil) + attributes[kSecAttrAccessGroup as String] = "*" + attributes["asen"] = query["asen"] ?? keychainDict?["asen"] ?? 0 + attributes[kSecAttrApplicationTag as String] = + query["atag"] as? Data? ?? keychainDict?["atag"] as? Data? ?? Data() + attributes[kSecAttrKeySizeInBits as String] = query["bsiz"] ?? keychainDict?["bsiz"] ?? 0 + attributes[kSecAttrCreationDate as String] = + String(describing: currentTime).replacingOccurrences(of: "UTC", with: "+0000") + attributes[kSecAttrCreator as String] = query["crtr"] ?? keychainDict?["crtr"] ?? 0 + attributes["decr"] = query["decr"] ?? keychainDict?["decr"] ?? 0 + attributes["drve"] = query["drve"] ?? keychainDict?["drve"] ?? 0 + attributes["edat"] = query["edat"] ?? keychainDict?["edat"] ?? + String(describing: jan1st2001).replacingOccurrences(of: "UTC", with: "+0000") + attributes["encr"] = query["encr"] ?? keychainDict?["encr"] ?? 0 + attributes["esiz"] = query["esiz"] ?? keychainDict?["esiz"] ?? 0 + attributes["extr"] = query["extr"] ?? keychainDict?["extr"] ?? 0 + attributes["kcls"] = query["kcls"] ?? keychainDict?["kcls"] ?? 0 + attributes[kSecAttrLabel as String] = query["labl"] ?? keychainDict?["labl"] ?? "" + attributes[kSecAttrModificationDate as String] = + String(describing: currentTime).replacingOccurrences(of: "UTC", with: "+0000") + attributes["modi"] = query["modi"] ?? keychainDict?["modi"] ?? 1 + attributes["musr"] = Data() + attributes["next"] = query["next"] ?? keychainDict?["next"] ?? 0 + attributes[kSecAttrPath as String] = keychainDict?["pdmn"] ?? "dku" + attributes["perm"] = query["perm"] ?? keychainDict?["perm"] ?? 1 + attributes["priv"] = query["priv"] ?? keychainDict?["priv"] ?? 1 + attributes["sdat"] = query["sdat"] ?? keychainDict?["sdat"] ?? String(describing: jan1st2001) + .replacingOccurrences(of: "UTC", with: "+0000") + attributes["sens"] = query["sens"] ?? keychainDict?["sens"] ?? 0 + attributes["sha1"] = query["sha1"] as? Data ?? keychainDict?["sha1"] as? Data? ?? Data() + return attributes + } + + static private func getAttributes(_ query: NSDictionary, _ keychainPath: URL) -> NSDictionary? { + // First, check if the kind of key is genp or keys + let classType = query[kSecClass as String] as? String ?? "" + if classType == "genp" { + return createGenpAttr(query, keychainPath) + } else if classType == "keys" { + return createKeysAttr(query, keychainPath) + } else { + return nil + } + } + @objc public static func debugLogger(_ logContent: String) { if PlaySettings.shared.settingsData.playChainDebugging { NSLog("PC-DEBUG: \(logContent)") @@ -69,7 +150,7 @@ public class PlayKeychain: NSObject { return errSecIO } // Place v_data in the result - if let v_data = attributes["v_data"] { + if let v_data = attributes["v_Data"] { result?.pointee = v_data as CFTypeRef } return errSecSuccess @@ -128,14 +209,20 @@ public class PlayKeychain: NSObject { -> OSStatus { // Get the path to the keychain file let keychainPath = keychainPath(query) - // Read the dictionary from the keychain file - let keychainDict = NSDictionary(contentsOf: keychainPath) // Check the `r_Attributes` key. If it is set to 1 in the query // DROP, NOT IMPLEMENTED let classType = query[kSecClass as String] as? String ?? "" if query["r_Attributes"] as? Int == 1 { - return errSecItemNotFound + let attributesDict = getAttributes(query, keychainPath) + if attributesDict == nil { + return errSecItemNotFound + } + // convert to CFDictonary + let attributes = attributesDict! as CFDictionary + result?.pointee = attributes as CFTypeRef + return errSecSuccess } + let keychainDict = NSDictionary(contentsOf: keychainPath) // If the keychain file doesn't exist, return errSecItemNotFound if keychainDict == nil { debugLogger("Keychain file not found at \(keychainPath)") From c1c7627408023cdea2f7650066a8781866f761fb Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Tue, 14 Mar 2023 10:06:55 +0700 Subject: [PATCH 32/54] fix: Log when attributes is accessed --- PlayTools/MysticRunes/PlayedApple.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index c90b2a24..710f69b3 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -213,6 +213,7 @@ public class PlayKeychain: NSObject { // DROP, NOT IMPLEMENTED let classType = query[kSecClass as String] as? String ?? "" if query["r_Attributes"] as? Int == 1 { + debugLogger("Attributes requested") let attributesDict = getAttributes(query, keychainPath) if attributesDict == nil { return errSecItemNotFound From 2423aaaf0211dea3b7549c9e03d3dfbc88bafcc0 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Thu, 9 Feb 2023 01:58:01 +0800 Subject: [PATCH 33/54] add new hint toast --- PlayTools/Controls/PlayInput.swift | 26 +++----------------------- PlayTools/Utils/Toast.swift | 15 ++++----------- 2 files changed, 7 insertions(+), 34 deletions(-) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index a91b61ea..6b031afd 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -164,38 +164,18 @@ class PlayInput { setupShortcuts() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { - self.parseKeymap() - if !settings.mouseMapping || !mode.visible || self.actions.count <= 0 { - return - } - let persistenceKeyname = "playtoolsKeymappingDisabledAt" - let lastUse = UserDefaults.standard.float(forKey: persistenceKeyname) - var thisUse = lastUse - if lastUse < 1 { - thisUse = 2 - } else { - thisUse = Float(Date.timeIntervalSinceReferenceDate) - } - var token2: NSObjectProtocol? - let center = NotificationCenter.default - token2 = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillDisable, - object: nil, queue: OperationQueue.main) { _ in - center.removeObserver(token2!) - UserDefaults.standard.set(thisUse, forKey: persistenceKeyname) - } - if lastUse > Float(Date.now.addingTimeInterval(-86400*14).timeIntervalSinceReferenceDate) { + if !settings.mouseMapping || !mode.visible { return } Toast.showHint(title: "Keymapping Disabled", text: ["Press ", "option ⌥", " to enable keymapping"], - timeout: 10, notification: NSNotification.Name.playtoolsKeymappingWillEnable) + let center = NotificationCenter.default var token: NSObjectProtocol? token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, object: nil, queue: OperationQueue.main) { _ in center.removeObserver(token!) Toast.showHint(title: "Keymapping Enabled", text: ["Press ", "option ⌥", " to disable keymapping"], - timeout: 10, - notification: NSNotification.Name.playtoolsKeymappingWillDisable) + notification: NSNotification.Name.playtoolsKeymappingWillDisable) } } diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index aafc0ad1..f6540a87 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -17,7 +17,7 @@ class Toast { private static let gap: CGFloat = 40 public static func hideHint(hint: UIView) { - guard let id = hintView.firstIndex(of: hint) else {return} + let id = hintView.firstIndex(of: hint)! for index in 0.. 4 { - hideHint(hint: hintView.first!) - } - var life = timeout if let note = notification { let center = NotificationCenter.default var token: NSObjectProtocol? @@ -97,11 +93,8 @@ class Toast { center.removeObserver(token!) hideHint(hint: messageLabel) } - } else if life < 0 { - life = 3 - } - if life >= 0 { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + life) { + } else { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + timeout) { hideHint(hint: messageLabel) } } From 3720ae72a58ef715a8a529acbdc58372cc43cb73 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 11 Feb 2023 17:29:03 +0800 Subject: [PATCH 34/54] limit toast number to four --- PlayTools/Utils/Toast.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index f6540a87..44fba46a 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -17,7 +17,7 @@ class Toast { private static let gap: CGFloat = 40 public static func hideHint(hint: UIView) { - let id = hintView.firstIndex(of: hint)! + guard let id = hintView.firstIndex(of: hint) else {return} for index in 0.. 4 { + hideHint(hint: hintView.first!) + } if let note = notification { let center = NotificationCenter.default var token: NSObjectProtocol? From 8c7decc799663f98f2d950f2d1f647b2851e586a Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Wed, 15 Feb 2023 22:04:54 +0800 Subject: [PATCH 35/54] Localization --- PlayTools.xcodeproj/project.pbxproj | 19 ++++++++ PlayTools/Controls/ControlMode.swift | 2 +- PlayTools/Controls/MenuController.swift | 22 +++++++--- PlayTools/Controls/PlayInput.swift | 50 +++++++++++++++------- PlayTools/Keymap/EditorController.swift | 12 ++++-- PlayTools/en.lproj/Playtools.strings | Bin 0 -> 3000 bytes PlayTools/zh-Hans.lproj/Playtools.strings | Bin 0 -> 2924 bytes 7 files changed, 78 insertions(+), 27 deletions(-) create mode 100644 PlayTools/en.lproj/Playtools.strings create mode 100644 PlayTools/zh-Hans.lproj/Playtools.strings diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index 01f751fe..e9c1825c 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 6E7663A528D0FEBE00DE4AF9 /* AKPluginLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7663A428D0FEBE00DE4AF9 /* AKPluginLoader.swift */; }; 6E84A14528D0F94E00BF7495 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CBA287ABFD5000BEE9D /* UIKit.framework */; }; 6E84A15028D0F97500BF7495 /* AKInterface.bundle in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 951D8275299D097C00D35B20 /* Playtools.strings in Resources */ = {isa = PBXBuildFile; fileRef = 951D8277299D097C00D35B20 /* Playtools.strings */; }; AA71970B287A44D200623C15 /* PlayLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = AA719702287A44D200623C15 /* PlayLoader.m */; }; AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719704287A44D200623C15 /* PlaySettings.swift */; }; AA71970E287A44D200623C15 /* PlayLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719705287A44D200623C15 /* PlayLoader.h */; }; @@ -75,6 +76,8 @@ 6E7663A028D0FB5300DE4AF9 /* AKPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AKPlugin.swift; sourceTree = ""; }; 6E7663A428D0FEBE00DE4AF9 /* AKPluginLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AKPluginLoader.swift; sourceTree = ""; }; 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AKInterface.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 951D8276299D097C00D35B20 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Playtools.strings; sourceTree = ""; }; + 951D8278299D098000D35B20 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Playtools.strings"; sourceTree = ""; }; AA7196D8287A447700623C15 /* PlayTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlayTools.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AA719702287A44D200623C15 /* PlayLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlayLoader.m; sourceTree = ""; }; AA719704287A44D200623C15 /* PlaySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaySettings.swift; sourceTree = ""; }; @@ -152,6 +155,7 @@ AA7196CE287A447700623C15 = { isa = PBXGroup; children = ( + 951D8277299D097C00D35B20 /* Playtools.strings */, AA7196DA287A447700623C15 /* PlayTools */, 6E76639928D0FA6F00DE4AF9 /* AKInterface */, AA7196D9287A447700623C15 /* Products */, @@ -376,6 +380,7 @@ knownRegions = ( en, Base, + "zh-Hans", ); mainGroup = AA7196CE287A447700623C15; packageReferences = ( @@ -403,6 +408,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 951D8275299D097C00D35B20 /* Playtools.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -478,6 +484,19 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXVariantGroup section */ + 951D8277299D097C00D35B20 /* Playtools.strings */ = { + isa = PBXVariantGroup; + children = ( + 951D8276299D097C00D35B20 /* en */, + 951D8278299D098000D35B20 /* zh-Hans */, + ); + name = Playtools.strings; + path = PlayTools; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ 6E84A14E28D0F96D00BF7495 /* Debug */ = { isa = XCBuildConfiguration; diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index df298807..deb68764 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -48,7 +48,7 @@ public class ControlMode { extension NSNotification.Name { public static let playtoolsKeymappingWillEnable: NSNotification.Name = NSNotification.Name("playtools.keymappingWillEnable") - + public static let playtoolsKeymappingWillDisable: NSNotification.Name = NSNotification.Name("playtools.keymappingWillDisable") } diff --git a/PlayTools/Controls/MenuController.swift b/PlayTools/Controls/MenuController.swift index 3ec96d13..a304b48c 100644 --- a/PlayTools/Controls/MenuController.swift +++ b/PlayTools/Controls/MenuController.swift @@ -61,12 +61,19 @@ extension UIViewController { struct CommandsList { static let KeymappingToolbox = "keymapping" } - -var keymapping = ["Open/Close Keymapping Editor", - "Delete selected element", - "Upsize selected element", - "Downsize selected element", - "Rotate display area"] +// have to use a customized name, in case it conflicts with the game's localization file +var keymapping = [ + NSLocalizedString("menu.keymapping.toggleEditor", tableName: "Playtools", + value: "Open/Close Keymapping Editor", comment: ""), + NSLocalizedString("menu.keymapping.deleteElement", tableName: "Playtools", + value: "Delete selected element", comment: ""), + NSLocalizedString("menu.keymapping.upsizeElement", tableName: "Playtools", + value: "Upsize selected element", comment: ""), + NSLocalizedString("menu.keymapping.downsizeElement", tableName: "Playtools", + value: "Downsize selected element", comment: ""), + NSLocalizedString("menu.keymapping.rotateDisplay", tableName: "Playtools", + value: "Rotate display area", comment: "") + ] var keymappingSelectors = [#selector(UIApplication.switchEditorMode(_:)), #selector(UIApplication.removeElement(_:)), #selector(UIApplication.upscaleElement(_:)), @@ -97,7 +104,8 @@ class MenuController { options: .displayInline, children: arrowKeyChildrenCommands) - return UIMenu(title: NSLocalizedString("Keymapping", comment: ""), + return UIMenu(title: NSLocalizedString("menu.keymapping", tableName: "Playtools", + value: "Keymapping", comment: ""), image: nil, identifier: .keymappingMenu, options: [], diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 6b031afd..b632667c 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -135,6 +135,40 @@ class PlayInput { } } + func initializeToasts() { + if !settings.mouseMapping || !mode.visible { + return + } + Toast.showHint(title: NSLocalizedString("hint.enableKeymapping.title", + tableName: "Playtools", + value: "Keymapping Disabled", comment: ""), + text: [NSLocalizedString("hint.enableKeymapping.content.before", + tableName: "Playtools", + value: "Press", comment: ""), + " option ⌥ ", + NSLocalizedString("hint.enableKeymapping.content.after", + tableName: "Playtools", + value: "to enable keymapping", comment: "")], + notification: NSNotification.Name.playtoolsKeymappingWillEnable) + let center = NotificationCenter.default + var token: NSObjectProtocol? + token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token!) + Toast.showHint(title: NSLocalizedString("hint.disableKeymapping.title", + tableName: "Playtools", + value: "Keymapping Enabled", comment: ""), + text: [NSLocalizedString("hint.disableKeymapping.content.before", + tableName: "Playtools", + value: "Press", comment: ""), + " option ⌥ ", + NSLocalizedString("hint.disableKeymapping.content.after", + tableName: "Playtools", + value: "to disable keymapping", comment: "")], + notification: NSNotification.Name.playtoolsKeymappingWillDisable) + } + } + func initialize() { if !PlaySettings.shared.keymapping { return @@ -163,21 +197,7 @@ class PlayInput { } setupShortcuts() - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { - if !settings.mouseMapping || !mode.visible { - return - } - Toast.showHint(title: "Keymapping Disabled", text: ["Press ", "option ⌥", " to enable keymapping"], - notification: NSNotification.Name.playtoolsKeymappingWillEnable) - let center = NotificationCenter.default - var token: NSObjectProtocol? - token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, - object: nil, queue: OperationQueue.main) { _ in - center.removeObserver(token!) - Toast.showHint(title: "Keymapping Enabled", text: ["Press ", "option ⌥", " to disable keymapping"], - notification: NSNotification.Name.playtoolsKeymappingWillDisable) - } - } + DispatchQueue.main.asyncAfter(deadline: .now() + 5, qos: .utility, execute: initializeToasts) // Fix beep sound AKInterface.shared! diff --git a/PlayTools/Keymap/EditorController.swift b/PlayTools/Keymap/EditorController.swift index c6f7075e..43af3eaf 100644 --- a/PlayTools/Keymap/EditorController.swift +++ b/PlayTools/Keymap/EditorController.swift @@ -60,16 +60,20 @@ class EditorController { previousWindow?.makeKeyAndVisible() PlayInput.shared.toggleEditor(show: false) focusedControl = nil - Toast.showHint(title: "Keymap Saved") + Toast.showHint(title: NSLocalizedString("hint.keymapSaved", + tableName: "Playtools", value: "Keymap Saved", comment: "")) } else { PlayInput.shared.toggleEditor(show: true) previousWindow = screen.keyWindow editorWindow = initWindow() editorWindow?.makeKeyAndVisible() showButtons() - Toast.showHint(title: "Keymapping Editor", - text: ["Click a button to edit its position or key bind\n" + - "Click an empty area to open input menu"], + Toast.showHint(title: NSLocalizedString("hint.keymappingEditor.title", + tableName: "Playtools", value: "Keymapping Editor", comment: ""), + text: [NSLocalizedString("hint.keymappingEditor.content", + tableName: "Playtools", + value: "Click a button to edit its position or key bind\n" + + "Click an empty area to open input menu", comment: "")], notification: NSNotification.Name.playtoolsKeymappingWillEnable) } // Toast.showOver(msg: "\(UIApplication.shared.windows.count)") diff --git a/PlayTools/en.lproj/Playtools.strings b/PlayTools/en.lproj/Playtools.strings new file mode 100644 index 0000000000000000000000000000000000000000..1e82629eb58fd99c06b0626d339ec84318dafe43 GIT binary patch literal 3000 zcmd6p&rZTX5XR@sQ*1bLfCn(1gp-LHjW;ia79?of&=!Laul{DsX17U|dVmtrW&h01 ze7pP2xBLAyk&zg=lZpZ0Xs_T4MTQdgijH)bSO)DYXQfF?}PM$Uuhj zYWqw2Vrqp%%z4A=mT!;m2(P)dSz)6QHC7#Q%;?g#ws zVpnI##p65Kod@9woL&P%f6!(k%T1&+n^g+FkC7^rcq9 zYu)EsVRLpmLPh-9JO(!IMdhr#)|@IA|7QFcJDyllDbUVjgPyG{Btl<=o|)`Way&tAidqaC4}Bx? zWgtU&)BOedBGhsTFlPm;JG?ur9bnZ=+a$1|NBXKfdKjJS)&?snxxqU`o7kGGDOoz! z8_G2>41lChdzu019DCT{Tkq;ow{t|OAE*nQNuf`w#^^dTB@>*OIn}5G^yG%|=R$NE zi@bfD_%Byw`_cYCUN=K7!#-ej@!~S{_G*4H9d#5@SFTUAB2XKsKTRE}8GH*yFEF}< zf&_SHQiA22m7-NC3zd!BSGvsc%`l$p*5Y7EFGh2?3bikiDaxh?t{Ow-GOS0X40M*$ zbLDG|5%Pn+Qq+k?9Td0hAeW9u(skahtfLN8abs-SXR3|-8FAerj#M9WOV}8d*cR>F z3DqrGf_pYHbFUOpTY&(}&xU5&vgJ6lc8bfYN8@}&gcV+?z>Y))s_4@Cd<2><{9 literal 0 HcmV?d00001 From 7cab6fde878f112f03e37fc86f8d9c399d9fca60 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Fri, 14 Apr 2023 20:32:29 +0700 Subject: [PATCH 36/54] Revert "Merge pull request #99 from ohaiibuzzle/feat/PlayChain-attr" This reverts commit 7f02ae52c44ff03a850bea9dd6574d34b91a0eaf, reversing changes made to 1dcc1414d49108db388b414c089d008e5cbb6253. --- PlayTools/MysticRunes/PlayedApple.swift | 96 ++----------------------- 1 file changed, 4 insertions(+), 92 deletions(-) diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index 710f69b3..7eae64d3 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -45,87 +45,6 @@ public class PlayKeychain: NSObject { .appendingPathComponent("\(serviceName)-\(accountName)-\(classType).plist") } - private static func createGenpAttr(_ query: NSDictionary, _ keychainPath: URL) -> NSDictionary? { - let currentTime = Date() - let keychainDict = NSDictionary(contentsOf: keychainPath) - - let attributes = NSMutableDictionary() - attributes[kSecAttrAccessControl as String] = - SecAccessControlCreateWithFlags(kCFAllocatorDefault, - kSecAttrAccessibleWhenUnlockedThisDeviceOnly, - .or, - nil) - attributes[kSecAttrAccount as String] = query["acct"] ?? keychainDict?["acct"] ?? "" - attributes[kSecAttrAccessGroup as String] = "*" - attributes[kSecAttrCreationDate as String] = String(describing: currentTime) - .replacingOccurrences(of: "UTC", with: "+0000") - attributes[kSecAttrModificationDate as String] = String(describing: currentTime) - .replacingOccurrences(of: "UTC", with: "+0000") - attributes["musr"] = Data() - attributes[kSecAttrPath as String] = keychainDict?["pdmn"] ?? "ak" - attributes["sha1"] = Data() - attributes[kSecAttrService as String] = query["svce"] ?? keychainDict?["svce"] ?? "" - attributes[kSecAttrSynchronizable as String] = query["sync"] ?? keychainDict?["sync"] ?? 0 - attributes["tomb"] = query["tomb"] ?? keychainDict?["tomb"] ?? 0 - return attributes - } - - private static func createKeysAttr(_ query: NSDictionary, _ keychainPath: URL) -> NSDictionary? { - let currentTime = Date() - let jan1st2001 = Date(timeIntervalSince1970: 978307200) - let keychainDict = NSDictionary(contentsOf: keychainPath) - - let attributes = NSMutableDictionary() - attributes["UUID"] = UUID().uuidString - attributes[kSecAttrAccessControl as String] = - SecAccessControlCreateWithFlags(kCFAllocatorDefault, - kSecAttrAccessibleWhenUnlockedThisDeviceOnly, - .or, - nil) - attributes[kSecAttrAccessGroup as String] = "*" - attributes["asen"] = query["asen"] ?? keychainDict?["asen"] ?? 0 - attributes[kSecAttrApplicationTag as String] = - query["atag"] as? Data? ?? keychainDict?["atag"] as? Data? ?? Data() - attributes[kSecAttrKeySizeInBits as String] = query["bsiz"] ?? keychainDict?["bsiz"] ?? 0 - attributes[kSecAttrCreationDate as String] = - String(describing: currentTime).replacingOccurrences(of: "UTC", with: "+0000") - attributes[kSecAttrCreator as String] = query["crtr"] ?? keychainDict?["crtr"] ?? 0 - attributes["decr"] = query["decr"] ?? keychainDict?["decr"] ?? 0 - attributes["drve"] = query["drve"] ?? keychainDict?["drve"] ?? 0 - attributes["edat"] = query["edat"] ?? keychainDict?["edat"] ?? - String(describing: jan1st2001).replacingOccurrences(of: "UTC", with: "+0000") - attributes["encr"] = query["encr"] ?? keychainDict?["encr"] ?? 0 - attributes["esiz"] = query["esiz"] ?? keychainDict?["esiz"] ?? 0 - attributes["extr"] = query["extr"] ?? keychainDict?["extr"] ?? 0 - attributes["kcls"] = query["kcls"] ?? keychainDict?["kcls"] ?? 0 - attributes[kSecAttrLabel as String] = query["labl"] ?? keychainDict?["labl"] ?? "" - attributes[kSecAttrModificationDate as String] = - String(describing: currentTime).replacingOccurrences(of: "UTC", with: "+0000") - attributes["modi"] = query["modi"] ?? keychainDict?["modi"] ?? 1 - attributes["musr"] = Data() - attributes["next"] = query["next"] ?? keychainDict?["next"] ?? 0 - attributes[kSecAttrPath as String] = keychainDict?["pdmn"] ?? "dku" - attributes["perm"] = query["perm"] ?? keychainDict?["perm"] ?? 1 - attributes["priv"] = query["priv"] ?? keychainDict?["priv"] ?? 1 - attributes["sdat"] = query["sdat"] ?? keychainDict?["sdat"] ?? String(describing: jan1st2001) - .replacingOccurrences(of: "UTC", with: "+0000") - attributes["sens"] = query["sens"] ?? keychainDict?["sens"] ?? 0 - attributes["sha1"] = query["sha1"] as? Data ?? keychainDict?["sha1"] as? Data? ?? Data() - return attributes - } - - static private func getAttributes(_ query: NSDictionary, _ keychainPath: URL) -> NSDictionary? { - // First, check if the kind of key is genp or keys - let classType = query[kSecClass as String] as? String ?? "" - if classType == "genp" { - return createGenpAttr(query, keychainPath) - } else if classType == "keys" { - return createKeysAttr(query, keychainPath) - } else { - return nil - } - } - @objc public static func debugLogger(_ logContent: String) { if PlaySettings.shared.settingsData.playChainDebugging { NSLog("PC-DEBUG: \(logContent)") @@ -150,7 +69,7 @@ public class PlayKeychain: NSObject { return errSecIO } // Place v_data in the result - if let v_data = attributes["v_Data"] { + if let v_data = attributes["v_data"] { result?.pointee = v_data as CFTypeRef } return errSecSuccess @@ -209,21 +128,14 @@ public class PlayKeychain: NSObject { -> OSStatus { // Get the path to the keychain file let keychainPath = keychainPath(query) + // Read the dictionary from the keychain file + let keychainDict = NSDictionary(contentsOf: keychainPath) // Check the `r_Attributes` key. If it is set to 1 in the query // DROP, NOT IMPLEMENTED let classType = query[kSecClass as String] as? String ?? "" if query["r_Attributes"] as? Int == 1 { - debugLogger("Attributes requested") - let attributesDict = getAttributes(query, keychainPath) - if attributesDict == nil { - return errSecItemNotFound - } - // convert to CFDictonary - let attributes = attributesDict! as CFDictionary - result?.pointee = attributes as CFTypeRef - return errSecSuccess + return errSecItemNotFound } - let keychainDict = NSDictionary(contentsOf: keychainPath) // If the keychain file doesn't exist, return errSecItemNotFound if keychainDict == nil { debugLogger("Keychain file not found at \(keychainPath)") From dc4d7208e4c6d4e38b5d4abe5b2d44168841acd5 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Fri, 14 Apr 2023 20:33:38 +0700 Subject: [PATCH 37/54] fix: small issue in add --- PlayTools/MysticRunes/PlayedApple.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index 7eae64d3..34acf2b5 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -68,8 +68,8 @@ public class PlayKeychain: NSObject { debugLogger("Failed to write keychain file") return errSecIO } - // Place v_data in the result - if let v_data = attributes["v_data"] { + // Place v_Data in the result + if let v_data = attributes["v_Data"] { result?.pointee = v_data as CFTypeRef } return errSecSuccess From 7afd172413a1ae533218cdee9c66d804a01e10fd Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Wed, 19 Apr 2023 00:03:43 +0700 Subject: [PATCH 38/54] fix: change working directory to / like iOS --- PlayTools/PlayCover.swift | 5 +++++ PlayTools/PlaySettings.swift | 3 +++ 2 files changed, 8 insertions(+) diff --git a/PlayTools/PlayCover.swift b/PlayTools/PlayCover.swift index 4f15a11a..b8c5fe35 100644 --- a/PlayTools/PlayCover.swift +++ b/PlayTools/PlayCover.swift @@ -16,6 +16,11 @@ public class PlayCover: NSObject { AKInterface.initialize() PlayInput.shared.initialize() DiscordIPC.shared.initialize() + + if PlaySettings.shared.rootWorkDir { + // Change the working directory to / just like iOS + FileManager.default.changeCurrentDirectoryPath("/") + } } @objc static public func initMenu(menu: NSObject) { diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index cdbb79ff..5b64c3bb 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -71,6 +71,8 @@ let settings = PlaySettings.shared @objc lazy var windowFixMethod = settingsData.windowFixMethod @objc lazy var customScaler = settingsData.customScaler + + @objc lazy var rootWorkDir = settingsData.rootWorkDir } struct AppSettingsData: Codable { @@ -93,4 +95,5 @@ struct AppSettingsData: Codable { var playChainDebugging = false var inverseScreenValues = false var windowFixMethod = 0 + var rootWorkDir = true } From b5a7b18071a263e077482a60d824a3d80e2c0bd2 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sat, 22 Apr 2023 01:44:46 +0700 Subject: [PATCH 39/54] fix: buffer allocation --- PlayTools/PlayLoader.m | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index 742dfed5..b80ba2ec 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -63,7 +63,11 @@ static int pt_sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void * if ((strcmp(name, "hw.machine") == 0) || (strcmp(name, "hw.product") == 0) || (strcmp(name, "hw.model") == 0)) { if (oldp == NULL) { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); - *oldlenp = strlen(DEVICE_MODEL) + 1; + // We don't want to accidentally decrease it because the real sysctl call will ENOMEM + // as model are much longer on Macs (eg. MacBookAir10,1) + if (*oldlenp < strlen(DEVICE_MODEL) + 1) { + *oldlenp = strlen(DEVICE_MODEL) + 1; + } return ret; } else if (oldp != NULL) { @@ -79,7 +83,9 @@ static int pt_sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void * } else if ((strcmp(name, "hw.target") == 0)) { if (oldp == NULL) { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); - *oldlenp = strlen(OEM_ID) + 1; + if (*oldlenp < strlen(OEM_ID) + 1) { + *oldlenp = strlen(OEM_ID) + 1; + } return ret; } else if (oldp != NULL) { int ret = sysctlbyname(name, oldp, oldlenp, newp, newlen); From 4cf86c9bc296d4603428901139e7115efe70e832 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Wed, 22 Feb 2023 03:29:53 +0800 Subject: [PATCH 40/54] automatic keyboard mapping --- AKPlugin.swift | 4 +- PlayTools/Controls/ControlMode.swift | 24 +--- PlayTools/Controls/PlayInput.swift | 188 +++++++++++++++------------ PlayTools/Controls/PlayMice.swift | 62 ++++----- PlayTools/Controls/Toucher.swift | 9 +- PlayTools/PlaySettings.swift | 3 - 6 files changed, 147 insertions(+), 143 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 0e17c448..2d1a76b3 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -18,11 +18,11 @@ class AKPlugin: NSObject, Plugin { } var mousePoint: CGPoint { - NSApplication.shared.windows.first!.mouseLocationOutsideOfEventStream as CGPoint + NSApplication.shared.windows.first?.mouseLocationOutsideOfEventStream ?? CGPoint() } var windowFrame: CGRect { - NSApplication.shared.windows.first!.frame as CGRect + NSApplication.shared.windows.first?.frame ?? CGRect() } var isMainScreenEqualToFirst: Bool { diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index deb68764..e95842dd 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -10,29 +10,21 @@ let mode = ControlMode.mode public class ControlMode { static public let mode = ControlMode() - public var visible: Bool = PlaySettings.shared.mouseMapping + public var visible: Bool = true func show(_ show: Bool) { if !editor.editorMode { if show { if !visible { - NotificationCenter.default.post(name: NSNotification.Name.playtoolsKeymappingWillDisable, - object: nil, userInfo: [:]) if screen.fullscreen { screen.switchDock(true) } - if PlaySettings.shared.mouseMapping { - AKInterface.shared!.unhideCursor() - } - PlayInput.shared.invalidate() + AKInterface.shared!.unhideCursor() +// PlayInput.shared.invalidate() } } else { if visible { - NotificationCenter.default.post(name: NSNotification.Name.playtoolsKeymappingWillEnable, - object: nil, userInfo: [:]) - if PlaySettings.shared.mouseMapping { - AKInterface.shared!.hideCursor() - } + AKInterface.shared!.hideCursor() if screen.fullscreen { screen.switchDock(false) } @@ -44,11 +36,3 @@ public class ControlMode { } } } - -extension NSNotification.Name { - public static let playtoolsKeymappingWillEnable: NSNotification.Name - = NSNotification.Name("playtools.keymappingWillEnable") - - public static let playtoolsKeymappingWillDisable: NSNotification.Name - = NSNotification.Name("playtools.keymappingWillDisable") -} diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index b632667c..27c192b2 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -5,20 +5,55 @@ import UIKit class PlayInput { static let shared = PlayInput() var actions = [Action]() - var timeoutForBind = true + static var keyboardMapped = true static private var lCmdPressed = false static private var rCmdPressed = false + static public var buttonHandlers: [String: [(Bool) -> Void]] = [:] + func invalidate() { - PlayMice.shared.stop() for action in self.actions { action.invalidate() } } + static public func registerButton(key: String, handler: @escaping (Bool) -> Void) { + if PlayInput.buttonHandlers[key] == nil { + PlayInput.buttonHandlers[key] = [] + } + PlayInput.buttonHandlers[key]!.append(handler) + } + + func keyboardHandler(_ keyCode: UInt16, _ pressed: Bool) { + let name = KeyCodeNames.virtualCodes[keyCode] ?? "Btn" + guard let handlers = PlayInput.buttonHandlers[name] else { + return + } + for handler in handlers { + handler(pressed) + } + } + + func controllerButtonHandler(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { + let name: String = element.aliases.first! + if let buttonElement = element as? GCControllerButtonInput { +// Toast.showOver(msg: "recognised controller button: \(name)") + guard let handlers = PlayInput.buttonHandlers[name] else { return } + Toast.showOver(msg: name + ": \(buttonElement.isPressed)") + for handler in handlers { + handler(buttonElement.isPressed) + } + } else if let dpadElement = element as? GCControllerDirectionPad { + PlayMice.shared.handleControllerDirectionPad(profile, dpadElement) + } else { + Toast.showOver(msg: "unrecognised controller element input happens") + } + } + func parseKeymap() { actions = [] + PlayInput.buttonHandlers.removeAll(keepingCapacity: true) for button in keymap.keymapData.buttonModels { actions.append(ButtonAction(data: button)) } @@ -28,9 +63,7 @@ class PlayInput { } for mouse in keymap.keymapData.mouseAreaModel { - if mouse.keyName.hasSuffix("tick") || settings.mouseMapping { - actions.append(CameraAction(data: mouse)) - } + actions.append(CameraAction(data: mouse)) } for joystick in keymap.keymapData.joystickModel { @@ -45,12 +78,14 @@ class PlayInput { public func toggleEditor(show: Bool) { mode.show(show) + PlayInput.keyboardMapped = !show if show { if let keyboard = GCKeyboard.coalesced!.keyboardInput { keyboard.keyChangedHandler = { _, _, keyCode, _ in if !PlayInput.cmdPressed() && !PlayInput.FORBIDDEN.contains(keyCode) - && self.isSafeToBind(keyboard) { + && self.isSafeToBind(keyboard) + && KeyCodeNames.keyCodes[keyCode.rawValue] != nil { EditorController.shared.setKey(keyCode.rawValue) } } @@ -58,27 +93,34 @@ class PlayInput { if let controller = GCController.current?.extendedGamepad { controller.valueChangedHandler = { _, element in // This is the index of controller buttons, which is String, not Int - let alias: String! = element.aliases.first + var alias: String = element.aliases.first! + if alias == "Direction Pad" { + guard let dpadElement = element as? GCControllerDirectionPad else { + Toast.showOver(msg: "cannot map direction pad: element type not recognizable") + return + } + if dpadElement.xAxis.value > 0 { + alias = dpadElement.right.aliases.first! + } else if dpadElement.xAxis.value < 0 { + alias = dpadElement.left.aliases.first! + } + if dpadElement.yAxis.value > 0 { + alias = dpadElement.down.aliases.first! + } else if dpadElement.yAxis.value < 0 { + alias = dpadElement.up.aliases.first! + } + } EditorController.shared.setKey(alias) } } } else { - GCKeyboard.coalesced!.keyboardInput!.keyChangedHandler = nil - GCController.current?.extendedGamepad?.valueChangedHandler = nil + DispatchQueue.main.async(execute: parseKeymap) } } func setup() { - parseKeymap() - - for mouse in GCMouse.mice() { - if settings.mouseMapping { - mouse.mouseInput?.mouseMovedHandler = PlayMice.shared.handleMouseMoved - } else { - mouse.mouseInput?.mouseMovedHandler = PlayMice.shared.handleFakeMouseMoved - } - } - + GCKeyboard.coalesced?.keyboardInput?.keyChangedHandler = nil + GCController.current?.extendedGamepad?.valueChangedHandler = controllerButtonHandler } static public func cmdPressed() -> Bool { @@ -102,23 +144,21 @@ class PlayInput { .printScreen ] - private func swapMode(_ pressed: Bool) { - if !settings.mouseMapping { - return - } - if pressed { - if !mode.visible { - self.invalidate() - } - mode.show(!mode.visible) - } + private func swapMode() { +// if !settings.mouseMapping { +// return +// } +// if !mode.visible { +// self.invalidate() +// } + mode.show(!mode.visible) } var root: UIViewController? { return screen.window?.rootViewController } - func setupShortcuts() { + func setupHotkeys() { if let keyboard = GCKeyboard.coalesced?.keyboardInput { keyboard.button(forKeyCode: .leftGUI)?.pressedChangedHandler = { _, _, pressed in PlayInput.lCmdPressed = pressed @@ -126,46 +166,7 @@ class PlayInput { keyboard.button(forKeyCode: .rightGUI)?.pressedChangedHandler = { _, _, pressed in PlayInput.rCmdPressed = pressed } - keyboard.button(forKeyCode: .leftAlt)?.pressedChangedHandler = { _, _, pressed in - self.swapMode(pressed) - } - keyboard.button(forKeyCode: .rightAlt)?.pressedChangedHandler = { _, _, pressed in - self.swapMode(pressed) - } - } - } - - func initializeToasts() { - if !settings.mouseMapping || !mode.visible { - return - } - Toast.showHint(title: NSLocalizedString("hint.enableKeymapping.title", - tableName: "Playtools", - value: "Keymapping Disabled", comment: ""), - text: [NSLocalizedString("hint.enableKeymapping.content.before", - tableName: "Playtools", - value: "Press", comment: ""), - " option ⌥ ", - NSLocalizedString("hint.enableKeymapping.content.after", - tableName: "Playtools", - value: "to enable keymapping", comment: "")], - notification: NSNotification.Name.playtoolsKeymappingWillEnable) - let center = NotificationCenter.default - var token: NSObjectProtocol? - token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, - object: nil, queue: OperationQueue.main) { _ in - center.removeObserver(token!) - Toast.showHint(title: NSLocalizedString("hint.disableKeymapping.title", - tableName: "Playtools", - value: "Keymapping Enabled", comment: ""), - text: [NSLocalizedString("hint.disableKeymapping.content.before", - tableName: "Playtools", - value: "Press", comment: ""), - " option ⌥ ", - NSLocalizedString("hint.disableKeymapping.content.after", - tableName: "Playtools", - value: "to disable keymapping", comment: "")], - notification: NSNotification.Name.playtoolsKeymappingWillDisable) + // TODO: set a timeout to display usage guide of Option and Keymapping menu in turn } } @@ -178,7 +179,7 @@ class PlayInput { let main = OperationQueue.main centre.addObserver(forName: NSNotification.Name.GCKeyboardDidConnect, object: nil, queue: main) { _ in - self.setupShortcuts() + self.setupHotkeys() if !mode.visible { self.setup() } @@ -194,17 +195,42 @@ class PlayInput { if !mode.visible { self.setup() } + if EditorController.shared.editorMode { + self.toggleEditor(show: true) + } } + parseKeymap() + centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in + PlayInput.keyboardMapped = true + } + centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in + PlayInput.keyboardMapped = false + } + centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, + queue: main) { _ in + if !mode.visible { + AKInterface.shared!.warpCursor() + } + } + setupHotkeys() - setupShortcuts() - DispatchQueue.main.asyncAfter(deadline: .now() + 5, qos: .utility, execute: initializeToasts) - - // Fix beep sound - AKInterface.shared! - .eliminateRedundantKeyPressEvents(self.dontIgnore) - } - - func dontIgnore() -> Bool { - (mode.visible && !EditorController.shared.editorMode) || PlayInput.cmdPressed() + AKInterface.shared!.initialize(keyboard: {keycode, pressed in + let consumed = PlayInput.keyboardMapped && !PlayInput.cmdPressed() + if !consumed { + return false + } + self.keyboardHandler(keycode, pressed) + return consumed + }, mouseMoved: {deltaX, deltaY in + if !PlayInput.keyboardMapped { + return false + } + if mode.visible { + PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) + } else { + PlayMice.shared.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) + } + return true + }, swapMode: self.swapMode) } } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 3cb3328e..14ae316e 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -12,16 +12,12 @@ public class PlayMice { public static let elementName = "Mouse" private static var isInit = false - private var acceptMouseEvents = !PlaySettings.shared.mouseMapping - public init() { if !PlayMice.isInit { setupMouseButton(_up: 2, _down: 4) setupMouseButton(_up: 8, _down: 16) setupMouseButton(_up: 33554432, _down: 67108864) - if !acceptMouseEvents { - setupScrollWheelHandler() - } + setupScrollWheelHandler() PlayMice.isInit = true } } @@ -37,11 +33,14 @@ public class PlayMice { cameraScaleHandler: [String: (CGFloat, CGFloat) -> Void] = [:], joystickHandler: [String: (CGFloat, CGFloat) -> Void] = [:] - public func cursorPos() -> CGPoint { + public func cursorPos() -> CGPoint? { var point = CGPoint(x: 0, y: 0) point = AKInterface.shared!.mousePoint let rect = AKInterface.shared!.windowFrame let viewRect: CGRect = screen.screenRect + if rect.width < 1 || rect.height < 1 { + return nil + } let widthRate = viewRect.width / rect.width var rate = viewRect.height / rect.height if widthRate > rate { @@ -101,7 +100,9 @@ public class PlayMice { public func handleFakeMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { if self.fakedMousePressed { - Toucher.touchcam(point: self.cursorPos(), phase: UITouch.Phase.moved, tid: &fakedMouseTouchPointId) + if let pos = self.cursorPos() { + Toucher.touchcam(point: pos, phase: UITouch.Phase.moved, tid: &fakedMouseTouchPointId) + } } } @@ -126,15 +127,8 @@ public class PlayMice { } private func dontIgnore(_ actionIndex: Int, _ pressed: Bool, _ isEventWindow: Bool) -> Bool { - if EditorController.shared.editorMode { - if pressed && actionIndex != 2 { - EditorController.shared.setKey(buttonIndex[actionIndex]!) - } - return true - } - if self.acceptMouseEvents { - let curPos = self.cursorPos() - if pressed { + if mode.visible && pressed && PlayInput.keyboardMapped { + if let curPos = self.cursorPos() { if !self.fakedMousePressed // For traffic light buttons when not fullscreen && curPos.y > 0 @@ -145,13 +139,12 @@ public class PlayMice { tid: &fakedMouseTouchPointId) return false } - } else { - if self.fakedMousePressed { - Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &fakedMouseTouchPointId) - return false - } } - return true + } else if self.fakedMousePressed { + if let curPos = self.cursorPos() { + Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &fakedMouseTouchPointId) + return false + } } if !mode.visible { if let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[buttonIndex[actionIndex]!]!] { @@ -161,6 +154,11 @@ public class PlayMice { } return false } + if EditorController.shared.editorMode { + if pressed && actionIndex != 2 { + EditorController.shared.setKey(buttonIndex[actionIndex]!) + } + } return true } } @@ -217,7 +215,13 @@ class CameraAction: Action { swipeScale1 = SwipeAction() swipeScale2 = SwipeAction() PlayMice.shared.cameraMoveHandler[key] = self.moveUpdated - PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = self.scaleUpdated + PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = {deltaX, deltaY in + if mode.visible { + CameraAction.dragUpdated(deltaX, deltaY) + } else { + self.scaleUpdated(deltaX, deltaY) + } + } } func moveUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { swipeMove.move(from: {return center}, deltaX: deltaX, deltaY: deltaY) @@ -239,16 +243,14 @@ class CameraAction: Action { return CGPoint(x: center.x, y: center.y + 100) }, deltaX: 0, deltaY: -moveY) } - // Event handlers SHOULD be SMALL - // DO NOT check things like mode.visible in an event handler - // change the handler itself instead + static func dragUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { swipeDrag.move(from: PlayMice.shared.cursorPos, deltaX: deltaX * 4, deltaY: -deltaY * 4) } func invalidate() { PlayMice.shared.cameraMoveHandler.removeValue(forKey: key) - PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = CameraAction.dragUpdated + PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = nil swipeMove.invalidate() swipeScale1.invalidate() swipeScale2.invalidate() @@ -263,7 +265,6 @@ class SwipeAction: Action { // in rare cases the cooldown reset task is lost by the dispatch queue self.cooldown = false // TODO: camera mode switch: Flexibility v.s. Precision - // TODO: find reasonable ways to promote fake touch priority timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.1, leeway: DispatchTimeInterval.never) timer.setEventHandler(qos: DispatchQoS.background, handler: self.checkEnded) timer.activate() @@ -293,13 +294,14 @@ class SwipeAction: Action { self.lastCounter = self.counter } - public func move(from: () -> CGPoint, deltaX: CGFloat, deltaY: CGFloat) { + public func move(from: () -> CGPoint?, deltaX: CGFloat, deltaY: CGFloat) { if id == nil { if cooldown { return } + guard let start = from() else {return} + location = start counter = 0 - location = from() Toucher.touchcam(point: location, phase: UITouch.Phase.began, tid: &id) timer.resume() } diff --git a/PlayTools/Controls/Toucher.swift b/PlayTools/Controls/Toucher.swift index 60a27be5..46b4dc39 100644 --- a/PlayTools/Controls/Toucher.swift +++ b/PlayTools/Controls/Toucher.swift @@ -20,7 +20,8 @@ class Toucher { if phase == UITouch.Phase.began { tid = nextId nextId += 1 -// Toast.showOver(msg: tid!.description) + keyWindow = screen.keyWindow + keyView = keyWindow!.hitTest(point, with: nil) } guard let bigId = tid else { // sending other phases with empty id is no-op @@ -30,12 +31,6 @@ class Toucher { tid = nil } touchQueue.async { - if keyWindow == nil || keyView == nil { - keyWindow = screen.keyWindow - DispatchQueue.main.sync { - keyView = keyWindow?.hitTest(point, with: nil) - } - } var pointId: Int = 0 if phase != UITouch.Phase.began { guard let id = idMap.firstIndex(of: bigId) else { diff --git a/PlayTools/PlaySettings.swift b/PlayTools/PlaySettings.swift index 5b64c3bb..0fab777e 100644 --- a/PlayTools/PlaySettings.swift +++ b/PlayTools/PlaySettings.swift @@ -27,8 +27,6 @@ let settings = PlaySettings.shared lazy var keymapping = settingsData.keymapping - lazy var mouseMapping = settingsData.mouseMapping - lazy var notch = settingsData.notch lazy var sensitivity = settingsData.sensitivity / 100 @@ -77,7 +75,6 @@ let settings = PlaySettings.shared struct AppSettingsData: Codable { var keymapping = true - var mouseMapping = true var sensitivity: Float = 50 var disableTimeout = false From 76b0f16a49a43bc99157c38164ad23614be26c1b Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Fri, 24 Feb 2023 17:46:06 +0800 Subject: [PATCH 41/54] switch CMD key to NSEvent --- PlayTools/Controls/PlayInput.swift | 113 ++++++++++++++++++----------- 1 file changed, 71 insertions(+), 42 deletions(-) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 27c192b2..9bd2b6dd 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -6,7 +6,7 @@ class PlayInput { static let shared = PlayInput() var actions = [Action]() static var keyboardMapped = true - + static var shouldLockCursor = true static private var lCmdPressed = false static private var rCmdPressed = false @@ -19,20 +19,26 @@ class PlayInput { } static public func registerButton(key: String, handler: @escaping (Bool) -> Void) { + if ["LMB", "RMB", "MMB"].contains(key) { + PlayInput.shouldLockCursor = true + } if PlayInput.buttonHandlers[key] == nil { PlayInput.buttonHandlers[key] = [] } PlayInput.buttonHandlers[key]!.append(handler) } - func keyboardHandler(_ keyCode: UInt16, _ pressed: Bool) { + func keyboardHandler(_ keyCode: UInt16, _ pressed: Bool) -> Bool { let name = KeyCodeNames.virtualCodes[keyCode] ?? "Btn" guard let handlers = PlayInput.buttonHandlers[name] else { - return + return false } + var mapped = false for handler in handlers { handler(pressed) + mapped = true } + return mapped } func controllerButtonHandler(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { @@ -53,6 +59,7 @@ class PlayInput { func parseKeymap() { actions = [] + PlayInput.shouldLockCursor = false PlayInput.buttonHandlers.removeAll(keepingCapacity: true) for button in keymap.keymapData.buttonModels { actions.append(ButtonAction(data: button)) @@ -74,12 +81,16 @@ class PlayInput { actions.append(JoystickAction(data: joystick)) } } + if !PlayInput.shouldLockCursor { + PlayInput.shouldLockCursor = PlayMice.shared.mouseMovementMapped() + } } public func toggleEditor(show: Bool) { - mode.show(show) PlayInput.keyboardMapped = !show if show { + self.invalidate() + mode.show(show) if let keyboard = GCKeyboard.coalesced!.keyboardInput { keyboard.keyChangedHandler = { _, _, keyCode, _ in if !PlayInput.cmdPressed() @@ -114,7 +125,9 @@ class PlayInput { } } } else { - DispatchQueue.main.async(execute: parseKeymap) + setup() + parseKeymap() + self.swapMode() } } @@ -145,12 +158,10 @@ class PlayInput { ] private func swapMode() { -// if !settings.mouseMapping { -// return -// } -// if !mode.visible { -// self.invalidate() -// } + if !PlayInput.shouldLockCursor { + mode.show(true) + return + } mode.show(!mode.visible) } @@ -158,18 +169,6 @@ class PlayInput { return screen.window?.rootViewController } - func setupHotkeys() { - if let keyboard = GCKeyboard.coalesced?.keyboardInput { - keyboard.button(forKeyCode: .leftGUI)?.pressedChangedHandler = { _, _, pressed in - PlayInput.lCmdPressed = pressed - } - keyboard.button(forKeyCode: .rightGUI)?.pressedChangedHandler = { _, _, pressed in - PlayInput.rCmdPressed = pressed - } - // TODO: set a timeout to display usage guide of Option and Keymapping menu in turn - } - } - func initialize() { if !PlaySettings.shared.keymapping { return @@ -178,19 +177,6 @@ class PlayInput { let centre = NotificationCenter.default let main = OperationQueue.main - centre.addObserver(forName: NSNotification.Name.GCKeyboardDidConnect, object: nil, queue: main) { _ in - self.setupHotkeys() - if !mode.visible { - self.setup() - } - } - - centre.addObserver(forName: NSNotification.Name.GCMouseDidConnect, object: nil, queue: main) { _ in - if !mode.visible { - self.setup() - } - } - centre.addObserver(forName: NSNotification.Name.GCControllerDidConnect, object: nil, queue: main) { _ in if !mode.visible { self.setup() @@ -212,15 +198,57 @@ class PlayInput { AKInterface.shared!.warpCursor() } } - setupHotkeys() + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { + if !mode.visible || self.actions.count <= 0 || !PlayInput.shouldLockCursor { + return + } + let persistenceKeyname = "playtoolsKeymappingDisabledAt" + let lastUse = UserDefaults.standard.float(forKey: persistenceKeyname) + var thisUse = lastUse + if lastUse < 1 { + thisUse = 2 + } else { + thisUse = Float(Date.timeIntervalSinceReferenceDate) + } + var token2: NSObjectProtocol? + let center = NotificationCenter.default + token2 = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillDisable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token2!) + UserDefaults.standard.set(thisUse, forKey: persistenceKeyname) + } + if lastUse > Float(Date.now.addingTimeInterval(-86400*14).timeIntervalSinceReferenceDate) { + return + } + Toast.showHint(title: "Mouse Mapping Disabled", text: ["Press ", "option ⌥", " to enable mouse mapping"], + timeout: 10, + notification: NSNotification.Name.playtoolsKeymappingWillEnable) + var token: NSObjectProtocol? + token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token!) + Toast.showHint(title: "Cursor Locked", text: ["Press ", "option ⌥", " to release cursor"], + timeout: 10, + notification: NSNotification.Name.playtoolsKeymappingWillDisable) + } + } - AKInterface.shared!.initialize(keyboard: {keycode, pressed in - let consumed = PlayInput.keyboardMapped && !PlayInput.cmdPressed() - if !consumed { + AKInterface.shared!.initialize(keyboard: {keycode, pressed, isRepeat in + if keycode == 54 { + PlayInput.rCmdPressed = pressed return false + } else if keycode == 55 { + PlayInput.lCmdPressed = pressed + return false + } + if !PlayInput.keyboardMapped || PlayInput.cmdPressed() { + return false + } + if isRepeat { + return true } - self.keyboardHandler(keycode, pressed) - return consumed + let mapped = self.keyboardHandler(keycode, pressed) + return mapped }, mouseMoved: {deltaX, deltaY in if !PlayInput.keyboardMapped { return false @@ -232,5 +260,6 @@ class PlayInput { } return true }, swapMode: self.swapMode) + PlayMice.shared.initialize() } } From 232395090d02ce658b7e3d517380c43092aca7c2 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 25 Feb 2023 21:18:28 +0800 Subject: [PATCH 42/54] optimize mouse button event handler for branch prediction --- AKPlugin.swift | 41 ++++++--- PlayTools/Controls/PlayInput.swift | 30 +++---- PlayTools/Controls/PlayMice.swift | 135 +++++++++++++---------------- Plugin.swift | 3 +- 4 files changed, 105 insertions(+), 104 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 2d1a76b3..7a77331e 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -8,6 +8,7 @@ import AppKit import CoreGraphics import Foundation +import GameController class AKPlugin: NSObject, Plugin { required override init() { @@ -37,6 +38,8 @@ class AKPlugin: NSObject, Plugin { NSApplication.shared.windows.first!.styleMask.contains(.fullScreen) } + var cmdPressed: Bool = false + func hideCursor() { NSCursor.hide() CGAssociateMouseAndMouseCursorPosition(0) @@ -58,26 +61,41 @@ class AKPlugin: NSObject, Plugin { } private var modifierFlag: UInt = 0 - func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, + func initialize(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, swapMode: @escaping() -> Void) { + func checkCmd(modifier: NSEvent.ModifierFlags) -> Bool { + if modifier.contains(.command) { + self.cmdPressed = true + return true + } else if self.cmdPressed { + self.cmdPressed = false + } + return false + } NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: { event in - if event.isARepeat { - return nil + if checkCmd(modifier: event.modifierFlags) { + return event } - let consumed = keyboard(event.keyCode, true) + let consumed = keyboard(event.keyCode, true, event.isARepeat) if consumed { return nil } return event }) NSEvent.addLocalMonitorForEvents(matching: .keyUp, handler: { event in - let consumed = keyboard(event.keyCode, false) + if checkCmd(modifier: event.modifierFlags) { + return event + } + let consumed = keyboard(event.keyCode, false, false) if consumed { return nil } return event }) NSEvent.addLocalMonitorForEvents(matching: .flagsChanged, handler: { event in + if checkCmd(modifier: event.modifierFlags) { + return event + } let pressed = self.modifierFlag < event.modifierFlags.rawValue let changed = self.modifierFlag ^ event.modifierFlags.rawValue self.modifierFlag = event.modifierFlags.rawValue @@ -85,7 +103,7 @@ class AKPlugin: NSObject, Plugin { swapMode() return nil } - let consumed = keyboard(event.keyCode, pressed) + let consumed = keyboard(event.keyCode, pressed, false) if consumed { return nil } @@ -101,16 +119,19 @@ class AKPlugin: NSObject, Plugin { }) } - func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, Bool) -> Bool) { + func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool) -> Bool) { NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask(rawValue: UInt64(_up)), handler: { event in - let isEventWindow = event.window == NSApplication.shared.windows.first! - if dontIgnore(_up, true, isEventWindow) { + // For traffic light buttons when fullscreen + if event.window != NSApplication.shared.windows.first! { + return event + } + if dontIgnore(_up, true) { return event } return nil }) NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask(rawValue: UInt64(_down)), handler: { event in - if dontIgnore(_up, false, true) { + if dontIgnore(_up, false) { return event } return nil diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 9bd2b6dd..e7f1ab93 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -7,9 +7,8 @@ class PlayInput { var actions = [Action]() static var keyboardMapped = true static var shouldLockCursor = true - static private var lCmdPressed = false - static private var rCmdPressed = false + static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive) static public var buttonHandlers: [String: [(Bool) -> Void]] = [:] func invalidate() { @@ -35,7 +34,9 @@ class PlayInput { } var mapped = false for handler in handlers { - handler(pressed) + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + handler(pressed) + }) mapped = true } return mapped @@ -137,7 +138,7 @@ class PlayInput { } static public func cmdPressed() -> Bool { - return lCmdPressed || rCmdPressed + return AKInterface.shared!.cmdPressed } private func isSafeToBind(_ input: GCKeyboardInput) -> Bool { @@ -234,14 +235,7 @@ class PlayInput { } AKInterface.shared!.initialize(keyboard: {keycode, pressed, isRepeat in - if keycode == 54 { - PlayInput.rCmdPressed = pressed - return false - } else if keycode == 55 { - PlayInput.lCmdPressed = pressed - return false - } - if !PlayInput.keyboardMapped || PlayInput.cmdPressed() { + if !PlayInput.keyboardMapped { return false } if isRepeat { @@ -253,11 +247,13 @@ class PlayInput { if !PlayInput.keyboardMapped { return false } - if mode.visible { - PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) - } else { - PlayMice.shared.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) - } + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + if mode.visible { + PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) + } else { + PlayMice.shared.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) + } + }) return true }, swapMode: self.swapMode) PlayMice.shared.initialize() diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 14ae316e..fe46d425 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -10,16 +10,12 @@ public class PlayMice { public static let shared = PlayMice() public static let elementName = "Mouse" - private static var isInit = false - public init() { - if !PlayMice.isInit { - setupMouseButton(_up: 2, _down: 4) - setupMouseButton(_up: 8, _down: 16) - setupMouseButton(_up: 33554432, _down: 67108864) - setupScrollWheelHandler() - PlayMice.isInit = true - } + public func initialize() { + setupMouseButton(_up: 2, _down: 4) + setupMouseButton(_up: 8, _down: 16) + setupMouseButton(_up: 33554432, _down: 67108864) + setupScrollWheelHandler() } var fakedMouseTouchPointId: Int? @@ -33,30 +29,40 @@ public class PlayMice { cameraScaleHandler: [String: (CGFloat, CGFloat) -> Void] = [:], joystickHandler: [String: (CGFloat, CGFloat) -> Void] = [:] + public func mouseMovementMapped() -> Bool { + for handler in [draggableHandler, cameraMoveHandler, joystickHandler] + where handler[PlayMice.elementName] != nil { + return true + } + return false + } + public func cursorPos() -> CGPoint? { - var point = CGPoint(x: 0, y: 0) - point = AKInterface.shared!.mousePoint + var point = AKInterface.shared!.mousePoint let rect = AKInterface.shared!.windowFrame - let viewRect: CGRect = screen.screenRect if rect.width < 1 || rect.height < 1 { return nil } + let viewRect: CGRect = screen.screenRect let widthRate = viewRect.width / rect.width var rate = viewRect.height / rect.height if widthRate > rate { // Keep aspect ratio rate = widthRate } - // Horizontally in center - point.x -= (rect.width - viewRect.width / rate)/2 - point.x *= rate if screen.fullscreen { // Vertically in center point.y -= (rect.height - viewRect.height / rate)/2 } point.y *= rate point.y = viewRect.height - point.y - + // For traffic light buttons when not fullscreen + if point.y < 0 { + return nil + } + // Horizontally in center + point.x -= (rect.width - viewRect.width / rate)/2 + point.x *= rate return point } @@ -107,9 +113,9 @@ public class PlayMice { } public func handleMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { - let sensy = CGFloat(PlaySettings.shared.sensitivity) - let cgDx = deltaX * sensy * 0.5, - cgDy = -deltaY * sensy * 0.5 + let sensy = CGFloat(PlaySettings.shared.sensitivity * 0.6) + let cgDx = deltaX * sensy, + cgDy = -deltaY * sensy let name = PlayMice.elementName if let draggableUpdate = self.draggableHandler[name] { draggableUpdate(cgDx, cgDy) @@ -123,43 +129,38 @@ public class PlayMice { let buttonIndex: [Int: Int] = [2: -1, 8: -2, 33554432: -3] private func setupMouseButton(_up: Int, _down: Int) { - AKInterface.shared!.setupMouseButton(_up, _down, dontIgnore(_:_:_:)) + AKInterface.shared!.setupMouseButton(_up, _down, dontIgnore) } - private func dontIgnore(_ actionIndex: Int, _ pressed: Bool, _ isEventWindow: Bool) -> Bool { - if mode.visible && pressed && PlayInput.keyboardMapped { - if let curPos = self.cursorPos() { - if !self.fakedMousePressed - // For traffic light buttons when not fullscreen - && curPos.y > 0 - // For traffic light buttons when fullscreen - && isEventWindow { - Toucher.touchcam(point: curPos, - phase: UITouch.Phase.began, - tid: &fakedMouseTouchPointId) - return false - } - } - } else if self.fakedMousePressed { - if let curPos = self.cursorPos() { - Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &fakedMouseTouchPointId) - return false - } - } - if !mode.visible { - if let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[buttonIndex[actionIndex]!]!] { - for handler in handlers { - handler(pressed) - } - } - return false + private func dontIgnore(_ actionIndex: Int, _ pressed: Bool) -> Bool { + if !PlayInput.shouldLockCursor { + return true } if EditorController.shared.editorMode { if pressed && actionIndex != 2 { EditorController.shared.setKey(buttonIndex[actionIndex]!) } + return true } - return true + guard let curPos = self.cursorPos() else { return true } + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + if self.fakedMousePressed { + Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &self.fakedMouseTouchPointId) + return + } + if mode.visible && pressed { + Toucher.touchcam(point: curPos, + phase: UITouch.Phase.began, + tid: &self.fakedMouseTouchPointId) + } else { + if let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[self.buttonIndex[actionIndex]!]!] { + for handler in handlers { + handler(pressed) + } + } + } + }) + return false } } @@ -216,11 +217,13 @@ class CameraAction: Action { swipeScale2 = SwipeAction() PlayMice.shared.cameraMoveHandler[key] = self.moveUpdated PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = {deltaX, deltaY in - if mode.visible { - CameraAction.dragUpdated(deltaX, deltaY) - } else { - self.scaleUpdated(deltaX, deltaY) - } + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + if mode.visible { + CameraAction.dragUpdated(deltaX, deltaY) + } else { + self.scaleUpdated(deltaX, deltaY) + } + }) } } func moveUpdated(_ deltaX: CGFloat, _ deltaY: CGFloat) { @@ -260,26 +263,16 @@ class CameraAction: Action { class SwipeAction: Action { var location: CGPoint = CGPoint.zero var id: Int? - let timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main) + let timer = DispatchSource.makeTimerSource(flags: [], queue: PlayInput.touchQueue) init() { - // in rare cases the cooldown reset task is lost by the dispatch queue - self.cooldown = false - // TODO: camera mode switch: Flexibility v.s. Precision - timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.1, leeway: DispatchTimeInterval.never) + timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.1, leeway: DispatchTimeInterval.milliseconds(50)) timer.setEventHandler(qos: DispatchQoS.background, handler: self.checkEnded) timer.activate() timer.suspend() } - func delay(_ delay: Double, closure: @escaping () -> Void) { - let when = DispatchTime.now() + delay - Toucher.touchQueue.asyncAfter(deadline: when, execute: closure) - } - - // like sequence but resets when touch begins. Used to calc touch duration + // Count swipe duration var counter = 0 - // if should wait before beginning next touch - var cooldown = false var lastCounter = 0 func checkEnded() { @@ -296,9 +289,6 @@ class SwipeAction: Action { public func move(from: () -> CGPoint?, deltaX: CGFloat, deltaY: CGFloat) { if id == nil { - if cooldown { - return - } guard let start = from() else {return} location = start counter = 0 @@ -317,13 +307,6 @@ class SwipeAction: Action { return } Toucher.touchcam(point: self.location, phase: UITouch.Phase.ended, tid: &id) - // ending and beginning too frequently leads to the beginning event not recognized - // so let the beginning event wait some time - // pause for one frame or two - delay(0.02) { - self.cooldown = false - } - cooldown = true } func invalidate() { diff --git a/Plugin.swift b/Plugin.swift index 877c9db4..37ade2f3 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -17,6 +17,7 @@ public protocol Plugin: NSObjectProtocol { var mainScreenFrame: CGRect { get } var isMainScreenEqualToFirst: Bool { get } var isFullscreen: Bool { get } + var cmdPressed: Bool { get } func hideCursor() func warpCursor() @@ -24,7 +25,7 @@ public protocol Plugin: NSObjectProtocol { func terminateApplication() func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, swapMode: @escaping() -> Void) - func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool, Bool) -> Bool) + func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? func setMenuBarVisible(_ value: Bool) From 3a1c34e8df78aeecd07be3457adf01a749f01842 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sun, 26 Feb 2023 17:44:43 +0800 Subject: [PATCH 43/54] revert removing swipe cd --- PlayTools/Controls/PlayInput.swift | 4 +++- PlayTools/Controls/PlayMice.swift | 17 +++++++++++++++-- PlayTools/Utils/Toast.swift | 10 +++++++--- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index e7f1ab93..f77bb23e 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -188,7 +188,9 @@ class PlayInput { } parseKeymap() centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - PlayInput.keyboardMapped = true + DispatchQueue.main.async(qos: .userInteractive, execute: { + PlayInput.keyboardMapped = true + }) } centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in PlayInput.keyboardMapped = false diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index fe46d425..b37b21cd 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -271,14 +271,20 @@ class SwipeAction: Action { timer.suspend() } + func delay(_ delay: Double, closure: @escaping () -> Void) { + let when = DispatchTime.now() + delay + PlayInput.touchQueue.asyncAfter(deadline: when, execute: closure) + } // Count swipe duration var counter = 0 + // if should wait before beginning next touch + var cooldown = false var lastCounter = 0 func checkEnded() { if self.counter == self.lastCounter { - if self.counter < 12 { - counter += 12 + if self.counter < 4 { + counter += 1 } else { timer.suspend() self.doLiftOff() @@ -289,6 +295,9 @@ class SwipeAction: Action { public func move(from: () -> CGPoint?, deltaX: CGFloat, deltaY: CGFloat) { if id == nil { + if cooldown { + return + } guard let start = from() else {return} location = start counter = 0 @@ -307,6 +316,10 @@ class SwipeAction: Action { return } Toucher.touchcam(point: self.location, phase: UITouch.Phase.ended, tid: &id) + delay(0.02) { + self.cooldown = false + } + cooldown = true } func invalidate() { diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 44fba46a..2832a3a5 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -64,7 +64,7 @@ class Toast { return txt } - public static func showHint(title: String, text: [String] = [], timeout: Double = 3, + public static func showHint(title: String, text: [String] = [], timeout: Double = -3, notification: NSNotification.Name? = nil) { let parent = screen.keyWindow! @@ -89,6 +89,7 @@ class Toast { if hintView.count > 4 { hideHint(hint: hintView.first!) } + var life = timeout if let note = notification { let center = NotificationCenter.default var token: NSObjectProtocol? @@ -96,8 +97,11 @@ class Toast { center.removeObserver(token!) hideHint(hint: messageLabel) } - } else { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + timeout) { + } else if life < 0 { + life = 3 + } + if life >= 0 { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5 + life, qos: .background) { hideHint(hint: messageLabel) } } From 6cd4104a101acf0eea128f427baef88e39440b8b Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Tue, 28 Feb 2023 22:26:03 +0800 Subject: [PATCH 44/54] delay keyboard mapping toggling --- AKPlugin.swift | 4 +++- PlayTools/Controls/PlayInput.swift | 12 +++++++----- PlayTools/Controls/PlayMice.swift | 24 +++++++++++++++++------- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 7a77331e..17cbc810 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -47,8 +47,10 @@ class AKPlugin: NSObject, Plugin { } func warpCursor() { + guard let firstScreen = NSScreen.screens.first else {return} let frame = windowFrame - CGWarpMouseCursorPosition(CGPoint(x: frame.midX, y: frame.midY)) + // Convert from NS coordinates to CG coordinates + CGWarpMouseCursorPosition(CGPoint(x: frame.midX, y: firstScreen.frame.height - frame.midY)) } func unhideCursor() { diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index f77bb23e..7436c2e7 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -5,7 +5,8 @@ import UIKit class PlayInput { static let shared = PlayInput() var actions = [Action]() - static var keyboardMapped = true + static var keyboardMapped = false + private static var keyboardWillMap = true static var shouldLockCursor = true static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive) @@ -188,12 +189,10 @@ class PlayInput { } parseKeymap() centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - DispatchQueue.main.async(qos: .userInteractive, execute: { - PlayInput.keyboardMapped = true - }) + PlayInput.keyboardWillMap = true } centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in - PlayInput.keyboardMapped = false + PlayInput.keyboardWillMap = false } centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, queue: main) { _ in @@ -237,6 +236,9 @@ class PlayInput { } AKInterface.shared!.initialize(keyboard: {keycode, pressed, isRepeat in + if PlayInput.keyboardWillMap != PlayInput.keyboardMapped && pressed { + PlayInput.keyboardMapped = PlayInput.keyboardWillMap + } if !PlayInput.keyboardMapped { return false } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index b37b21cd..83ebfcaa 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -133,11 +133,8 @@ public class PlayMice { } private func dontIgnore(_ actionIndex: Int, _ pressed: Bool) -> Bool { - if !PlayInput.shouldLockCursor { - return true - } - if EditorController.shared.editorMode { - if pressed && actionIndex != 2 { + if !PlayInput.keyboardMapped { + if EditorController.shared.editorMode && actionIndex != 2 && pressed { EditorController.shared.setKey(buttonIndex[actionIndex]!) } return true @@ -258,15 +255,24 @@ class CameraAction: Action { swipeScale1.invalidate() swipeScale2.invalidate() } + + func debug() { + var count = 0 + for swipe in [swipeScale1, swipeScale2, swipeMove, CameraAction.swipeDrag] { + count += 1 + guard let id = swipe.getTouchId() else {continue} + Toast.showHint(title: "type:\(count), id:\(id)") + } + } } class SwipeAction: Action { var location: CGPoint = CGPoint.zero - var id: Int? + private var id: Int? let timer = DispatchSource.makeTimerSource(flags: [], queue: PlayInput.touchQueue) init() { timer.schedule(deadline: DispatchTime.now() + 1, repeating: 0.1, leeway: DispatchTimeInterval.milliseconds(50)) - timer.setEventHandler(qos: DispatchQoS.background, handler: self.checkEnded) + timer.setEventHandler(qos: .userInteractive, handler: self.checkEnded) timer.activate() timer.suspend() } @@ -326,4 +332,8 @@ class SwipeAction: Action { timer.cancel() self.doLiftOff() } + + public func getTouchId() -> Int? { + return id + } } From 2aea47eae426e5dbc5c35a5399542bc4c0a3acf0 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Fri, 10 Mar 2023 01:13:55 +0800 Subject: [PATCH 45/54] fix option key consumed --- AKPlugin.swift | 8 +++++--- PlayTools/Controls/PlayInput.swift | 16 ++++++++-------- Plugin.swift | 4 ++-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/AKPlugin.swift b/AKPlugin.swift index 17cbc810..3e12d5cf 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -64,7 +64,7 @@ class AKPlugin: NSObject, Plugin { private var modifierFlag: UInt = 0 func initialize(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, - swapMode: @escaping() -> Void) { + swapMode: @escaping() -> Bool) { func checkCmd(modifier: NSEvent.ModifierFlags) -> Bool { if modifier.contains(.command) { self.cmdPressed = true @@ -102,8 +102,10 @@ class AKPlugin: NSObject, Plugin { let changed = self.modifierFlag ^ event.modifierFlags.rawValue self.modifierFlag = event.modifierFlags.rawValue if pressed && NSEvent.ModifierFlags(rawValue: changed).contains(.option) { - swapMode() - return nil + if swapMode() { + return nil + } + return event } let consumed = keyboard(event.keyCode, pressed, false) if consumed { diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 7436c2e7..46128ecc 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -46,9 +46,8 @@ class PlayInput { func controllerButtonHandler(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { let name: String = element.aliases.first! if let buttonElement = element as? GCControllerButtonInput { -// Toast.showOver(msg: "recognised controller button: \(name)") guard let handlers = PlayInput.buttonHandlers[name] else { return } - Toast.showOver(msg: name + ": \(buttonElement.isPressed)") +// Toast.showOver(msg: name + ": \(buttonElement.isPressed)") for handler in handlers { handler(buttonElement.isPressed) } @@ -129,7 +128,7 @@ class PlayInput { } else { setup() parseKeymap() - self.swapMode() + _ = self.swapMode() } } @@ -159,12 +158,13 @@ class PlayInput { .printScreen ] - private func swapMode() { - if !PlayInput.shouldLockCursor { - mode.show(true) - return + private func swapMode() -> Bool { + if PlayInput.shouldLockCursor { + mode.show(!mode.visible) + return true } - mode.show(!mode.visible) + mode.show(true) + return false } var root: UIViewController? { diff --git a/Plugin.swift b/Plugin.swift index 37ade2f3..291052b9 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -23,8 +23,8 @@ public protocol Plugin: NSObjectProtocol { func warpCursor() func unhideCursor() func terminateApplication() - func initialize(keyboard: @escaping(UInt16, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, - swapMode: @escaping() -> Void) + func initialize(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, + swapMode: @escaping() -> Bool) func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? From 14766d31b6cccd34c56c26a4dfc0cd5481012f69 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Thu, 6 Apr 2023 03:44:13 +0800 Subject: [PATCH 46/54] Logging system and ignore repeated Enter key press while typing --- PlayTools/Controls/ControlMode.swift | 1 + PlayTools/Controls/MenuController.swift | 35 +++++------ PlayTools/Controls/PlayInput.swift | 19 +++--- PlayTools/Controls/PlayMice.swift | 1 + PlayTools/Controls/Toucher.swift | 77 +++++++++++++++++-------- 5 files changed, 83 insertions(+), 50 deletions(-) diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index e95842dd..15c16080 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -32,6 +32,7 @@ public class ControlMode { PlayInput.shared.setup() } } + Toucher.writeLog(logMessage: "cursor show switched to \(show)") visible = show } } diff --git a/PlayTools/Controls/MenuController.swift b/PlayTools/Controls/MenuController.swift index a304b48c..d5008a59 100644 --- a/PlayTools/Controls/MenuController.swift +++ b/PlayTools/Controls/MenuController.swift @@ -44,6 +44,13 @@ extension UIApplication { func downscaleElement(_ sender: AnyObject) { EditorController.shared.focusedControl?.resize(down: true) } + + // put a mark in the toucher log, so as to align with tester description + @objc + func markToucherLog(_ sender: AnyObject) { + Toucher.writeLog(logMessage:"mark") + Toast.showHint(title: "Log marked") + } } extension UIViewController { @@ -61,24 +68,19 @@ extension UIViewController { struct CommandsList { static let KeymappingToolbox = "keymapping" } -// have to use a customized name, in case it conflicts with the game's localization file -var keymapping = [ - NSLocalizedString("menu.keymapping.toggleEditor", tableName: "Playtools", - value: "Open/Close Keymapping Editor", comment: ""), - NSLocalizedString("menu.keymapping.deleteElement", tableName: "Playtools", - value: "Delete selected element", comment: ""), - NSLocalizedString("menu.keymapping.upsizeElement", tableName: "Playtools", - value: "Upsize selected element", comment: ""), - NSLocalizedString("menu.keymapping.downsizeElement", tableName: "Playtools", - value: "Downsize selected element", comment: ""), - NSLocalizedString("menu.keymapping.rotateDisplay", tableName: "Playtools", - value: "Rotate display area", comment: "") - ] + +var keymapping = ["Open/Close Keymapping Editor", + "Delete selected element", + "Upsize selected element", + "Downsize selected element", + "Rotate display area", + "Put a mark in toucher log"] var keymappingSelectors = [#selector(UIApplication.switchEditorMode(_:)), #selector(UIApplication.removeElement(_:)), #selector(UIApplication.upscaleElement(_:)), #selector(UIApplication.downscaleElement(_:)), - #selector(UIViewController.rotateView(_:))] + #selector(UIViewController.rotateView(_:)), + #selector(UIApplication.markToucherLog)] class MenuController { init(with builder: UIMenuBuilder) { @@ -86,7 +88,7 @@ class MenuController { } class func keymappingMenu() -> UIMenu { - let keyCommands = [ "K", UIKeyCommand.inputDelete, UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R" ] + let keyCommands = [ "K", UIKeyCommand.inputDelete, UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R", "L"] let arrowKeyChildrenCommands = zip(keyCommands, keymapping).map { (command, btn) in UIKeyCommand(title: btn, @@ -104,8 +106,7 @@ class MenuController { options: .displayInline, children: arrowKeyChildrenCommands) - return UIMenu(title: NSLocalizedString("menu.keymapping", tableName: "Playtools", - value: "Keymapping", comment: ""), + return UIMenu(title: NSLocalizedString("Keymapping", comment: ""), image: nil, identifier: .keymappingMenu, options: [], diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 46128ecc..3e531170 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -5,11 +5,11 @@ import UIKit class PlayInput { static let shared = PlayInput() var actions = [Action]() - static var keyboardMapped = false - private static var keyboardWillMap = true + static var keyboardMapped = true static var shouldLockCursor = true - static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive) + static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive, + autoreleaseFrequency: .workItem) static public var buttonHandlers: [String: [(Bool) -> Void]] = [:] func invalidate() { @@ -89,6 +89,7 @@ class PlayInput { public func toggleEditor(show: Bool) { PlayInput.keyboardMapped = !show + Toucher.writeLog(logMessage: "editor opened? \(show)") if show { self.invalidate() mode.show(show) @@ -189,10 +190,12 @@ class PlayInput { } parseKeymap() centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - PlayInput.keyboardWillMap = true + PlayInput.keyboardMapped = true + Toucher.writeLog(logMessage: "virtual keyboard did hide") } centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in - PlayInput.keyboardWillMap = false + PlayInput.keyboardMapped = false + Toucher.writeLog(logMessage: "virtual keyboard will show") } centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, queue: main) { _ in @@ -236,11 +239,9 @@ class PlayInput { } AKInterface.shared!.initialize(keyboard: {keycode, pressed, isRepeat in - if PlayInput.keyboardWillMap != PlayInput.keyboardMapped && pressed { - PlayInput.keyboardMapped = PlayInput.keyboardWillMap - } if !PlayInput.keyboardMapped { - return false + // explicitly ignore repeated Enter key + return isRepeat && keycode == 36 } if isRepeat { return true diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 83ebfcaa..a32a1a20 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -137,6 +137,7 @@ public class PlayMice { if EditorController.shared.editorMode && actionIndex != 2 && pressed { EditorController.shared.setKey(buttonIndex[actionIndex]!) } + Toucher.writeLog(logMessage: "mouse button pressed? \(pressed)") return true } guard let curPos = self.cursorPos() else { return true } diff --git a/PlayTools/Controls/Toucher.swift b/PlayTools/Controls/Toucher.swift index 46b4dc39..cc949669 100644 --- a/PlayTools/Controls/Toucher.swift +++ b/PlayTools/Controls/Toucher.swift @@ -9,42 +9,71 @@ import UIKit class Toucher { static weak var keyWindow: UIWindow? static weak var keyView: UIView? - static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive) - static var nextId: Int = 0 - static var idMap = [Int?](repeating: nil, count: 64) + // For debug only + static var logEnabled = true + static var logFilePath = + NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/toucher.log" + static private var logCount = 0 + static var logFile: FileHandle? /** on invocations with phase "began", an int id is allocated, which can be used later to refer to this touch point. on invocations with phase "ended", id is set to nil representing the touch point is no longer valid. */ static func touchcam(point: CGPoint, phase: UITouch.Phase, tid: inout Int?) { if phase == UITouch.Phase.began { - tid = nextId - nextId += 1 + if tid != nil { + return + } + tid = -1 keyWindow = screen.keyWindow keyView = keyWindow!.hitTest(point, with: nil) - } - guard let bigId = tid else { - // sending other phases with empty id is no-op + } else if tid == nil { return } - if phase == UITouch.Phase.ended || phase == UITouch.Phase.cancelled { + tid = PTFakeMetaTouch.fakeTouchId(tid!, at: point, with: phase, in: keyWindow, on: keyView) + writeLog(logMessage: + "\(phase.rawValue.description) \(tid!.description) \(point.debugDescription)") + if tid! < 0 { tid = nil } - touchQueue.async { - var pointId: Int = 0 - if phase != UITouch.Phase.began { - guard let id = idMap.firstIndex(of: bigId) else { - // sending other phases before began is no-op - return - } - pointId = id - } - let resultingId = PTFakeMetaTouch.fakeTouchId(pointId, at: point, with: phase, in: keyWindow, on: keyView) - if resultingId < 0 { - idMap[pointId] = nil - } else { - idMap[resultingId] = bigId - } + } + + static func setupLogfile() { + if FileManager.default.createFile(atPath: logFilePath, contents: nil, attributes: nil) { + logFile = FileHandle(forWritingAtPath: logFilePath) + Toast.showOver(msg: logFilePath) + } else { + Toast.showHint(title: "logFile creation failed") + return + } + NotificationCenter.default.addObserver( + forName: NSNotification.Name(rawValue: "NSApplicationWillTerminateNotification"), + object: nil, + queue: OperationQueue.main + ) { _ in + try? logFile?.close() + } + } + + static func writeLog(logMessage: String) { + if !logEnabled { + return + } + guard let file = logFile else { + setupLogfile() + return + } + let message = "\(DispatchTime.now().rawValue) \(logMessage)\n" + guard let data = message.data(using: .utf8) else { + Toast.showHint(title: "log message is utf8 uncodable") + return + } + logCount += 1 + // roll over + if logCount > 60000 { + file.seek(toFileOffset: 0) + logCount = 0 } + file.write(data) } } From ef59e0536082baf8331aef10fb03cc13271fe953 Mon Sep 17 00:00:00 2001 From: Jason Ak Date: Mon, 13 Mar 2023 05:30:48 +0100 Subject: [PATCH 47/54] Bugfix: Controller binding not working if connected after app launch Fix a small regression: When connecting a controller after app launch we have to toggle the Keymap editor to get it saved before the binding work. it's due to ControlMode.visibile being init at true so we don't call `setup` when the notification fires. --- PlayTools/Controls/PlayInput.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 3e531170..7e7a0625 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -181,11 +181,10 @@ class PlayInput { let main = OperationQueue.main centre.addObserver(forName: NSNotification.Name.GCControllerDidConnect, object: nil, queue: main) { _ in - if !mode.visible { - self.setup() - } if EditorController.shared.editorMode { self.toggleEditor(show: true) + } else { + self.setup() } } parseKeymap() From 7c09c0a143d51a42a6c94718530813a0d19a2300 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Fri, 28 Apr 2023 11:31:31 +0800 Subject: [PATCH 48/54] support fixed and free joystick --- AKPlugin.swift | 27 ++- PlayTools/Controls/ControlMode.swift | 13 +- PlayTools/Controls/MenuController.swift | 67 +++++-- PlayTools/Controls/PlayAction.swift | 106 +++++++---- PlayTools/Controls/PlayController.swift | 121 ++++++++++++ PlayTools/Controls/PlayInput.swift | 224 +++++++--------------- PlayTools/Controls/PlayMice.swift | 237 ++++++++++-------------- PlayTools/Controls/Toucher.swift | 2 +- PlayTools/Utils/Toast.swift | 56 ++++++ Plugin.swift | 7 +- 10 files changed, 499 insertions(+), 361 deletions(-) create mode 100644 PlayTools/Controls/PlayController.swift diff --git a/AKPlugin.swift b/AKPlugin.swift index 3e12d5cf..ee9e72b1 100644 --- a/AKPlugin.swift +++ b/AKPlugin.swift @@ -8,7 +8,6 @@ import AppKit import CoreGraphics import Foundation -import GameController class AKPlugin: NSObject, Plugin { required override init() { @@ -63,8 +62,8 @@ class AKPlugin: NSObject, Plugin { } private var modifierFlag: UInt = 0 - func initialize(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, - swapMode: @escaping() -> Bool) { + func setupKeyboard(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, + swapMode: @escaping() -> Bool) { func checkCmd(modifier: NSEvent.ModifierFlags) -> Bool { if modifier.contains(.command) { self.cmdPressed = true @@ -113,7 +112,10 @@ class AKPlugin: NSObject, Plugin { } return event }) - let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged, .mouseMoved] + } + + func setupMouseMoved(mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) { + let mask: NSEvent.EventTypeMask = [.leftMouseDragged, .otherMouseDragged, .rightMouseDragged] NSEvent.addLocalMonitorForEvents(matching: mask, handler: { event in let consumed = mouseMoved(event.deltaX, event.deltaY) if consumed { @@ -121,21 +123,28 @@ class AKPlugin: NSObject, Plugin { } return event }) + // transpass mouse moved event when no button pressed, for traffic light button to light up + NSEvent.addLocalMonitorForEvents(matching: .mouseMoved, handler: { event in + _ = mouseMoved(event.deltaX, event.deltaY) + return event + }) } - func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool) -> Bool) { - NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask(rawValue: UInt64(_up)), handler: { event in + func setupMouseButton(left: Bool, right: Bool, _ dontIgnore: @escaping(Bool) -> Bool) { + let downType: NSEvent.EventTypeMask = left ? .leftMouseDown : right ? .rightMouseDown : .otherMouseDown + let upType: NSEvent.EventTypeMask = left ? .leftMouseUp : right ? .rightMouseUp : .otherMouseUp + NSEvent.addLocalMonitorForEvents(matching: downType, handler: { event in // For traffic light buttons when fullscreen if event.window != NSApplication.shared.windows.first! { return event } - if dontIgnore(_up, true) { + if dontIgnore(true) { return event } return nil }) - NSEvent.addLocalMonitorForEvents(matching: NSEvent.EventTypeMask(rawValue: UInt64(_down)), handler: { event in - if dontIgnore(_up, false) { + NSEvent.addLocalMonitorForEvents(matching: upType, handler: { event in + if dontIgnore(false) { return event } return nil diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index 15c16080..57862501 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -11,6 +11,16 @@ public class ControlMode { static public let mode = ControlMode() public var visible: Bool = true + public var keyboardMapped = true + + public static func trySwap() -> Bool { + if PlayInput.shouldLockCursor { + mode.show(!mode.visible) + return true + } + mode.show(true) + return false + } func show(_ show: Bool) { if !editor.editorMode { @@ -28,8 +38,7 @@ public class ControlMode { if screen.fullscreen { screen.switchDock(false) } - - PlayInput.shared.setup() +// PlayInput.shared.setup() } } Toucher.writeLog(logMessage: "cursor show switched to \(show)") diff --git a/PlayTools/Controls/MenuController.swift b/PlayTools/Controls/MenuController.swift index d5008a59..1da0786c 100644 --- a/PlayTools/Controls/MenuController.swift +++ b/PlayTools/Controls/MenuController.swift @@ -48,7 +48,7 @@ extension UIApplication { // put a mark in the toucher log, so as to align with tester description @objc func markToucherLog(_ sender: AnyObject) { - Toucher.writeLog(logMessage:"mark") + Toucher.writeLog(logMessage: "mark") Toast.showHint(title: "Log marked") } } @@ -68,28 +68,66 @@ extension UIViewController { struct CommandsList { static let KeymappingToolbox = "keymapping" } - -var keymapping = ["Open/Close Keymapping Editor", - "Delete selected element", - "Upsize selected element", - "Downsize selected element", - "Rotate display area", - "Put a mark in toucher log"] +// have to use a customized name, in case it conflicts with the game's localization file +var keymapping = [ + NSLocalizedString("menu.keymapping.toggleEditor", tableName: "Playtools", + value: "Open/Close Keymapping Editor", comment: ""), + NSLocalizedString("menu.keymapping.deleteElement", tableName: "Playtools", + value: "Delete selected element", comment: ""), + NSLocalizedString("menu.keymapping.upsizeElement", tableName: "Playtools", + value: "Upsize selected element", comment: ""), + NSLocalizedString("menu.keymapping.downsizeElement", tableName: "Playtools", + value: "Downsize selected element", comment: ""), + NSLocalizedString("menu.keymapping.rotateDisplay", tableName: "Playtools", + value: "Rotate display area", comment: "") + ] var keymappingSelectors = [#selector(UIApplication.switchEditorMode(_:)), #selector(UIApplication.removeElement(_:)), #selector(UIApplication.upscaleElement(_:)), #selector(UIApplication.downscaleElement(_:)), - #selector(UIViewController.rotateView(_:)), - #selector(UIApplication.markToucherLog)] + #selector(UIViewController.rotateView(_:)) + ] class MenuController { init(with builder: UIMenuBuilder) { + if Toucher.logEnabled { + builder.insertSibling(MenuController.debuggingMenu(), afterMenu: .view) + } builder.insertSibling(MenuController.keymappingMenu(), afterMenu: .view) } - class func keymappingMenu() -> UIMenu { - let keyCommands = [ "K", UIKeyCommand.inputDelete, UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R", "L"] + static func debuggingMenu() -> UIMenu { + let menuTitle = [ + "Put a mark in toucher log" + ] + let keyCommands = ["L"] + let selectors = [ + #selector(UIApplication.markToucherLog) + ] + let arrowKeyChildrenCommands = zip(keyCommands, menuTitle).map { (command, btn) in + UIKeyCommand(title: btn, + image: nil, + action: selectors[menuTitle.firstIndex(of: btn)!], + input: command, + modifierFlags: .command, + propertyList: [CommandsList.KeymappingToolbox: btn] + ) + } + return UIMenu(title: "Debug", + image: nil, + identifier: .debuggingMenu, + options: [], + children: [ + UIMenu(title: "", + image: nil, + identifier: .debuggingOptionsMenu, + options: .displayInline, + children: arrowKeyChildrenCommands)]) + } + class func keymappingMenu() -> UIMenu { + let keyCommands = [ "K", UIKeyCommand.inputDelete, + UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow, "R", "L"] let arrowKeyChildrenCommands = zip(keyCommands, keymapping).map { (command, btn) in UIKeyCommand(title: btn, image: nil, @@ -106,7 +144,8 @@ class MenuController { options: .displayInline, children: arrowKeyChildrenCommands) - return UIMenu(title: NSLocalizedString("Keymapping", comment: ""), + return UIMenu(title: NSLocalizedString("menu.keymapping", tableName: "Playtools", + value: "Keymapping", comment: ""), image: nil, identifier: .keymappingMenu, options: [], @@ -117,4 +156,6 @@ class MenuController { extension UIMenu.Identifier { static var keymappingMenu: UIMenu.Identifier { UIMenu.Identifier("io.playcover.PlayTools.menus.editor") } static var keymappingOptionsMenu: UIMenu.Identifier { UIMenu.Identifier("io.playcover.PlayTools.menus.keymapping") } + static var debuggingMenu: UIMenu.Identifier { UIMenu.Identifier("io.playcover.PlayTools.menus.debug") } + static var debuggingOptionsMenu: UIMenu.Identifier { UIMenu.Identifier("io.playcover.PlayTools.menus.debugging") } } diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index 855f82c5..2955bb5a 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -60,15 +60,17 @@ class DraggableButtonAction: ButtonAction { if pressed { Toucher.touchcam(point: point, phase: UITouch.Phase.began, tid: &id) self.releasePoint = point - PlayMice.shared.draggableHandler[keyName] = self.onMouseMoved + PlayInput.draggableHandler[keyName] = self.onMouseMoved + AKInterface.shared!.hideCursor() } else { - PlayMice.shared.draggableHandler.removeValue(forKey: keyName) + PlayInput.draggableHandler.removeValue(forKey: keyName) Toucher.touchcam(point: releasePoint, phase: UITouch.Phase.ended, tid: &id) + AKInterface.shared!.unhideCursor() } } override func invalidate() { - PlayMice.shared.draggableHandler.removeValue(forKey: keyName) + PlayInput.draggableHandler.removeValue(forKey: keyName) super.invalidate() } @@ -95,9 +97,9 @@ class ContinuousJoystickAction: Action { position = center self.sensitivity = data.transform.size.absoluteSize / 4 if key == PlayMice.elementName { - PlayMice.shared.joystickHandler[key] = self.mouseUpdate + PlayInput.joystickHandler[key] = self.mouseUpdate } else { - PlayMice.shared.joystickHandler[key] = self.thumbstickUpdate + PlayInput.joystickHandler[key] = self.thumbstickUpdate } } @@ -129,22 +131,22 @@ class ContinuousJoystickAction: Action { } func invalidate() { - PlayMice.shared.joystickHandler.removeValue(forKey: key) + PlayInput.joystickHandler.removeValue(forKey: key) } } class JoystickAction: Action { let keys: [Int] let center: CGPoint + var touch: CGPoint let shift: CGFloat var id: Int? - var moving = false - private var keyPressed = [Bool](repeating: false, count: 4) init(keys: [Int], center: CGPoint, shift: CGFloat) { self.keys = keys self.center = center - self.shift = shift / 2 + self.touch = center + self.shift = shift / 4 for index in 0.. (Bool) -> Void { - return { pressed in - self.keyPressed[index] = pressed - self.update() + // if the size of joystick is large, set control type to free, otherwise fixed. + // this is a temporary method. ideally should give the user an option. + if shift < 200 { + return { pressed in + self.updateTouch(index: index, pressed: pressed) + self.handleFixed() + } + } else { + return { pressed in + self.updateTouch(index: index, pressed: pressed) + self.handleFree() + } } } - func update() { - var touch = center - if keyPressed[0] { - touch.y -= shift / 2 - } else if keyPressed[1] { - touch.y += shift / 2 - } - if keyPressed[2] { - touch.x -= shift / 2 - } else if keyPressed[3] { - touch.x += shift / 2 + func updateTouch(index: Int, pressed: Bool) { + let isPlus = index & 1 != 0 + let realShift = isPlus ? shift : -shift + if index > 1 { + if pressed { + touch.x = center.x + realShift + } else { + touch.x = center.x + } + } else { + if pressed { + touch.y = center.y + realShift + } else { + touch.y = center.y + } } - if moving { - if touch.equalTo(center) { - moving = false + } + + func atCenter() -> Bool { + return (center.x - touch.x).magnitude + (center.y - touch.y).magnitude < 8 + } + + func handleCommon(_ begin: () -> Void) { + let moving = id != nil + if atCenter() { + if moving { Toucher.touchcam(point: touch, phase: UITouch.Phase.ended, tid: &id) - } else { - Toucher.touchcam(point: touch, phase: UITouch.Phase.moved, tid: &id) } } else { - if !touch.equalTo(center) { - moving = true - Toucher.touchcam(point: touch, phase: UITouch.Phase.began, tid: &id) - } // end if - } // end else + if moving { + Toucher.touchcam(point: touch, phase: UITouch.Phase.moved, tid: &id) + } else { + begin() + } + } + } + + func handleFree() { + handleCommon { + Toucher.touchcam(point: self.center, phase: UITouch.Phase.began, tid: &id) + PlayInput.touchQueue.asyncAfter(deadline: .now() + 0.04, qos: .userInitiated) { + if self.id == nil { + return + } + Toucher.touchcam(point: self.touch, phase: UITouch.Phase.moved, tid: &self.id) + } // end closure + } + } + + func handleFixed() { + handleCommon { + Toucher.touchcam(point: self.touch, phase: UITouch.Phase.began, tid: &id) + } } } diff --git a/PlayTools/Controls/PlayController.swift b/PlayTools/Controls/PlayController.swift new file mode 100644 index 00000000..64c641ec --- /dev/null +++ b/PlayTools/Controls/PlayController.swift @@ -0,0 +1,121 @@ +// +// PlayController.swift +// PlayTools +// +// Created by 许沂聪 on 2023/4/21. +// + +import Foundation +import GameController + +class PlayController { + private static var directionPadXValue: Float = 0, + directionPadYValue: Float = 0, + thumbstickCursorControl: [String: (((CGFloat, CGFloat) -> Void)?, CGFloat, CGFloat) -> Void] + = ["Left Thumbstick": ThumbstickCursorControl().update, "Right Thumbstick": ThumbstickCursorControl().update] + + public static func initialize() { + GCController.current?.extendedGamepad?.valueChangedHandler = handleEvent + } + + static func handleEditorEvent(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { + // This is the index of controller buttons, which is String, not Int + var alias: String = element.aliases.first! + if alias == "Direction Pad" { + guard let dpadElement = element as? GCControllerDirectionPad else { + Toast.showOver(msg: "cannot map direction pad: element type not recognizable") + return + } + if dpadElement.xAxis.value > 0 { + alias = dpadElement.right.aliases.first! + } else if dpadElement.xAxis.value < 0 { + alias = dpadElement.left.aliases.first! + } + if dpadElement.yAxis.value > 0 { + alias = dpadElement.down.aliases.first! + } else if dpadElement.yAxis.value < 0 { + alias = dpadElement.up.aliases.first! + } + } + EditorController.shared.setKey(alias) + } + + static func handleEvent(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { + let name: String = element.aliases.first! + if let buttonElement = element as? GCControllerButtonInput { + guard let handlers = PlayInput.buttonHandlers[name] else { return } +// Toast.showOver(msg: name + ": \(buttonElement.isPressed)") + for handler in handlers { + handler(buttonElement.isPressed) + } + } else if let dpadElement = element as? GCControllerDirectionPad { + PlayController.handleDirectionPad(profile, dpadElement) + } else { + Toast.showOver(msg: "unrecognised controller element input happens") + } + } + public static func handleDirectionPad(_ profile: GCExtendedGamepad, _ dpad: GCControllerDirectionPad) { + let name = dpad.aliases.first! + let xAxis = dpad.xAxis, yAxis = dpad.yAxis + if name == "Direction Pad" { + if (xAxis.value > 0) != (directionPadXValue > 0) { + PlayController.handleEvent(profile, dpad.right) + } + if (xAxis.value < 0) != (directionPadXValue < 0) { + PlayController.handleEvent(profile, dpad.left) + } + if (yAxis.value > 0) != (directionPadYValue > 0) { + PlayController.handleEvent(profile, dpad.up) + } + if (yAxis.value < 0) != (directionPadYValue < 0) { + PlayController.handleEvent(profile, dpad.down) + } + directionPadXValue = xAxis.value + directionPadYValue = yAxis.value + return + } + let deltaX = xAxis.value, deltaY = yAxis.value + let cgDx = CGFloat(deltaX) + let cgDy = CGFloat(deltaY) + thumbstickCursorControl[name]!( + PlayInput.draggableHandler[name] ?? PlayInput.cameraMoveHandler[name], cgDx * 6, cgDy * 6) + PlayInput.joystickHandler[name]?(cgDx, cgDy) + } +} + +class ThumbstickCursorControl { + private var thumbstickVelocity: CGVector = CGVector.zero, + thumbstickPolling: Bool = false, + eventHandler: ((CGFloat, CGFloat) -> Void)! + + static private func isVectorSignificant(_ vector: CGVector) -> Bool { + return vector.dx.magnitude + vector.dy.magnitude > 0.2 + } + + public func update(handler: ((CGFloat, CGFloat) -> Void)?, velocityX: CGFloat, velocityY: CGFloat) { + guard let hdlr = handler else { + if thumbstickPolling { + self.thumbstickVelocity.dx = 0 + self.thumbstickVelocity.dy = 0 + } + return + } + self.eventHandler = hdlr + self.thumbstickVelocity.dx = velocityX + self.thumbstickVelocity.dy = velocityY + if !thumbstickPolling { + PlayInput.touchQueue.async(execute: self.thumbstickPoll) + self.thumbstickPolling = true + } + } + + private func thumbstickPoll() { + if !ThumbstickCursorControl.isVectorSignificant(self.thumbstickVelocity) { + self.thumbstickPolling = false + return + } + self.eventHandler(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) + PlayInput.touchQueue.asyncAfter( + deadline: DispatchTime.now() + 0.017, execute: self.thumbstickPoll) + } +} diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 7e7a0625..79f4c789 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -5,12 +5,15 @@ import UIKit class PlayInput { static let shared = PlayInput() var actions = [Action]() - static var keyboardMapped = true static var shouldLockCursor = true static var touchQueue = DispatchQueue.init(label: "playcover.toucher", qos: .userInteractive, autoreleaseFrequency: .workItem) - static public var buttonHandlers: [String: [(Bool) -> Void]] = [:] + static public var buttonHandlers: [String: [(Bool) -> Void]] = [:], + draggableHandler: [String: (CGFloat, CGFloat) -> Void] = [:], + cameraMoveHandler: [String: (CGFloat, CGFloat) -> Void] = [:], + cameraScaleHandler: [String: (CGFloat, CGFloat) -> Void] = [:], + joystickHandler: [String: (CGFloat, CGFloat) -> Void] = [:] func invalidate() { for action in self.actions { @@ -19,7 +22,7 @@ class PlayInput { } static public func registerButton(key: String, handler: @escaping (Bool) -> Void) { - if ["LMB", "RMB", "MMB"].contains(key) { + if "LMB" == key { PlayInput.shouldLockCursor = true } if PlayInput.buttonHandlers[key] == nil { @@ -28,36 +31,6 @@ class PlayInput { PlayInput.buttonHandlers[key]!.append(handler) } - func keyboardHandler(_ keyCode: UInt16, _ pressed: Bool) -> Bool { - let name = KeyCodeNames.virtualCodes[keyCode] ?? "Btn" - guard let handlers = PlayInput.buttonHandlers[name] else { - return false - } - var mapped = false - for handler in handlers { - PlayInput.touchQueue.async(qos: .userInteractive, execute: { - handler(pressed) - }) - mapped = true - } - return mapped - } - - func controllerButtonHandler(_ profile: GCExtendedGamepad, _ element: GCControllerElement) { - let name: String = element.aliases.first! - if let buttonElement = element as? GCControllerButtonInput { - guard let handlers = PlayInput.buttonHandlers[name] else { return } -// Toast.showOver(msg: name + ": \(buttonElement.isPressed)") - for handler in handlers { - handler(buttonElement.isPressed) - } - } else if let dpadElement = element as? GCControllerDirectionPad { - PlayMice.shared.handleControllerDirectionPad(profile, dpadElement) - } else { - Toast.showOver(msg: "unrecognised controller element input happens") - } - } - func parseKeymap() { actions = [] PlayInput.shouldLockCursor = false @@ -88,90 +61,31 @@ class PlayInput { } public func toggleEditor(show: Bool) { - PlayInput.keyboardMapped = !show + mode.keyboardMapped = !show Toucher.writeLog(logMessage: "editor opened? \(show)") if show { self.invalidate() mode.show(show) if let keyboard = GCKeyboard.coalesced!.keyboardInput { - keyboard.keyChangedHandler = { _, _, keyCode, _ in - if !PlayInput.cmdPressed() - && !PlayInput.FORBIDDEN.contains(keyCode) - && self.isSafeToBind(keyboard) - && KeyCodeNames.keyCodes[keyCode.rawValue] != nil { - EditorController.shared.setKey(keyCode.rawValue) - } + keyboard.keyChangedHandler = { _, _, keyCode, pressed in + PlayKeyboard.handleEditorEvent(keyCode: keyCode, pressed: pressed) } } if let controller = GCController.current?.extendedGamepad { - controller.valueChangedHandler = { _, element in - // This is the index of controller buttons, which is String, not Int - var alias: String = element.aliases.first! - if alias == "Direction Pad" { - guard let dpadElement = element as? GCControllerDirectionPad else { - Toast.showOver(msg: "cannot map direction pad: element type not recognizable") - return - } - if dpadElement.xAxis.value > 0 { - alias = dpadElement.right.aliases.first! - } else if dpadElement.xAxis.value < 0 { - alias = dpadElement.left.aliases.first! - } - if dpadElement.yAxis.value > 0 { - alias = dpadElement.down.aliases.first! - } else if dpadElement.yAxis.value < 0 { - alias = dpadElement.up.aliases.first! - } - } - EditorController.shared.setKey(alias) - } + controller.valueChangedHandler = PlayController.handleEditorEvent } } else { - setup() + GCKeyboard.coalesced?.keyboardInput?.keyChangedHandler = nil + PlayController.initialize() parseKeymap() - _ = self.swapMode() + _ = ControlMode.trySwap() } } - func setup() { - GCKeyboard.coalesced?.keyboardInput?.keyChangedHandler = nil - GCController.current?.extendedGamepad?.valueChangedHandler = controllerButtonHandler - } - static public func cmdPressed() -> Bool { return AKInterface.shared!.cmdPressed } - private func isSafeToBind(_ input: GCKeyboardInput) -> Bool { - var result = true - for forbidden in PlayInput.FORBIDDEN where input.button(forKeyCode: forbidden)?.isPressed ?? false { - result = false - break - } - return result - } - - private static let FORBIDDEN: [GCKeyCode] = [ - .leftGUI, - .rightGUI, - .leftAlt, - .rightAlt, - .printScreen - ] - - private func swapMode() -> Bool { - if PlayInput.shouldLockCursor { - mode.show(!mode.visible) - return true - } - mode.show(true) - return false - } - - var root: UIViewController? { - return screen.window?.rootViewController - } - func initialize() { if !PlaySettings.shared.keymapping { return @@ -184,82 +98,82 @@ class PlayInput { if EditorController.shared.editorMode { self.toggleEditor(show: true) } else { - self.setup() + PlayController.initialize() } } parseKeymap() - centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - PlayInput.keyboardMapped = true - Toucher.writeLog(logMessage: "virtual keyboard did hide") - } - centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in - PlayInput.keyboardMapped = false - Toucher.writeLog(logMessage: "virtual keyboard will show") - } centre.addObserver(forName: NSNotification.Name(rawValue: "NSWindowDidBecomeKeyNotification"), object: nil, queue: main) { _ in if !mode.visible { AKInterface.shared!.warpCursor() } } - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { + DispatchQueue.main.asyncAfter(deadline: .now() + 5, qos: .utility) { if !mode.visible || self.actions.count <= 0 || !PlayInput.shouldLockCursor { return } - let persistenceKeyname = "playtoolsKeymappingDisabledAt" - let lastUse = UserDefaults.standard.float(forKey: persistenceKeyname) - var thisUse = lastUse - if lastUse < 1 { - thisUse = 2 - } else { - thisUse = Float(Date.timeIntervalSinceReferenceDate) - } - var token2: NSObjectProtocol? - let center = NotificationCenter.default - token2 = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillDisable, - object: nil, queue: OperationQueue.main) { _ in - center.removeObserver(token2!) - UserDefaults.standard.set(thisUse, forKey: persistenceKeyname) - } - if lastUse > Float(Date.now.addingTimeInterval(-86400*14).timeIntervalSinceReferenceDate) { - return - } - Toast.showHint(title: "Mouse Mapping Disabled", text: ["Press ", "option ⌥", " to enable mouse mapping"], - timeout: 10, - notification: NSNotification.Name.playtoolsKeymappingWillEnable) - var token: NSObjectProtocol? - token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, - object: nil, queue: OperationQueue.main) { _ in - center.removeObserver(token!) - Toast.showHint(title: "Cursor Locked", text: ["Press ", "option ⌥", " to release cursor"], - timeout: 10, - notification: NSNotification.Name.playtoolsKeymappingWillDisable) - } + Toast.initialize() + } + PlayKeyboard.initialize() + PlayMice.shared.initialize() + + } +} + +class PlayKeyboard { + public static func handleEditorEvent(keyCode: GCKeyCode, pressed: Bool) { + if !PlayInput.cmdPressed() + && !PlayKeyboard.FORBIDDEN.contains(keyCode) + && KeyCodeNames.keyCodes[keyCode.rawValue] != nil { + EditorController.shared.setKey(keyCode.rawValue) } + } - AKInterface.shared!.initialize(keyboard: {keycode, pressed, isRepeat in - if !PlayInput.keyboardMapped { + private static let FORBIDDEN: [GCKeyCode] = [ + .leftGUI, + .rightGUI, + .leftAlt, + .rightAlt, + .printScreen + ] + + static func handleEvent(_ keyCode: UInt16, _ pressed: Bool) -> Bool { + let name = KeyCodeNames.virtualCodes[keyCode] ?? "Btn" + guard let handlers = PlayInput.buttonHandlers[name] else { + return false + } + var mapped = false + for handler in handlers { + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + handler(pressed) + }) + mapped = true + } + return mapped + } + + public static func initialize() { + let centre = NotificationCenter.default + let main = OperationQueue.main + centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in + mode.keyboardMapped = true + Toucher.writeLog(logMessage: "virtual keyboard did hide") + } + centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in + mode.keyboardMapped = false + Toucher.writeLog(logMessage: "virtual keyboard will show") + } + AKInterface.shared!.setupKeyboard(keyboard: {keycode, pressed, isRepeat in + if !mode.keyboardMapped { // explicitly ignore repeated Enter key return isRepeat && keycode == 36 } if isRepeat { return true } - let mapped = self.keyboardHandler(keycode, pressed) + let mapped = PlayKeyboard.handleEvent(keycode, pressed) return mapped - }, mouseMoved: {deltaX, deltaY in - if !PlayInput.keyboardMapped { - return false - } - PlayInput.touchQueue.async(qos: .userInteractive, execute: { - if mode.visible { - PlayMice.shared.handleFakeMouseMoved(deltaX: deltaX, deltaY: deltaY) - } else { - PlayMice.shared.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) - } - }) - return true - }, swapMode: self.swapMode) - PlayMice.shared.initialize() + }, + swapMode: ControlMode.trySwap) } } diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index a32a1a20..477293c7 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -4,7 +4,6 @@ // import Foundation -import GameController public class PlayMice { @@ -12,25 +11,34 @@ public class PlayMice { public static let elementName = "Mouse" public func initialize() { - setupMouseButton(_up: 2, _down: 4) - setupMouseButton(_up: 8, _down: 16) - setupMouseButton(_up: 33554432, _down: 67108864) - setupScrollWheelHandler() + setupLeftButton() + setupMouseButton(right: true) + setupMouseButton(right: false) + AKInterface.shared!.setupMouseMoved(mouseMoved: {deltaX, deltaY in + // this closure's return value only takes effect when any mouse button pressed + if !mode.keyboardMapped { + return false + } + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + self.handleMouseMoved(deltaX: deltaX, deltaY: deltaY) + }) + return true + }) + AKInterface.shared!.setupScrollWheel({deltaX, deltaY in + if let cameraScale = PlayInput.cameraScaleHandler[PlayMice.elementName] { + cameraScale(deltaX, deltaY) + let eventConsumed = !mode.visible + return eventConsumed + } + return false + }) } var fakedMouseTouchPointId: Int? var fakedMousePressed: Bool {fakedMouseTouchPointId != nil} - private var directionPadXValue: Float = 0, - directionPadYValue: Float = 0, - thumbstickCursorControl: [String: (((CGFloat, CGFloat) -> Void)?, CGFloat, CGFloat) -> Void] - = ["Left Thumbstick": ThumbstickCursorControl().update, "Right Thumbstick": ThumbstickCursorControl().update] - public var draggableHandler: [String: (CGFloat, CGFloat) -> Void] = [:], - cameraMoveHandler: [String: (CGFloat, CGFloat) -> Void] = [:], - cameraScaleHandler: [String: (CGFloat, CGFloat) -> Void] = [:], - joystickHandler: [String: (CGFloat, CGFloat) -> Void] = [:] public func mouseMovementMapped() -> Bool { - for handler in [draggableHandler, cameraMoveHandler, joystickHandler] + for handler in [PlayInput.cameraMoveHandler, PlayInput.joystickHandler] where handler[PlayMice.elementName] != nil { return true } @@ -66,136 +74,90 @@ public class PlayMice { return point } - public func setupScrollWheelHandler() { - AKInterface.shared!.setupScrollWheel({deltaX, deltaY in - if let cameraScale = self.cameraScaleHandler[PlayMice.elementName] { - cameraScale(deltaX, deltaY) - let eventConsumed = !mode.visible - return eventConsumed - } - return false - }) - } - - public func handleControllerDirectionPad(_ profile: GCExtendedGamepad, _ dpad: GCControllerDirectionPad) { - let name = dpad.aliases.first! - let xAxis = dpad.xAxis, yAxis = dpad.yAxis - if name == "Direction Pad" { - if (xAxis.value > 0) != (directionPadXValue > 0) { - PlayInput.shared.controllerButtonHandler(profile, dpad.right) - } - if (xAxis.value < 0) != (directionPadXValue < 0) { - PlayInput.shared.controllerButtonHandler(profile, dpad.left) - } - if (yAxis.value > 0) != (directionPadYValue > 0) { - PlayInput.shared.controllerButtonHandler(profile, dpad.up) - } - if (yAxis.value < 0) != (directionPadYValue < 0) { - PlayInput.shared.controllerButtonHandler(profile, dpad.down) - } - directionPadXValue = xAxis.value - directionPadYValue = yAxis.value - return - } - let deltaX = xAxis.value, deltaY = yAxis.value - let cgDx = CGFloat(deltaX) - let cgDy = CGFloat(deltaY) - thumbstickCursorControl[name]!(draggableHandler[name] ?? cameraMoveHandler[name], cgDx * 6, cgDy * 6) - joystickHandler[name]?(cgDx, cgDy) - } - - public func handleFakeMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { - if self.fakedMousePressed { - if let pos = self.cursorPos() { - Toucher.touchcam(point: pos, phase: UITouch.Phase.moved, tid: &fakedMouseTouchPointId) - } - } - } - public func handleMouseMoved(deltaX: CGFloat, deltaY: CGFloat) { let sensy = CGFloat(PlaySettings.shared.sensitivity * 0.6) let cgDx = deltaX * sensy, cgDy = -deltaY * sensy let name = PlayMice.elementName - if let draggableUpdate = self.draggableHandler[name] { + if let draggableUpdate = PlayInput.draggableHandler[name] { draggableUpdate(cgDx, cgDy) - return + } else if mode.visible { + if self.fakedMousePressed { + if let pos = self.cursorPos() { + Toucher.touchcam(point: pos, phase: UITouch.Phase.moved, tid: &fakedMouseTouchPointId) + } + } + } else { + PlayInput.cameraMoveHandler[name]?(cgDx, cgDy) + PlayInput.joystickHandler[name]?(cgDx, cgDy) } - self.cameraMoveHandler[name]?(cgDx, cgDy) - self.joystickHandler[name]?(cgDx, cgDy) } - // TODO: get rid of this shit - let buttonIndex: [Int: Int] = [2: -1, 8: -2, 33554432: -3] - - private func setupMouseButton(_up: Int, _down: Int) { - AKInterface.shared!.setupMouseButton(_up, _down, dontIgnore) - } - - private func dontIgnore(_ actionIndex: Int, _ pressed: Bool) -> Bool { - if !PlayInput.keyboardMapped { - if EditorController.shared.editorMode && actionIndex != 2 && pressed { - EditorController.shared.setKey(buttonIndex[actionIndex]!) + private func setupMouseButton(right: Bool) { + let keyCode = right ? -2 : -3 + guard let keyName = KeyCodeNames.keyCodes[keyCode] else { + Toast.showHint(title: "Failed initializing \(right ? "right" : "other") mouse button input") + return + } + AKInterface.shared!.setupMouseButton(left: false, right: right) {pressed in + if mode.keyboardMapped { // if mapping + if let handlers = PlayInput.buttonHandlers[keyName] { + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + for handler in handlers { + handler(pressed) + } + }) + // if mapped to any button, consumed and dispatch + return false + } + // if not mapped, transpass to app + return true + } else if EditorController.shared.editorMode { // if editor is open, consumed and set button + if pressed { + // asynced to return quickly. this branch contains UI operation so main queue. + // main queue is fine. should not be slower than keyboard + DispatchQueue.main.async(qos: .userInteractive, execute: { + EditorController.shared.setKey(keyCode) + Toucher.writeLog(logMessage: "mouse button editor set") + }) + } + return false + } else { // if typing, transpass event to app + Toucher.writeLog(logMessage: "mouse button pressed? \(pressed)") + return true } - Toucher.writeLog(logMessage: "mouse button pressed? \(pressed)") - return true } - guard let curPos = self.cursorPos() else { return true } - PlayInput.touchQueue.async(qos: .userInteractive, execute: { - if self.fakedMousePressed { - Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &self.fakedMouseTouchPointId) - return + } + // using high priority event handlers to prevent lag and stutter in demanding games + // but no free lunch. high priority handlers cannot execute for too long + // exceeding the time limit causes even more lag + private func setupLeftButton() { + AKInterface.shared!.setupMouseButton(left: true, right: false) {pressed in + if !mode.keyboardMapped { + Toucher.writeLog(logMessage: "left button pressed? \(pressed)") + return true } - if mode.visible && pressed { - Toucher.touchcam(point: curPos, - phase: UITouch.Phase.began, - tid: &self.fakedMouseTouchPointId) - } else { - if let handlers = PlayInput.buttonHandlers[KeyCodeNames.keyCodes[self.buttonIndex[actionIndex]!]!] { + guard let curPos = self.cursorPos() else { return true } + PlayInput.touchQueue.async(qos: .userInteractive, execute: { + if self.fakedMousePressed { + Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &self.fakedMouseTouchPointId) + return + } + if mode.visible && pressed { + Toucher.touchcam(point: curPos, + phase: UITouch.Phase.began, + tid: &self.fakedMouseTouchPointId) + return + } + if let handlers = PlayInput.buttonHandlers["LMB"] { for handler in handlers { handler(pressed) } + return } - } - }) - return false - } -} - -class ThumbstickCursorControl { - private var thumbstickVelocity: CGVector = CGVector.zero, - thumbstickPolling: Bool = false, - eventHandler: ((CGFloat, CGFloat) -> Void)! - - static private func isVectorSignificant(_ vector: CGVector) -> Bool { - return vector.dx.magnitude + vector.dy.magnitude > 0.2 - } - - public func update(handler: ((CGFloat, CGFloat) -> Void)?, velocityX: CGFloat, velocityY: CGFloat) { - guard let hdlr = handler else { - if thumbstickPolling { - self.thumbstickVelocity.dx = 0 - self.thumbstickVelocity.dy = 0 - } - return - } - self.eventHandler = hdlr - self.thumbstickVelocity.dx = velocityX - self.thumbstickVelocity.dy = velocityY - if !thumbstickPolling { - DispatchQueue.main.async(execute: self.thumbstickPoll) - self.thumbstickPolling = true - } - } - - private func thumbstickPoll() { - if !ThumbstickCursorControl.isVectorSignificant(self.thumbstickVelocity) { - self.thumbstickPolling = false - return + }) + return false } - self.eventHandler(self.thumbstickVelocity.dx, self.thumbstickVelocity.dy) - DispatchQueue.main.asyncAfter( - deadline: DispatchTime.now() + 0.017, execute: self.thumbstickPoll) } } @@ -213,8 +175,8 @@ class CameraAction: Action { swipeMove = SwipeAction() swipeScale1 = SwipeAction() swipeScale2 = SwipeAction() - PlayMice.shared.cameraMoveHandler[key] = self.moveUpdated - PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = {deltaX, deltaY in + PlayInput.cameraMoveHandler[key] = self.moveUpdated + PlayInput.cameraScaleHandler[PlayMice.elementName] = {deltaX, deltaY in PlayInput.touchQueue.async(qos: .userInteractive, execute: { if mode.visible { CameraAction.dragUpdated(deltaX, deltaY) @@ -250,21 +212,12 @@ class CameraAction: Action { } func invalidate() { - PlayMice.shared.cameraMoveHandler.removeValue(forKey: key) - PlayMice.shared.cameraScaleHandler[PlayMice.elementName] = nil + PlayInput.cameraMoveHandler.removeValue(forKey: key) + PlayInput.cameraScaleHandler[PlayMice.elementName] = nil swipeMove.invalidate() swipeScale1.invalidate() swipeScale2.invalidate() } - - func debug() { - var count = 0 - for swipe in [swipeScale1, swipeScale2, swipeMove, CameraAction.swipeDrag] { - count += 1 - guard let id = swipe.getTouchId() else {continue} - Toast.showHint(title: "type:\(count), id:\(id)") - } - } } class SwipeAction: Action { @@ -333,8 +286,4 @@ class SwipeAction: Action { timer.cancel() self.doLiftOff() } - - public func getTouchId() -> Int? { - return id - } } diff --git a/PlayTools/Controls/Toucher.swift b/PlayTools/Controls/Toucher.swift index cc949669..a4dce82d 100644 --- a/PlayTools/Controls/Toucher.swift +++ b/PlayTools/Controls/Toucher.swift @@ -10,7 +10,7 @@ class Toucher { static weak var keyWindow: UIWindow? static weak var keyView: UIView? // For debug only - static var logEnabled = true + static var logEnabled = false static var logFilePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/toucher.log" static private var logCount = 0 diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 2832a3a5..7133d5a5 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -112,6 +112,62 @@ class Toast { } } + static func syncUserDefaults() -> Float { + let persistenceKeyname = "playtoolsKeymappingDisabledAt" + let lastUse = UserDefaults.standard.float(forKey: persistenceKeyname) + var thisUse = lastUse + if lastUse < 1 { + thisUse = 2 + } else { + thisUse = Float(Date.timeIntervalSinceReferenceDate) + } + var token2: NSObjectProtocol? + let center = NotificationCenter.default + token2 = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillDisable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token2!) + UserDefaults.standard.set(thisUse, forKey: persistenceKeyname) + } + return lastUse + } + + public static func initialize() { + let lastUse = syncUserDefaults() + if lastUse > Float(Date.now.addingTimeInterval(-86400*14).timeIntervalSinceReferenceDate) { + return + } + Toast.showHint(title: NSLocalizedString("hint.enableKeymapping.title", + tableName: "Playtools", + value: "Keymapping Disabled", comment: ""), + text: [NSLocalizedString("hint.enableKeymapping.content.before", + tableName: "Playtools", + value: "Press", comment: ""), + " option ⌥ ", + NSLocalizedString("hint.enableKeymapping.content.after", + tableName: "Playtools", + value: "to enable keymapping", comment: "")], + timeout: 10, + notification: NSNotification.Name.playtoolsKeymappingWillEnable) + let center = NotificationCenter.default + var token: NSObjectProtocol? + token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, + object: nil, queue: OperationQueue.main) { _ in + center.removeObserver(token!) + Toast.showHint(title: NSLocalizedString("hint.disableKeymapping.title", + tableName: "Playtools", + value: "Keymapping Enabled", comment: ""), + text: [NSLocalizedString("hint.disableKeymapping.content.before", + tableName: "Playtools", + value: "Press", comment: ""), + " option ⌥ ", + NSLocalizedString("hint.disableKeymapping.content.after", + tableName: "Playtools", + value: "to disable keymapping", comment: "")], + timeout: 10, + notification: NSNotification.Name.playtoolsKeymappingWillDisable) + } + } + // swiftlint:disable function_body_length private static func show(message: String, parent: UIView) { diff --git a/Plugin.swift b/Plugin.swift index 291052b9..56bbe84d 100644 --- a/Plugin.swift +++ b/Plugin.swift @@ -23,9 +23,10 @@ public protocol Plugin: NSObjectProtocol { func warpCursor() func unhideCursor() func terminateApplication() - func initialize(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, mouseMoved: @escaping(CGFloat, CGFloat) -> Bool, - swapMode: @escaping() -> Bool) - func setupMouseButton(_ _up: Int, _ _down: Int, _ dontIgnore: @escaping(Int, Bool) -> Bool) + func setupKeyboard(keyboard: @escaping(UInt16, Bool, Bool) -> Bool, + swapMode: @escaping() -> Bool) + func setupMouseMoved(mouseMoved: @escaping(CGFloat, CGFloat) -> Bool) + func setupMouseButton(left: Bool, right: Bool, _ dontIgnore: @escaping(Bool) -> Bool) func setupScrollWheel(_ onMoved: @escaping(CGFloat, CGFloat) -> Bool) func urlForApplicationWithBundleIdentifier(_ value: String) -> URL? func setMenuBarVisible(_ value: Bool) From 6250bb8d2c840f818c4b60a3ff0fe15acfbdb95b Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 29 Apr 2023 00:09:43 +0800 Subject: [PATCH 49/54] create file PlayController --- PlayTools.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PlayTools.xcodeproj/project.pbxproj b/PlayTools.xcodeproj/project.pbxproj index e9c1825c..7bdd955b 100644 --- a/PlayTools.xcodeproj/project.pbxproj +++ b/PlayTools.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 6E84A14528D0F94E00BF7495 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA818CBA287ABFD5000BEE9D /* UIKit.framework */; }; 6E84A15028D0F97500BF7495 /* AKInterface.bundle in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 951D8275299D097C00D35B20 /* Playtools.strings in Resources */ = {isa = PBXBuildFile; fileRef = 951D8277299D097C00D35B20 /* Playtools.strings */; }; + 95A553E729F2BBB200E34C26 /* PlayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A553E629F2BBB200E34C26 /* PlayController.swift */; }; AA71970B287A44D200623C15 /* PlayLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = AA719702287A44D200623C15 /* PlayLoader.m */; }; AA71970D287A44D200623C15 /* PlaySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA719704287A44D200623C15 /* PlaySettings.swift */; }; AA71970E287A44D200623C15 /* PlayLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = AA719705287A44D200623C15 /* PlayLoader.h */; }; @@ -78,6 +79,7 @@ 6E84A14C28D0F96D00BF7495 /* AKInterface.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AKInterface.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 951D8276299D097C00D35B20 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Playtools.strings; sourceTree = ""; }; 951D8278299D098000D35B20 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Playtools.strings"; sourceTree = ""; }; + 95A553E629F2BBB200E34C26 /* PlayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayController.swift; sourceTree = ""; }; AA7196D8287A447700623C15 /* PlayTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlayTools.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AA719702287A44D200623C15 /* PlayLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlayLoader.m; sourceTree = ""; }; AA719704287A44D200623C15 /* PlaySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaySettings.swift; sourceTree = ""; }; @@ -202,6 +204,7 @@ AA719755287A480C00623C15 /* PlayInput.swift */, AA719756287A480C00623C15 /* ControlMode.swift */, AA719757287A480C00623C15 /* MenuController.swift */, + 95A553E629F2BBB200E34C26 /* PlayController.swift */, ); path = Controls; sourceTree = ""; @@ -449,6 +452,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 95A553E729F2BBB200E34C26 /* PlayController.swift in Sources */, AA71975A287A480D00623C15 /* Toucher.swift in Sources */, AA7197A1287A481500623C15 /* CircleMenuLoader.swift in Sources */, 6E76639B28D0FAE700DE4AF9 /* Plugin.swift in Sources */, From abd7e75dc25e04c917639142e88be7d06e46dec0 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 29 Apr 2023 03:46:08 +0800 Subject: [PATCH 50/54] fix text mismatch --- PlayTools/Utils/Toast.swift | 20 ++++++++++---------- PlayTools/en.lproj/Playtools.strings | Bin 3000 -> 3348 bytes PlayTools/zh-Hans.lproj/Playtools.strings | Bin 2924 -> 2622 bytes 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 7133d5a5..1e69ee04 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -136,16 +136,16 @@ class Toast { if lastUse > Float(Date.now.addingTimeInterval(-86400*14).timeIntervalSinceReferenceDate) { return } - Toast.showHint(title: NSLocalizedString("hint.enableKeymapping.title", + Toast.showHint(title: NSLocalizedString("hint.mouseMapping.title", tableName: "Playtools", - value: "Keymapping Disabled", comment: ""), - text: [NSLocalizedString("hint.enableKeymapping.content.before", + value: "Mouse mapping disabled", comment: ""), + text: [NSLocalizedString("hint.mouseMapping.content.before", tableName: "Playtools", value: "Press", comment: ""), " option ⌥ ", - NSLocalizedString("hint.enableKeymapping.content.after", + NSLocalizedString("hint.mouseMapping.content.after", tableName: "Playtools", - value: "to enable keymapping", comment: "")], + value: "to enable mouse mapping", comment: "")], timeout: 10, notification: NSNotification.Name.playtoolsKeymappingWillEnable) let center = NotificationCenter.default @@ -153,16 +153,16 @@ class Toast { token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, object: nil, queue: OperationQueue.main) { _ in center.removeObserver(token!) - Toast.showHint(title: NSLocalizedString("hint.disableKeymapping.title", + Toast.showHint(title: NSLocalizedString("hint.showCursor.title", tableName: "Playtools", - value: "Keymapping Enabled", comment: ""), - text: [NSLocalizedString("hint.disableKeymapping.content.before", + value: "Cursor locked", comment: ""), + text: [NSLocalizedString("hint.showCursor.content.before", tableName: "Playtools", value: "Press", comment: ""), " option ⌥ ", - NSLocalizedString("hint.disableKeymapping.content.after", + NSLocalizedString("hint.showCursor.content.after", tableName: "Playtools", - value: "to disable keymapping", comment: "")], + value: "to unlock cursor", comment: "")], timeout: 10, notification: NSNotification.Name.playtoolsKeymappingWillDisable) } diff --git a/PlayTools/en.lproj/Playtools.strings b/PlayTools/en.lproj/Playtools.strings index 1e82629eb58fd99c06b0626d339ec84318dafe43..0311d2a53e281d84fa4d99be96f93d92df923b5b 100644 GIT binary patch literal 3348 zcmd6p!EPEc5QgWRr?5EY01r^*RC21)R*8D+Wg$RNU_;s6MD*d?e!q=tFWLYXAVRx% z?eX}ZnSW;d_s_tt&DmFb;XUTPYQ9&tX6DuA_Q>2LGb7vZ*7}UulF+FW7$J~rZ`WGX=eHCEtAa z-xaoB3TKUHYafk-Dvz;vRKT6u$3?t3J<`O7JAd}dvuAcB-vYZXv>M{*G zgd}wU)e)g{{w;>BSg-t7$g4IZf~H_+WYP%f()Y2TDe@~YQ=LV*EOy0imvEfEh8>Pc z7t_9gbvKgE@>rf1`2!Jr_5Mqo$uT<1B~H_6Lw9ee{#`!p#ue-Jsl2rxQy*3a_KAD9 zo`>;{lJE{Q8Ru_5ZN3H#Y2PKhD@%0iD}8SM-fK-;?&DgdTd=y2Vl$+MRMegY-|FD; z&U&B8IA5!hRH}=YVwWo2jGy?@t_>0YH*C_4X25-YfpoZM=b0Nb_4=wu)LojirjLRx_#pP`6BfgzhAm7x+yCjn*i7*ZHw81g0?vgym_GZX-2@_;fR@dAcY Xh7uq<7c5pfS&%al>aECu#r$3 diff --git a/PlayTools/zh-Hans.lproj/Playtools.strings b/PlayTools/zh-Hans.lproj/Playtools.strings index 393a22385186c07ad2d48215bd8ae8d7df08caa1..11b93063807edc81d941ed4b67adca9ee03171c7 100644 GIT binary patch delta 571 zcmaDOwohb241Y00217nWIfFApDMJxM@#KRnq7(bX&SZdj@-Q#}uCb zI}3gXT9+nGcMepjop>$@o5sm4%o1$9fr{Cj$v_ptXhs9ADVi+LBF^_IHZ*yAGt6Y5 zO72{sTq#2_Ln?#s#6n}R<;Y5S{_BTml=eiIq$;Gb#9(NUpWMbQrtx9|TM$Tke$d|r zhUv)KeUtt+$d`Jg#xUeDfL(@c-{jwn%6uTVfb?pm_e5s|PIh2f5CrmWRaI@7uYX?s zQ$OjTUvV42wu210mb4;>GkM*Vr+#13EK|z@L24j|bNhWxxL7wUx}^*hfFPDZ5a$G4 zKalFleXKM1IFt3Jf@~72pV*%f1{BJhyp2r+F8nF>u75=I#B&jo6}aRkN3gHsQ3y(z z=8(iW0TS#WafQiiI5c=P>-l5Q#p^g#luoDS^xLIbRf3|5oXJwzMYEfD8(%Pc!&yx$wJ&JB6g|jkuZCd7_1q%fDiyY)W>oF literal 2924 zcmd5;%Wi`(5ZpsA%~x1CRXM;1R6R90Rcfm~Zn+R3G)hWPfK=&!JDyllDbUVjgPyG{Btl<=o|)`Way&tAidqaC4}Bx? zWgtU&)BOedBGhsTFlPm;JG?ur9bnZ=+a$1|NBXKfdKjJS)&?snxxqU`o7kGGDOoz! z8_G2>41lChdzu019DCT{Tkq;ow{t|OAE*nQNuf`w#^^dTB@>*OIn}5G^yG%|=R$NE zi@bfD_%Byw`_cYCUN=K7!#-ej@!~S{_G*4H9d#5@SFTUAB2XKsKTRE}8GH*yFEF}< zf&_SHQiA22m7-NC3zd!BSGvsc%`l$p*5Y7EFGh2?3bikiDaxh?t{Ow-GOS0X40M*$ zbLDG|5%Pn+Qq+k?9Td0hAeW9u(skahtfLN8abs-SXR3|-8FAerj#M9WOV}8d*cR>F z3DqrGf_pYHbFUOpTY&(}&xU5&vgJ6lc8bfYN8@}&gcV+?z>Y))s_4@Cd<2><{9 From 8e3c9510c1b98a18cdbd4faf99f0eadf0f387e7d Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sat, 6 May 2023 13:55:53 +0800 Subject: [PATCH 51/54] fix joystick opposite direction --- PlayTools/Controls/PlayAction.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/PlayTools/Controls/PlayAction.swift b/PlayTools/Controls/PlayAction.swift index 2955bb5a..cc49f5d7 100644 --- a/PlayTools/Controls/PlayAction.swift +++ b/PlayTools/Controls/PlayAction.swift @@ -141,7 +141,7 @@ class JoystickAction: Action { var touch: CGPoint let shift: CGFloat var id: Int? - + private var keyPressed = [Bool](repeating: false, count: 4) init(keys: [Int], center: CGPoint, shift: CGFloat) { self.keys = keys self.center = center @@ -189,17 +189,22 @@ class JoystickAction: Action { } func updateTouch(index: Int, pressed: Bool) { + self.keyPressed[index] = pressed let isPlus = index & 1 != 0 let realShift = isPlus ? shift : -shift if index > 1 { if pressed { touch.x = center.x + realShift + } else if self.keyPressed[index ^ 1] { + touch.x = center.x - realShift } else { touch.x = center.x } } else { if pressed { touch.y = center.y + realShift + } else if self.keyPressed[index ^ 1] { + touch.y = center.y - realShift } else { touch.y = center.y } From 0268d34b0a4f5ed95ada55646f010804c62eb013 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Sun, 7 May 2023 11:32:58 +0700 Subject: [PATCH 52/54] fix: make the Toast non-editable --- PlayTools/Utils/Toast.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index 1e69ee04..e8910884 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -83,6 +83,10 @@ class Toast { messageLabel.center.x = parent.center.x messageLabel.center.y = -messageLabel.frame.size.height / 2 + // Disable editing + messageLabel.isEditable = false + messageLabel.isSelectable = false + hintView.append(messageLabel) parent.addSubview(messageLabel) From 2636736b00c88b13aaa450cd7c658339e3ac5e51 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sun, 7 May 2023 05:21:53 +0800 Subject: [PATCH 53/54] invalidate when disable keyboard mapping --- PlayTools/Controls/ControlMode.swift | 23 +++++++++++++++++++++-- PlayTools/Controls/PlayInput.swift | 10 +++++----- PlayTools/Controls/PlayMice.swift | 9 ++++++++- PlayTools/Keymap/EditorController.swift | 2 +- PlayTools/Utils/Toast.swift | 8 ++++---- 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index 57862501..e4b4f5b8 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -22,23 +22,34 @@ public class ControlMode { return false } + func setMapping(_ mapped: Bool) { + keyboardMapped = mapped + if mapped { + PlayInput.shared.parseKeymap() + } else { + PlayInput.shared.invalidate() + } + } + func show(_ show: Bool) { if !editor.editorMode { if show { if !visible { + NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillShow, + object: nil, userInfo: [:]) if screen.fullscreen { screen.switchDock(true) } AKInterface.shared!.unhideCursor() -// PlayInput.shared.invalidate() } } else { if visible { + NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillHide, + object: nil, userInfo: [:]) AKInterface.shared!.hideCursor() if screen.fullscreen { screen.switchDock(false) } -// PlayInput.shared.setup() } } Toucher.writeLog(logMessage: "cursor show switched to \(show)") @@ -46,3 +57,11 @@ public class ControlMode { } } } + +extension NSNotification.Name { + public static let playtoolsCursorWillHide: NSNotification.Name + = NSNotification.Name("playtools.cursorWillHide") + + public static let playtoolsCursorWillShow: NSNotification.Name + = NSNotification.Name("playtools.cursorWillShow") +} diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index 79f4c789..e8a1ad4c 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -32,9 +32,9 @@ class PlayInput { } func parseKeymap() { - actions = [] - PlayInput.shouldLockCursor = false + actions = [PlayMice.shared] PlayInput.buttonHandlers.removeAll(keepingCapacity: true) + PlayInput.shouldLockCursor = false for button in keymap.keymapData.buttonModels { actions.append(ButtonAction(data: button)) } @@ -61,7 +61,7 @@ class PlayInput { } public func toggleEditor(show: Bool) { - mode.keyboardMapped = !show + mode.setMapping(!show) Toucher.writeLog(logMessage: "editor opened? \(show)") if show { self.invalidate() @@ -156,11 +156,11 @@ class PlayKeyboard { let centre = NotificationCenter.default let main = OperationQueue.main centre.addObserver(forName: UIApplication.keyboardDidHideNotification, object: nil, queue: main) { _ in - mode.keyboardMapped = true + mode.setMapping(true) Toucher.writeLog(logMessage: "virtual keyboard did hide") } centre.addObserver(forName: UIApplication.keyboardWillShowNotification, object: nil, queue: main) { _ in - mode.keyboardMapped = false + mode.setMapping(false) Toucher.writeLog(logMessage: "virtual keyboard will show") } AKInterface.shared!.setupKeyboard(keyboard: {keycode, pressed, isRepeat in diff --git a/PlayTools/Controls/PlayMice.swift b/PlayTools/Controls/PlayMice.swift index 477293c7..487090db 100644 --- a/PlayTools/Controls/PlayMice.swift +++ b/PlayTools/Controls/PlayMice.swift @@ -5,7 +5,7 @@ import Foundation -public class PlayMice { +public class PlayMice: Action { public static let shared = PlayMice() public static let elementName = "Mouse" @@ -139,6 +139,7 @@ public class PlayMice { } guard let curPos = self.cursorPos() else { return true } PlayInput.touchQueue.async(qos: .userInteractive, execute: { + // considering cases where cursor becomes hidden while holding left button if self.fakedMousePressed { Toucher.touchcam(point: curPos, phase: UITouch.Phase.ended, tid: &self.fakedMouseTouchPointId) return @@ -149,6 +150,7 @@ public class PlayMice { tid: &self.fakedMouseTouchPointId) return } + // considering cases where cursor becomes visible while holding left button if let handlers = PlayInput.buttonHandlers["LMB"] { for handler in handlers { handler(pressed) @@ -159,6 +161,11 @@ public class PlayMice { return false } } + // For all other actions, this is a destructor. should release held resources. + func invalidate() { + Toucher.touchcam(point: self.cursorPos() ?? CGPoint(x: 10, y: 10), + phase: UITouch.Phase.ended, tid: &self.fakedMouseTouchPointId) + } } class CameraAction: Action { diff --git a/PlayTools/Keymap/EditorController.swift b/PlayTools/Keymap/EditorController.swift index 43af3eaf..3b44ee66 100644 --- a/PlayTools/Keymap/EditorController.swift +++ b/PlayTools/Keymap/EditorController.swift @@ -74,7 +74,7 @@ class EditorController { tableName: "Playtools", value: "Click a button to edit its position or key bind\n" + "Click an empty area to open input menu", comment: "")], - notification: NSNotification.Name.playtoolsKeymappingWillEnable) + notification: NSNotification.Name.playtoolsCursorWillHide) } // Toast.showOver(msg: "\(UIApplication.shared.windows.count)") lock.unlock() diff --git a/PlayTools/Utils/Toast.swift b/PlayTools/Utils/Toast.swift index e8910884..e84bfaa4 100644 --- a/PlayTools/Utils/Toast.swift +++ b/PlayTools/Utils/Toast.swift @@ -127,7 +127,7 @@ class Toast { } var token2: NSObjectProtocol? let center = NotificationCenter.default - token2 = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillDisable, + token2 = center.addObserver(forName: NSNotification.Name.playtoolsCursorWillShow, object: nil, queue: OperationQueue.main) { _ in center.removeObserver(token2!) UserDefaults.standard.set(thisUse, forKey: persistenceKeyname) @@ -151,10 +151,10 @@ class Toast { tableName: "Playtools", value: "to enable mouse mapping", comment: "")], timeout: 10, - notification: NSNotification.Name.playtoolsKeymappingWillEnable) + notification: NSNotification.Name.playtoolsCursorWillHide) let center = NotificationCenter.default var token: NSObjectProtocol? - token = center.addObserver(forName: NSNotification.Name.playtoolsKeymappingWillEnable, + token = center.addObserver(forName: NSNotification.Name.playtoolsCursorWillHide, object: nil, queue: OperationQueue.main) { _ in center.removeObserver(token!) Toast.showHint(title: NSLocalizedString("hint.showCursor.title", @@ -168,7 +168,7 @@ class Toast { tableName: "Playtools", value: "to unlock cursor", comment: "")], timeout: 10, - notification: NSNotification.Name.playtoolsKeymappingWillDisable) + notification: NSNotification.Name.playtoolsCursorWillShow) } } From ac33fe56e2edc9f6caac0161398d6897bd4b1816 Mon Sep 17 00:00:00 2001 From: Xyct <87l46110@gmail.com> Date: Sun, 7 May 2023 14:40:12 +0800 Subject: [PATCH 54/54] show cursor when typing --- PlayTools/Controls/ControlMode.swift | 5 +++-- PlayTools/Controls/PlayInput.swift | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PlayTools/Controls/ControlMode.swift b/PlayTools/Controls/ControlMode.swift index e4b4f5b8..f690406c 100644 --- a/PlayTools/Controls/ControlMode.swift +++ b/PlayTools/Controls/ControlMode.swift @@ -23,16 +23,17 @@ public class ControlMode { } func setMapping(_ mapped: Bool) { - keyboardMapped = mapped if mapped { PlayInput.shared.parseKeymap() } else { + show(true) PlayInput.shared.invalidate() } + keyboardMapped = mapped } func show(_ show: Bool) { - if !editor.editorMode { + if keyboardMapped { if show { if !visible { NotificationCenter.default.post(name: NSNotification.Name.playtoolsCursorWillShow, diff --git a/PlayTools/Controls/PlayInput.swift b/PlayTools/Controls/PlayInput.swift index e8a1ad4c..d9866252 100644 --- a/PlayTools/Controls/PlayInput.swift +++ b/PlayTools/Controls/PlayInput.swift @@ -65,7 +65,6 @@ class PlayInput { Toucher.writeLog(logMessage: "editor opened? \(show)") if show { self.invalidate() - mode.show(show) if let keyboard = GCKeyboard.coalesced!.keyboardInput { keyboard.keyChangedHandler = { _, _, keyCode, pressed in PlayKeyboard.handleEditorEvent(keyCode: keyCode, pressed: pressed)