diff options
| author | Pinapelz <yukais@pinapelz.com> | 2025-01-14 00:13:38 -0800 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2025-01-14 00:13:38 -0800 |
| commit | bba427d31081f99b7dc5e724308ca6151753ccd1 (patch) | |
| tree | c77350108472acab71d0300417f2964ab0a56788 | |
initial commit1.0
| -rw-r--r-- | .gitignore | 462 | ||||
| -rw-r--r-- | brokenithm-evolved-umi.py | 96 | ||||
| -rw-r--r-- | key_config.py | 65 | ||||
| -rw-r--r-- | server/Brokenithm-Evolved-iOS.sln | 25 | ||||
| -rw-r--r-- | server/Brokenithm-Evolved-iOS/App.config | 6 | ||||
| -rw-r--r-- | server/Brokenithm-Evolved-iOS/Brokenithm-Evolved-iOS.csproj | 108 | ||||
| -rw-r--r-- | server/Brokenithm-Evolved-iOS/Brokenithm.ico | bin | 0 -> 12862 bytes | |||
| -rw-r--r-- | server/Brokenithm-Evolved-iOS/Program.cs | 343 | ||||
| -rw-r--r-- | server/Brokenithm-Evolved-iOS/Properties/AssemblyInfo.cs | 36 | ||||
| -rw-r--r-- | server/Brokenithm-Evolved-iOS/packages.config | 4 | ||||
| -rw-r--r-- | umi_led.py | 203 |
11 files changed, 1348 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7030cbd --- /dev/null +++ b/.gitignore @@ -0,0 +1,462 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +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 +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# 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/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# PyPI configuration file +.pypirc +*.spec +.vs +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# VS Code +.vscode/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs
\ No newline at end of file diff --git a/brokenithm-evolved-umi.py b/brokenithm-evolved-umi.py new file mode 100644 index 0000000..b1b72b1 --- /dev/null +++ b/brokenithm-evolved-umi.py @@ -0,0 +1,96 @@ +import mmap +import ctypes +import time +import key_config +import umi_led +import threading +import keyboard + +class SharedMemoryData(ctypes.Structure): + _fields_ = [ + ("airIoStatus", ctypes.c_uint8 * 6), + ("sliderIoStatus", ctypes.c_uint8 * 32), + ("ledRgbData", ctypes.c_uint8 * 96), + ("reserved", ctypes.c_uint8 * 4) + ] + +# Shared memory details +SHARED_MEMORY_NAME = "Local\\BROKENITHM_SHARED_BUFFER" +SHARED_MEMORY_SIZE = ctypes.sizeof(ctypes.c_uint8) * ctypes.sizeof(SharedMemoryData) + + +def initialize_shared_mem(): + try: + shared_memory = mmap.mmap(-1, SHARED_MEMORY_SIZE, tagname=SHARED_MEMORY_NAME, access=mmap.ACCESS_WRITE) + slider_status = [0] * 32 # Default: All slider zones inactive + air_status = [0] * 6 # Default: All air zones inactive + shared_memory.seek(0) + shared_memory.write(bytearray(air_status)) + print("[Initialization] Resetting Air Note Status") + shared_memory.seek(6) + shared_memory.write(bytearray(slider_status)) + print("[Initialization] Resetting Slider Status") + shared_memory.seek(6 + 32) + print("[Initialization] Setting Default Slider LED state") + shared_memory.write(bytearray(umi_led.DEFAULT_LED_STATE)) + print("[Initialization] Success. Complete") + return shared_memory + except Exception: + print("[Error] A Fatal Error occured while trying to write to the Shared Memory while initializing") + return None + +def monitor_key_presses_and_air(controller: key_config.InputConfig): + try: + shared_memory = mmap.mmap(-1, SHARED_MEMORY_SIZE, tagname=SHARED_MEMORY_NAME, access=mmap.ACCESS_READ) + key_states = {zone: False for zone in controller.get_keyzone_layout().keys()} + air_states = {zone: False for zone in controller.get_airzone_layout().keys()} + print("Now Monitoring for Inputs") + while True: + shared_memory.seek(0) + current_air_status = list(shared_memory.read(6)) + shared_memory.seek(6) + current_slider_status = list(shared_memory.read(32)) + for zone, key in controller.get_keyzone_layout().items(): + is_pressed = current_slider_status[zone] == 128 + if is_pressed != key_states[zone]: + if is_pressed: + keyboard.press(key) + print(f"Key {key} pressed") + else: + keyboard.release(key) + print(f"Key {key} released") + key_states[zone] = is_pressed + for zone, key in controller.get_airzone_layout().items(): + is_active = current_air_status[zone] == 128 + if is_active != air_states[zone]: + if is_active: + keyboard.press(key) + print(f"Air {key} activated") + else: + keyboard.release(key) + print(f"Air {key} deactivated") + air_states[zone] = is_active + time.sleep(0.01) + except Exception: + print("[Error] A Fatal Error occured while monitoring and translating inputs") + except KeyboardInterrupt: + for key in controller.get_keyzone_layout().values(): + keyboard.release(key) + for key in controller.get_airzone_layout().values(): + keyboard.release(key) + print("Stopping monitoring.") + + +if __name__ == "__main__": + shared_memory = initialize_shared_mem() + controller = key_config.Umiguri32KeyZone() + if shared_memory is not None: + stop_event = threading.Event() + server_thread = threading.Thread(target=umi_led.start_umiguri_websocket_server, args=(shared_memory, stop_event)) + server_thread.start() + monitor_key_presses_and_air(controller) + print("Shutting down...") + stop_event.set() + shared_memory.close() + exit() + diff --git a/key_config.py b/key_config.py new file mode 100644 index 0000000..eaa2be2 --- /dev/null +++ b/key_config.py @@ -0,0 +1,65 @@ +from abc import ABC, abstractmethod + + +class InputConfig(ABC): + @abstractmethod + def get_keyzone_layout(): + pass + + @abstractmethod + def get_airzone_layout(): + pass + + +class Umiguri32KeyZone(InputConfig): + def __init__(self): + super().__init__() + self._UMIGIRI_32_KEYZONE_LAYOUT = { + 31: "a", + 30: "1", + 29: "z", + 28: "q", + 27: "s", + 26: "2", + 25: "x", + 24: "w", + 23: "d", + 22: "3", + 21: "c", + 20: "e", + 19: "f", + 18: "4", + 17: "v", + 16: "r", + 15: "g", + 14: "5", + 13: "b", + 12: "t", + 11: "h", + 10: "6", + 9: "n", + 8: "y", + 7: "j", + 6: "7", + 5: "m", + 4: "u", + 3: "k", + 2: "8", + 1: "9", + 0: "i", + } + + self._UMIGIRI_32_AIRZONE_LAYOUT = { + 0: "o", + 1: "0", + 2: "p", + 3: "l", + 4: ",", + 5: ".", + } + + def get_keyzone_layout(self): + return self._UMIGIRI_32_KEYZONE_LAYOUT + + def get_airzone_layout(self): + return self._UMIGIRI_32_AIRZONE_LAYOUT diff --git a/server/Brokenithm-Evolved-iOS.sln b/server/Brokenithm-Evolved-iOS.sln new file mode 100644 index 0000000..4747dfe --- /dev/null +++ b/server/Brokenithm-Evolved-iOS.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29709.97 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Brokenithm-Evolved-iOS", "Brokenithm-Evolved-iOS\Brokenithm-Evolved-iOS.csproj", "{D127D9DF-1FB2-48F1-A8ED-F50D9852732A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D127D9DF-1FB2-48F1-A8ED-F50D9852732A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D127D9DF-1FB2-48F1-A8ED-F50D9852732A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D127D9DF-1FB2-48F1-A8ED-F50D9852732A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D127D9DF-1FB2-48F1-A8ED-F50D9852732A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C3AA92C4-7827-465C-9827-FB95FDFF4C29} + EndGlobalSection +EndGlobal diff --git a/server/Brokenithm-Evolved-iOS/App.config b/server/Brokenithm-Evolved-iOS/App.config new file mode 100644 index 0000000..ecdcf8a --- /dev/null +++ b/server/Brokenithm-Evolved-iOS/App.config @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<configuration> + <startup> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/> + </startup> +</configuration> diff --git a/server/Brokenithm-Evolved-iOS/Brokenithm-Evolved-iOS.csproj b/server/Brokenithm-Evolved-iOS/Brokenithm-Evolved-iOS.csproj new file mode 100644 index 0000000..9f5fe70 --- /dev/null +++ b/server/Brokenithm-Evolved-iOS/Brokenithm-Evolved-iOS.csproj @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{D127D9DF-1FB2-48F1-A8ED-F50D9852732A}</ProjectGuid> + <OutputType>Exe</OutputType> + <RootNamespace>Brokenithm_Evolved_iOS_Umi</RootNamespace> + <AssemblyName>Brokenithm-Evolved-iOS-Umi</AssemblyName> + <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> + <Deterministic>true</Deterministic> + <TargetFrameworkProfile /> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + <IsWebBootstrapper>false</IsWebBootstrapper> + <PublishUrl>publish\</PublishUrl> + <Install>true</Install> + <InstallFrom>Disk</InstallFrom> + <UpdateEnabled>false</UpdateEnabled> + <UpdateMode>Foreground</UpdateMode> + <UpdateInterval>7</UpdateInterval> + <UpdateIntervalUnits>Days</UpdateIntervalUnits> + <UpdatePeriodically>false</UpdatePeriodically> + <UpdateRequired>false</UpdateRequired> + <MapFileExtensions>true</MapFileExtensions> + <AutorunEnabled>true</AutorunEnabled> + <ApplicationRevision>1</ApplicationRevision> + <ApplicationVersion>1.0.0.%2a</ApplicationVersion> + <UseApplicationTrust>false</UseApplicationTrust> + <PublishWizardCompleted>true</PublishWizardCompleted> + <BootstrapperEnabled>true</BootstrapperEnabled> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup> + <GenerateManifests>true</GenerateManifests> + </PropertyGroup> + <PropertyGroup> + <SignManifests>true</SignManifests> + </PropertyGroup> + <PropertyGroup> + <ApplicationIcon>Brokenithm.ico</ApplicationIcon> + </PropertyGroup> + <ItemGroup> + <Reference Include="iMobileDevice-net, Version=1.2.0.0, Culture=neutral, PublicKeyToken=040ae19651fac98a, processorArchitecture=MSIL"> + <HintPath>..\packages\iMobileDevice-net.1.2.186\lib\net45\iMobileDevice-net.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Program.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="App.config" /> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <BootstrapperPackage Include=".NETFramework,Version=v4.7.2"> + <Visible>False</Visible> + <ProductName>Microsoft .NET Framework 4.7.2 %28x86 和 x64%29</ProductName> + <Install>true</Install> + </BootstrapperPackage> + <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1"> + <Visible>False</Visible> + <ProductName>.NET Framework 3.5 SP1</ProductName> + <Install>false</Install> + </BootstrapperPackage> + </ItemGroup> + <ItemGroup> + <Content Include="Brokenithm.ico" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Import Project="..\packages\iMobileDevice-net.1.2.186\build\net45\iMobileDevice-net.targets" Condition="Exists('..\packages\iMobileDevice-net.1.2.186\build\net45\iMobileDevice-net.targets')" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\packages\iMobileDevice-net.1.2.186\build\net45\iMobileDevice-net.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\iMobileDevice-net.1.2.186\build\net45\iMobileDevice-net.targets'))" /> + </Target> +</Project>
\ No newline at end of file diff --git a/server/Brokenithm-Evolved-iOS/Brokenithm.ico b/server/Brokenithm-Evolved-iOS/Brokenithm.ico Binary files differnew file mode 100644 index 0000000..8033b43 --- /dev/null +++ b/server/Brokenithm-Evolved-iOS/Brokenithm.ico diff --git a/server/Brokenithm-Evolved-iOS/Program.cs b/server/Brokenithm-Evolved-iOS/Program.cs new file mode 100644 index 0000000..f6785cb --- /dev/null +++ b/server/Brokenithm-Evolved-iOS/Program.cs @@ -0,0 +1,343 @@ +using iMobileDevice; +using iMobileDevice.iDevice; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.MemoryMappedFiles; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Brokenithm_Evolved_iOS +{ + class Program + { + public static Dictionary<string, iDeviceHandle> device_map = new Dictionary<string, iDeviceHandle>(); + public static Dictionary<string, Process> process_map = new Dictionary<string, Process>(); + public static Dictionary<string, iDeviceConnectionHandle> connection_map = new Dictionary<string, iDeviceConnectionHandle>(); + public static IiDeviceApi idevice; + public static MemoryMappedFile sharedBuffer; + public static MemoryMappedViewAccessor sharedBufferAccessor; + public static bool exiting = false; + public static iDeviceEventCallBack _eventCallback; + + static void Main(string[] args) + { + Console.Title = "Brokenithm-Evolved-iOS"; + Console.WriteLine("================================================="); + Console.WriteLine("= Brokenithm-Evolved-iOS (Umi) ="); + Console.WriteLine("= Brokenithm with full IO and USB connection ="); + Console.WriteLine("= v0.3 by esterTion ="); + Console.WriteLine("= Modified for Umiguri usage by Pinapelz ="); + Console.WriteLine("= Original: thebit.link ="); + Console.WriteLine("================================================="); + Console.WriteLine(""); + + NativeLibraries.Load(); + idevice = LibiMobileDevice.Instance.iDevice; + iDeviceError status; + _eventCallback = new iDeviceEventCallBack(eventCallback); + status = LibiMobileDevice.Instance.iDevice.idevice_event_subscribe(_eventCallback, IntPtr.Zero); + + MemoryMappedFileSecurity CustomSecurity = new MemoryMappedFileSecurity(); + SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); + var acct = sid.Translate(typeof(NTAccount)) as NTAccount; + CustomSecurity.AddAccessRule(new System.Security.AccessControl.AccessRule<MemoryMappedFileRights>(acct.ToString(), MemoryMappedFileRights.FullControl, System.Security.AccessControl.AccessControlType.Allow)); + sharedBuffer = MemoryMappedFile.CreateOrOpen("Local\\BROKENITHM_SHARED_BUFFER", 1024, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None, CustomSecurity, System.IO.HandleInheritability.Inheritable); + sharedBufferAccessor = sharedBuffer.CreateViewAccessor(); + + { + Thread ledThread = new Thread(new ThreadStart(outputLed)); + ledThread.Start(); + } + + Console.WriteLine("Waiting for device..."); + while (Console.ReadKey().Key != ConsoleKey.Q) { } + exiting = true; + } + + public static void eventCallback(ref iDeviceEvent e, IntPtr user_data) + { + iDeviceError status; + string udid = e.udidString; + switch (e.@event) + { + case iDeviceEventType.DeviceAdd: + { + Console.WriteLine(string.Format("device add\tudid: {0}", udid)); + if (device_map.ContainsKey(udid)) + { + return; + } + iDeviceHandle device; + status = idevice.idevice_new(out device, e.udidString); + if (status != iDeviceError.Success) + { + Console.WriteLine("Create device failed: {0}", status); + return; + } + device_map[udid] = device; + + Process process = new Process(); + process.StartInfo.FileName = @"umi-evolved\brokenithm-evolved-umi.exe"; + try + { + process.Start(); + + } + catch(Exception ex){ + Console.WriteLine("Unable to launch brokenithm-evolved-umi server. Is the .exe there?"); + Console.WriteLine("Current Directory: " + Environment.CurrentDirectory); + Console.WriteLine("Press Enter to continue..."); + Console.ReadLine(); + Environment.Exit(0); + } + process_map[udid] = process; + + Thread thread = new Thread(new ParameterizedThreadStart(connectDevice)); + thread.Start(udid); + break; + } + case iDeviceEventType.DevicePaired: + { + Console.WriteLine(string.Format("device paired\tudid: {0}", udid)); + break; + } + case iDeviceEventType.DeviceRemove: + { + Console.WriteLine(string.Format("device remove\tudid: {0}", udid)); + if (device_map.ContainsKey(udid)) + { + iDeviceHandle device = device_map[udid]; + device.Dispose(); + device_map.Remove(udid); + } + if (process_map.ContainsKey(udid)) + { + try + { + Process processToKill = process_map[udid]; + if (!processToKill.HasExited) + { + processToKill.Kill(); + } + processToKill.Dispose(); + process_map.Remove(udid); + } + catch (Exception ex) + { + Console.WriteLine($"Error killing process for device {udid}: {ex.Message}"); + } + } + break; + } + } + } + + public static void connectDevice(object arg) + { + string udid = (string)arg; + if (!device_map.ContainsKey(udid)) + { + return; + } + iDeviceHandle device = device_map[udid]; + iDeviceConnectionHandle conn; + iDeviceError status; + status = idevice.idevice_connect(device, 24864, out conn); + if (status != iDeviceError.Success) + { + //Console.WriteLine(string.Format("connect failed: {0}", status)); + Thread.Sleep(1000); + Thread thread = new Thread(new ParameterizedThreadStart(connectDevice)); + thread.Start(udid); + return; + } + + byte[] buf = new byte[256]; + uint read = 0; + status = idevice.idevice_connection_receive(conn, buf, 4, ref read); + if (status != iDeviceError.Success) + { + Console.WriteLine(string.Format("receive data failed: {0}", status)); + return; + } + if ( + buf[0] != 3 || + buf[1] != 'W' || + buf[2] != 'E' || + buf[3] != 'L' + ) + { + // welcome message + Console.WriteLine("received invalid data"); + conn.Dispose(); + return; + } + Console.WriteLine("connected to device"); + connection_map[udid] = conn; + { + Thread thread = new Thread(new ParameterizedThreadStart(readInputFromDevice)); + thread.Start(udid); + } + return; + } + public static void readInputFromDevice(object arg) + { + string udid = (string)arg; + if (!connection_map.ContainsKey(udid)) + { + return; + } + iDeviceConnectionHandle conn = connection_map[udid]; + iDeviceError status; + bool airEnabled = true; + + byte[] buf = new byte[256]; + uint read = 0; + while (true) + { + if (exiting) break; + status = idevice.idevice_connection_receive_timeout(conn, buf, 1, ref read, 5); + if (status != iDeviceError.Success) + { + if (status == iDeviceError.Timeout) + { + continue; + } + break; + } + byte len = buf[0]; + status = idevice.idevice_connection_receive_timeout(conn, buf, len, ref read, 5); + if (status != iDeviceError.Success) + { + break; + } + if (len >= 3+6+32 && + buf[0] == 'I' && + buf[1] == 'N' && + buf[2] == 'P') + { + // key input + if (airEnabled) + { + sharedBufferAccessor.WriteArray<byte>(0, buf, 3, 6 + 32); + } + else + { + sharedBufferAccessor.WriteArray<byte>(6, buf, 3 + 6, 32); + } + if (len > 3 + 6 + 32) + { + sharedBufferAccessor.WriteArray<byte>(6+32+96, buf, 3+6+32, len - (3 + 6 + 32)); + } + } + else if (len >= 4 && + buf[0] == 'A' && + buf[1] == 'I' && + buf[2] == 'R') + { + // air input control + airEnabled = buf[3] != 0; + Console.WriteLine(string.Format("Air input {0}", airEnabled ? "enabled" : "disabled")); + } + else if (len >= 4 && + buf[0] == 'F' && + buf[1] == 'N' && + buf[2] == 'C') + { + // function key + if (buf[3] == (byte)BNI_FUNCTION_KEYS.COIN) + { + sharedBufferAccessor.Write(6 + 32 + 96 + 2, 1); + } + else if (buf[3] == (byte)BNI_FUNCTION_KEYS.CARD) + { + sharedBufferAccessor.Write(6 + 32 + 96 + 3, 1); + } + } + else + { + if (len >= 3) + { + StringBuilder sb = new StringBuilder(); + sb.Append((char)buf[0]); + sb.Append((char)buf[1]); + sb.Append((char)buf[2]); + Console.WriteLine(string.Format("unknown packet: {0}", sb.ToString())); + } + else + { + Console.WriteLine("unknown packet"); + } + } + } + conn.Dispose(); + connection_map.Remove(udid); + Console.WriteLine("disconnected"); + if (exiting) return; + Thread.Sleep(1000); + { + Thread thread = new Thread(new ParameterizedThreadStart(connectDevice)); + thread.Start(udid); + } + } + + enum BNI_FUNCTION_KEYS { + COIN = 1, + CARD + }; + + private static byte[] prevLedRgb = new byte[32 * 3]; + private static int skipCount = 0; + public static void outputLed() + { + while (true) + { + if (exiting) break; + byte[] ledRgb = new byte[32 * 3]; + sharedBufferAccessor.ReadArray<byte>(6 + 32, ledRgb, 0, 32 * 3); + bool same = true; + for (int i = 0; i < 32 * 3; i++) + { + if (ledRgb[i] != prevLedRgb[i]) + { + same = false; + break; + } + } + if (!same) + { + BroadcastLEDStatus(ledRgb); + prevLedRgb = ledRgb; + skipCount = 0; + } + else + { + if (++skipCount > 50) + { + BroadcastLEDStatus(prevLedRgb); + skipCount = 0; + } + } + Thread.Sleep(10); + } + } + public static void BroadcastLEDStatus(byte[] led) + { + uint sent = 0; + byte[] head = { 99, (byte)'L', (byte)'E', (byte)'D' }; + foreach (var conn in connection_map) + { + try + { + idevice.idevice_connection_send(conn.Value, head, 4, ref sent); + idevice.idevice_connection_send(conn.Value, led, 96, ref sent); + } + catch (Exception e) { } + } + } + } +} diff --git a/server/Brokenithm-Evolved-iOS/Properties/AssemblyInfo.cs b/server/Brokenithm-Evolved-iOS/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0e57bf6 --- /dev/null +++ b/server/Brokenithm-Evolved-iOS/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("Brokenithm-Evolved-iOS")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Brokenithm-Evolved-iOS")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("d127d9df-1fb2-48f1-a8ed-f50d9852732a")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/server/Brokenithm-Evolved-iOS/packages.config b/server/Brokenithm-Evolved-iOS/packages.config new file mode 100644 index 0000000..4266474 --- /dev/null +++ b/server/Brokenithm-Evolved-iOS/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="iMobileDevice-net" version="1.2.186" targetFramework="net472" /> +</packages>
\ No newline at end of file diff --git a/umi_led.py b/umi_led.py new file mode 100644 index 0000000..e5718ff --- /dev/null +++ b/umi_led.py @@ -0,0 +1,203 @@ +""" +Umiguri LED Controller Protocol. Some commands have been stubbed +Thanks inonote for the great resources on the protocol +https://gist.github.com/inonote/00251fed881a82c9df1e505eef1722bc + +The implementation below is largely derived from +https://github.com/inonote/UmiguriSampleLedServer/blob/master/src/App.cpp +""" + +import asyncio +import websockets + +kLedServerPort = 7124 +kLedServerName = "BrokenithmLedServer" +kLedServerVersion = (123, 456) +kLedServerHardwareName = "Brokenithm" +kLedServerHardwareVersion = (987, 654) + +kLedProtocolVersion = 0x01 + +ULED_COMMAND_SET_LED = 0x10 +ULED_COMMAND_INITIALIZE = 0x11 +ULED_COMMAND_READY = ULED_COMMAND_INITIALIZE | 0x08 # 0x19 +ULED_COMMAND_PING = 0x12 +ULED_COMMAND_PONG = ULED_COMMAND_PING | 0x08 # 0x1A +ULED_COMMAND_REQUEST_SERVER_INFO = 0xD0 +ULED_COMMAND_REPORT_SERVER_INFO = ULED_COMMAND_REQUEST_SERVER_INFO | 0x08 # 0xD8 + +DEFAULT_LED_STATE = [ + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255, 0, 0, 0, + 255, 255, 255 +] + +SHOW_LOG = False + +def log_message(message: str): + if not SHOW_LOG: + return + print(message) + +def validate_message(payload: bytes) -> bool: + """ + Checks if the incoming payload conforms to the protocol: + 1. At least 3 bytes: [protocolVersion, command, length]. + 2. The length byte must match (payload.size() - 3). + 3. For known commands, check expected payload size if needed. + """ + if len(payload) < 3: + return False + + if payload[0] != kLedProtocolVersion: + return False + + length_byte = payload[2] + if length_byte != (len(payload) - 3): + return False + + command = payload[1] + if command == ULED_COMMAND_SET_LED: + if len(payload) != 106: # 103 bytes of LED data + 3 header bytes + return False + elif command == ULED_COMMAND_PING: + if len(payload) != 7: # 4 bytes payload + 3 header bytes + return False + + return True + +def get_brokenithm_led_array(payload): + led_state = [] + land_colors = [] + border_colors = [] + for i in range(16): + offset = 1 + i * 3 + land_colors.append(tuple(payload[offset:offset + 3])) + + for i in range(15): + offset = 49 + i * 3 + border_colors.append(tuple(payload[offset:offset + 3])) + land_colors = list(reversed(land_colors)) + border_colors = list(reversed(border_colors)) + try: + for i in range(15): + led_state.append(land_colors[i][2]) + led_state.append(land_colors[i][0]) + led_state.append(land_colors[i][1]) + led_state.append(border_colors[i][2]) + led_state.append(border_colors[i][0]) + led_state.append(border_colors[i][1]) + led_state.append(land_colors[15][2]) + led_state.append(land_colors[15][0]) + led_state.append(land_colors[15][1]) + except Exception: + log_message("[LEDServer] Invalid LED State received ignoring") + return None + return led_state + +# R,G,B -> B,R,G + +async def handle_message(websocket, payload: bytes, shared_memory): + """ + Process incoming (validated) payload and respond if needed. + """ + command = payload[1] + log_message(f"Received raw data ({len(payload)} bytes): {payload}") + + if command == ULED_COMMAND_PING: + log_message("-> Handling PING command") + custom_data = payload[3:7] + response = bytearray([kLedProtocolVersion, ULED_COMMAND_PONG, 6]) + custom_data + bytearray([0x51, 0xED]) + await websocket.send(response) + + elif command == ULED_COMMAND_INITIALIZE: + log_message("-> Handling INITIALIZE command") + response = bytearray([kLedProtocolVersion, ULED_COMMAND_READY, 0]) + await websocket.send(response) + + elif command == ULED_COMMAND_REQUEST_SERVER_INFO: + log_message("-> Handling REQUEST_SERVER_INFO command") + buf = bytearray(3 + 44) + buf[0] = kLedProtocolVersion + buf[1] = ULED_COMMAND_REPORT_SERVER_INFO + buf[2] = 44 + + name_bytes = kLedServerName.encode('ascii')[:16] + buf[3:3+len(name_bytes)] = name_bytes + + ver0, ver1 = kLedServerVersion + buf[3+16] = ver0 & 0xFF + buf[3+17] = (ver0 >> 8) & 0xFF + buf[3+18] = ver1 & 0xFF + buf[3+19] = (ver1 >> 8) & 0xFF + + hw_bytes = kLedServerHardwareName.encode('ascii')[:16] + buf[3+22:3+22+len(hw_bytes)] = hw_bytes + + hwver0, hwver1 = kLedServerHardwareVersion + buf[3+38] = hwver0 & 0xFF + buf[3+39] = (hwver0 >> 8) & 0xFF + buf[3+40] = hwver1 & 0xFF + buf[3+41] = (hwver1 >> 8) & 0xFF + + await websocket.send(buf) + + elif command == ULED_COMMAND_SET_LED: + log_message("-> Handling SET_LED command") + brightness = payload[3] + led_data = payload[3:] + log_message(f" Brightness: {brightness}") + log_message(f" LED color data length: {len(led_data)}") + led_arr = get_brokenithm_led_array(led_data) + if led_arr is None: + return + shared_memory.seek(6 + 32) + shared_memory.write(bytearray(led_arr)) + + else: + log_message(f"-> Unknown command 0x{command:02X}") + +async def handle_client(websocket, shared_memory): + """ + Main entry point for each connected client. + Listens for messages, validates them, and calls handle_message. + """ + print(f"[LEDServer] Client Detected from: {websocket.remote_address}") + try: + async for raw_message in websocket: + if isinstance(raw_message, bytes): + if validate_message(raw_message): + await handle_message(websocket, raw_message, shared_memory) + else: + log_message(f"Invalid message received: {raw_message}") + else: + log_message(f"Received text message (ignored): {raw_message}") + except websockets.exceptions.ConnectionClosed as e: + log_message(f"Client disconnected: {e.reason}") + +async def run_websocket_server(shared_memory, stop_event): + print(f"[LEDerver] Starting WebSocket server on port {kLedServerPort}...") + async with websockets.serve(lambda ws: handle_client(ws, shared_memory), "0.0.0.0", kLedServerPort): + print("[LEDServer] LEDServer started. Awaiting connections...") + while not stop_event.is_set(): + await asyncio.sleep(0.1) + print("[LEDServer] Websocket Server Shutting Down") + +def start_umiguri_websocket_server(shared_memory, stop_event): + try: + asyncio.run(run_websocket_server(shared_memory, stop_event)) + except KeyboardInterrupt: + log_message("Keyboard Interrupt Received - Server stopped.") |
