diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1705d70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,211 @@ + +# Created by https://www.gitignore.io/api/python,objective-c,visualstudiocode +# Edit at https://www.gitignore.io/?templates=python,objective-c,visualstudiocode + +### Objective-C ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Objective-C Patch ### + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +### Python Patch ### +.venv/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +# End of https://www.gitignore.io/api/python,objective-c,visualstudiocode + +### Custom exclusions ### + +# Ignore all files generated after a successful build +iohid_wrap.dylib +mapKeys.h \ No newline at end of file diff --git a/KeyMapping.png b/KeyMapping.png new file mode 100644 index 0000000..6b536a0 Binary files /dev/null and b/KeyMapping.png differ diff --git a/KeyMapping.sketch b/KeyMapping.sketch new file mode 100644 index 0000000..9825b97 Binary files /dev/null and b/KeyMapping.sketch differ diff --git a/Readme.md b/Readme.md index 843511e..3ef315c 100644 --- a/Readme.md +++ b/Readme.md @@ -1,20 +1,36 @@ -Setup -===== +[![macos-catalina](https://img.shields.io/badge/macos-catalina-brightgreen.svg)](https://www.apple.com/macos/catalina-preview) +[![macos-mojave](https://img.shields.io/badge/macos-mojave-brightgreen.svg)](https://www.apple.com/lae/macos/mojave) -ShockEmu requires the OS X command line development tools to be installed. Installation steps: +# Key Mapping +`only_keyboard.se` goes like this: - git clone https://github.com/daeken/ShockEmu.git - ./build.sh .se - ./run.sh +![Key Mapping](https://github.com/backslash-f/ShockEmu/blob/master/KeyMapping.png) -It depends on your system having PS4 Remote Play installed at `/Applications/RemotePlay.app`. If this is not the case, you'll need to modify `run.sh` accordingly. +# Setup +```zsh +./build.sh only_keyboard.se +./run.sh +``` -SE file format -============== +It depends on your system having [PS4 Remote Play](https://remoteplay.dl.playstation.net/remoteplay/lang/en/index.html) installed at `/Applications/RemotePlay.app`. If this is not the case, you'll need to modify `run.sh` accordingly. -SE files are, generally speaking, a mapping between an input key, mouse button, or mouse movement to a DualShock 4 input. See the example file (`nomanssky.se`) for a breakdown of the format. +The `OS X Command Line Tools` needs [to be installed](https://stackoverflow.com/a/53078282/584548). -How it works -============ +Relies on `python2` (see #2), which can be installed via `brew install python@2` -ShockEmu works by intercepting the IOHID calls of PS4 Remote Play application and presents an emulated DualShock controller. It also hooks into the input routines of the application, to catch keyboard and mouse inputs, which then get mapped according to your SE file. +# SE File Format +SE files are, generally speaking, a mapping between an input key, mouse button, or mouse movement to a DualShock 4 input. See the example file (`example.se`) for a breakdown of the format. + +# How It Works +ShockEmu works by intercepting the IOHID calls of PS4 Remote Play application and presents an emulated DualShock controller. It also hooks into the input routines of the application, to catch keyboard and mouse inputs, which then get mapped according to your SE file. + +# But It Is Not Working! +You may have to [turn off System Integrity Protection via 'csrutil'](https://www.imore.com/how-turn-system-integrity-protection-macos) in order for `DYLD_INSERT_LIBRARIES` to function on the newest macOS. Thanks [Ben](https://github.com/benh57) for figuring this out! + +# Pro Tip +The `alias` below allows for typing `play` / `enter` anywhere in `Terminal` and have `RemotePlay.app` launched with the above keys mapped: +``` +$ cat ~/.zshrc | grep play +alias play="pushd [REPOSITORY_ROOT]; ./run.sh &; popd" +``` +👆🏻`ShockEmu` repo location must be updated according to your machine. diff --git a/build.sh b/build.sh index 72f56c1..0b5c63e 100755 --- a/build.sh +++ b/build.sh @@ -1,4 +1,4 @@ #!/bin/bash -python shockemu.py $1 +python2 shockemu.py $1 clang -dynamiclib -std=gnu99 iohid_wrap.m -current_version 1.0 -compatibility_version 1.0 -lobjc -framework Foundation -framework AppKit -framework CoreFoundation -o iohid_wrap.dylib diff --git a/nomanssky.se b/example.se similarity index 72% rename from nomanssky.se rename to example.se index 7a33344..0738f95 100644 --- a/nomanssky.se +++ b/example.se @@ -1,27 +1,27 @@ -# Map movement controls to the left stick +# Map movement controls to the left stick. The same can be done using 'right' instead of 'left'. w = leftY- a = leftX- s = leftY+ d = leftX+ -space = X -e = square +y = X +u = square +i = O +o = triangle -leftMouse = R2 -rightMouse = L1 +g = R1 +h = R2 +leftMouse = L1 +rightMouse = L2 -x = L2 +p = PS +t = touchpad up = dpadUp left = dpadLeft right = dpadRight down = dpadDown -p = PS -t = touchpad - -escape = O - # Map mouse to the right stick mouseLook.type = linear # 1:1 translation mode, no curve mouseLook.stick = right diff --git a/iohid_wrap.m b/iohid_wrap.m index b5a7811..a2f39aa 100644 --- a/iohid_wrap.m +++ b/iohid_wrap.m @@ -213,7 +213,7 @@ - (void)tick { prep->left_y = (uint8_t) fmin(fmax(128 + leftY * 127, 0), 255); prep->right_x = (uint8_t) fmin(fmax(128 + rightX * 127, 0), 255); prep->right_y = (uint8_t) fmin(fmax(128 + rightY * 127, 0), 255); - callback(context, kIOReturnSuccess, self, kIOHIDReportTypeInput, 1, report, 64); + callback(context, kIOReturnSuccess, (void *)0xDEADBEEF, kIOHIDReportTypeInput, 0x01, report, 64); ticks++; } diff --git a/only_keyboard.se b/only_keyboard.se new file mode 100644 index 0000000..07eac67 --- /dev/null +++ b/only_keyboard.se @@ -0,0 +1,32 @@ +# Map movement controls to the left stick. The same can be done using 'right' instead of 'left'. + +e = L2 +q = L1 + +w = leftY- +a = leftX- +s = leftY+ +d = leftX+ + +up = dpadUp +left = dpadLeft +right = dpadRight +down = dpadDown + +t = PS +space = touchpad +1 = share +0 = options + +u = R2 +o = R1 + +i = rightY- +j = rightX- +k = rightY+ +l = rightX+ + +f = square +c = X +h = triangle +n = O diff --git a/run.sh b/run.sh index 8b37d1d..2fd89cb 100755 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/bash -DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=iohid_wrap.dylib /Applications/RemotePlay.app/Contents/MacOS/RemotePlay \ No newline at end of file +DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=iohid_wrap.dylib /Applications/RemotePlay.app/Contents/MacOS/RemotePlay >/dev/null 2>&1 diff --git a/shockemu.py b/shockemu.py index 8098c54..bd7e0fe 100644 --- a/shockemu.py +++ b/shockemu.py @@ -74,4 +74,4 @@ def parse(data): {stick}X = {stick}Y = 0; }}'''.format(**mouseLook) else: - print 'Unknown mouseLook type:', mouseLook + print 'Unknown mouseLook type:', mouseLook \ No newline at end of file diff --git a/shockemu3.py b/shockemu3.py new file mode 100644 index 0000000..baaaf39 --- /dev/null +++ b/shockemu3.py @@ -0,0 +1,82 @@ +import json, string, sys + +letters = 0, 11, 8, 2, 14, 3, 5, 4, 34, 38, 40, 37, 46, 45, 31, 35, 12, 15, 1, 17, 32, 9, 13, 7, 16, 6 +nums = 29, 18, 19, 20, 21, 23, 22, 26, 28, 25 + +keys = dict( + space=49, + enter=36, + control=59, option=58, command=55, + up=126, + down=125, + left=123, + right=124, + shift=56, + capslock=57, + tab=48, + backtick=50, + comma=43, period=47, slash=44, backslash=42, + delete=51, + escape=53, +) + +buttons = 'dpadUp dpadLeft dpadRight dpadDown X O square triangle PS touchpad options share L1 L2 L3 R1 R2 R3'.split(' ') + +axes = 'leftX- leftX+ leftY- leftY+ rightX- rightX+ rightY- rightY+'.split(' ') + +for i, x in enumerate(letters): + keys[chr(ord('a') + i)] = x +for i, x in enumerate(nums): + keys[str(i)] = x + +def parse(data): + lines = (line.split('#', 1)[0].strip() for line in data.split('\n')) + return dict(map(string.strip, line.split('=', 1)) for line in lines if line) + +with open('mapKeys.h', 'w') as fp: + keysticks = [] + mouseLook = None + with open(sys.argv[1]) as f: + for line in f: + try: + k, v = line.split('#')[0].replace(" ", "").strip().split("=") + except: + continue + if k in keys or k == 'leftMouse' or k == 'rightMouse': + if k in keys: + k = 'DOWN({})'.format(keys[k]) + if v in buttons: + print('{v} = {k};'.format(v=v,k=k),file=fp) + elif v in axes: + stick = v[:-2] + if stick not in keysticks: + print('{x}X = {y}Y = 0;'.format(x=stick, y=stick),file=fp) + keysticks.append(stick) + print ('if({k}) {x} {y}= 1;'.format(k=k, x = v[:-1], y = v[-1]),file=fp) + else: + print ('Unknown button',v) + elif k.startswith('mouseLook.'): + if mouseLook is None: + mouseLook = dict(type='linear', deadZone='.1', decay='10', multX='1', multY='1', stick='right') + mouseLook[k.split('.', 1)[1]] = v + else: + print ('Unknown key:',k,v) + + if mouseLook is not None: + if mouseLook['type'] == 'linear': + print (\ + '''if(mouseMoved) {{ + {stick}X = -mouseAccelX; + {stick}Y = mouseAccelY; + mouseMoved = false; + }} else {{ + {stick}X /= {decay}; + {stick}Y /= {decay}; + if(fabs({stick}X) > {deadZone} || fabs({stick}Y) > {deadZone}) {{ + NSLog(@"Still decaying... %f %f", {stick}X, {stick}Y); + [self decayKick]; + }} else + {stick}X = {stick}Y = 0; + }}'''.format(**mouseLook),file=fp) + else: + print ('Unknown mouseLook type:',mouseLook) \ No newline at end of file