├── .gitignore ├── Docs ├── Resources │ ├── Jetbrains.svg │ ├── LevelSelection.png │ ├── Logo.png │ └── RunSample.png └── zh-cn │ └── 使用手册.md ├── LICENSE ├── README.md ├── Source ├── Auto Arknights.sln ├── Auto Arknights.sln.DotSettings ├── CLI │ ├── Auto Arknights CLI.csproj │ ├── Auto Arknights CLI.csproj.DotSettings │ ├── Build-Artifact.ps1 │ ├── Cli.cs │ ├── Entry.cs │ ├── Properties │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── Resources │ │ └── Auto Arknights CLI.config.yml │ └── global.json ├── Core │ ├── Adb │ │ ├── AdbWinApi.dll │ │ ├── AdbWinUsbApi.dll │ │ └── adb.exe │ ├── AdbDevice.cs │ ├── Assets │ │ ├── Combat │ │ │ ├── AutoDep.png │ │ │ ├── Begin.png │ │ │ ├── Start.png │ │ │ ├── TakeOver.png │ │ │ └── UseOriginite.png │ │ ├── General │ │ │ └── Yes.png │ │ ├── Home │ │ │ ├── Friends.png │ │ │ ├── Settings.png │ │ │ └── Tasks.png │ │ ├── Info.json │ │ ├── Infra │ │ │ ├── VisitNext.png │ │ │ └── VisitNextGrey.png │ │ ├── Menu │ │ │ └── Enter.png │ │ ├── Profile │ │ │ ├── FriendsList.png │ │ │ └── Visit.png │ │ ├── Tasks │ │ │ ├── ClaimAll.png │ │ │ ├── Daily.png │ │ │ └── Weekly.png │ │ └── Tesseract │ │ │ ├── TrainedData │ │ │ ├── chi_sim.traineddata │ │ │ ├── chi_sim_vert.traineddata │ │ │ └── eng.traineddata │ │ │ └── UserPatterns │ │ │ ├── CurrentSanity │ │ │ └── RequiredSanity │ ├── Auto Arknights Core.csproj │ ├── Auto Arknights Core.csproj.DotSettings │ ├── CV │ │ ├── Feature2DType.cs │ │ ├── FeatureDetector.Cache.cs │ │ ├── FeatureDetector.cs │ │ ├── FeatureMatcher.cs │ │ ├── FeatureRegistration.cs │ │ ├── ImageLocator.csproj │ │ ├── ImageRegistration.cs │ │ ├── MatFeature.cs │ │ ├── Ocr.cs │ │ ├── Quadrilateral32.cs │ │ ├── RegistrationResult.cs │ │ └── TemplateRegistration.cs │ ├── ChildProcessTracker.cs │ ├── CombatModule.cs │ ├── Game.cs │ ├── IDevice.cs │ ├── ImageAsset.cs │ ├── InfrastructureModule.cs │ ├── Interactor.cs │ ├── Level.cs │ ├── LevelCombatSettings.cs │ ├── RelativeArea.cs │ ├── Sanity.cs │ └── Utils.cs └── Test │ └── Core │ ├── Data │ └── FeatureRegistrationTest │ │ └── StandardRegister │ │ ├── 1 │ │ ├── model.png │ │ └── scene.png │ │ └── 2 │ │ ├── model.png │ │ └── scene.png │ ├── FeatureRegistrationTest.cs │ └── Test.Core.csproj └── azure-pipelines.yml /.gitignore: -------------------------------------------------------------------------------- 1 | artifact/ 2 | 3 | # JetBrains Rider 4 | .idea/ 5 | *.sln.iml 6 | 7 | ## Ignore Visual Studio temporary files, build results, and 8 | ## files generated by popular Visual Studio add-ons. 9 | ## 10 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 11 | 12 | # User-specific files 13 | *.rsuser 14 | *.suo 15 | *.user 16 | *.userosscache 17 | *.sln.docstates 18 | 19 | # User-specific files (MonoDevelop/Xamarin Studio) 20 | *.userprefs 21 | 22 | # Mono auto generated files 23 | mono_crash.* 24 | 25 | # Build results 26 | [Dd]ebug/ 27 | [Dd]ebugPublic/ 28 | [Rr]elease/ 29 | [Rr]eleases/ 30 | x64/ 31 | x86/ 32 | [Ww][Ii][Nn]32/ 33 | [Aa][Rr][Mm]/ 34 | [Aa][Rr][Mm]64/ 35 | bld/ 36 | [Bb]in/ 37 | [Oo]bj/ 38 | [Ll]og/ 39 | [Ll]ogs/ 40 | 41 | # Visual Studio 2015/2017 cache/options directory 42 | .vs/ 43 | # Uncomment if you have tasks that create the project's static files in wwwroot 44 | #wwwroot/ 45 | 46 | # Visual Studio 2017 auto generated files 47 | Generated\ Files/ 48 | 49 | # MSTest test Results 50 | [Tt]est[Rr]esult*/ 51 | [Bb]uild[Ll]og.* 52 | 53 | # NUnit 54 | *.VisualState.xml 55 | TestResult.xml 56 | nunit-*.xml 57 | 58 | # Build Results of an ATL Project 59 | [Dd]ebugPS/ 60 | [Rr]eleasePS/ 61 | dlldata.c 62 | 63 | # Benchmark Results 64 | BenchmarkDotNet.Artifacts/ 65 | 66 | # .NET Core 67 | project.lock.json 68 | project.fragment.lock.json 69 | artifacts/ 70 | 71 | # ASP.NET Scaffolding 72 | ScaffoldingReadMe.txt 73 | 74 | # StyleCop 75 | StyleCopReport.xml 76 | 77 | # Files built by Visual Studio 78 | *_i.c 79 | *_p.c 80 | *_h.h 81 | *.ilk 82 | *.meta 83 | *.obj 84 | *.iobj 85 | *.pch 86 | *.pdb 87 | *.ipdb 88 | *.pgc 89 | *.pgd 90 | *.rsp 91 | *.sbr 92 | *.tlb 93 | *.tli 94 | *.tlh 95 | *.tmp 96 | *.tmp_proj 97 | *_wpftmp.csproj 98 | *.log 99 | *.vspscc 100 | *.vssscc 101 | .builds 102 | *.pidb 103 | *.svclog 104 | *.scc 105 | 106 | # Chutzpah Test files 107 | _Chutzpah* 108 | 109 | # Visual C++ cache files 110 | ipch/ 111 | *.aps 112 | *.ncb 113 | *.opendb 114 | *.opensdf 115 | *.sdf 116 | *.cachefile 117 | *.VC.db 118 | *.VC.VC.opendb 119 | 120 | # Visual Studio profiler 121 | *.psess 122 | *.vsp 123 | *.vspx 124 | *.sap 125 | 126 | # Visual Studio Trace Files 127 | *.e2e 128 | 129 | # TFS 2012 Local Workspace 130 | $tf/ 131 | 132 | # Guidance Automation Toolkit 133 | *.gpState 134 | 135 | # ReSharper is a .NET coding add-in 136 | _ReSharper*/ 137 | *.[Rr]e[Ss]harper 138 | *.DotSettings.user 139 | 140 | # TeamCity is a build add-in 141 | _TeamCity* 142 | 143 | # DotCover is a Code Coverage Tool 144 | *.dotCover 145 | 146 | # AxoCover is a Code Coverage Tool 147 | .axoCover/* 148 | !.axoCover/settings.json 149 | 150 | # Coverlet is a free, cross platform Code Coverage Tool 151 | coverage*.json 152 | coverage*.xml 153 | coverage*.info 154 | 155 | # Visual Studio code coverage results 156 | *.coverage 157 | *.coveragexml 158 | 159 | # NCrunch 160 | _NCrunch_* 161 | .*crunch*.local.xml 162 | nCrunchTemp_* 163 | 164 | # MightyMoose 165 | *.mm.* 166 | AutoTest.Net/ 167 | 168 | # Web workbench (sass) 169 | .sass-cache/ 170 | 171 | # Installshield output folder 172 | [Ee]xpress/ 173 | 174 | # DocProject is a documentation generator add-in 175 | DocProject/buildhelp/ 176 | DocProject/Help/*.HxT 177 | DocProject/Help/*.HxC 178 | DocProject/Help/*.hhc 179 | DocProject/Help/*.hhk 180 | DocProject/Help/*.hhp 181 | DocProject/Help/Html2 182 | DocProject/Help/html 183 | 184 | # Click-Once directory 185 | publish/ 186 | 187 | # Publish Web Output 188 | *.[Pp]ublish.xml 189 | *.azurePubxml 190 | # Note: Comment the next line if you want to checkin your web deploy settings, 191 | # but database connection strings (with potential passwords) will be unencrypted 192 | *.pubxml 193 | *.publishproj 194 | 195 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 196 | # checkin your Azure Web App publish settings, but sensitive information contained 197 | # in these scripts will be unencrypted 198 | PublishScripts/ 199 | 200 | # NuGet Packages 201 | *.nupkg 202 | # NuGet Symbol Packages 203 | *.snupkg 204 | # The packages folder can be ignored because of Package Restore 205 | **/[Pp]ackages/* 206 | # except build/, which is used as an MSBuild target. 207 | !**/[Pp]ackages/build/ 208 | # Uncomment if necessary however generally it will be regenerated when needed 209 | #!**/[Pp]ackages/repositories.config 210 | # NuGet v3's project.json files produces more ignorable files 211 | *.nuget.props 212 | *.nuget.targets 213 | 214 | # Microsoft Azure Build Output 215 | csx/ 216 | *.build.csdef 217 | 218 | # Microsoft Azure Emulator 219 | ecf/ 220 | rcf/ 221 | 222 | # Windows Store app package directories and files 223 | AppPackages/ 224 | BundleArtifacts/ 225 | Package.StoreAssociation.xml 226 | _pkginfo.txt 227 | *.appx 228 | *.appxbundle 229 | *.appxupload 230 | 231 | # Visual Studio cache files 232 | # files ending in .cache can be ignored 233 | *.[Cc]ache 234 | # but keep track of directories ending in .cache 235 | !?*.[Cc]ache/ 236 | 237 | # Others 238 | ClientBin/ 239 | ~$* 240 | *~ 241 | *.dbmdl 242 | *.dbproj.schemaview 243 | *.jfm 244 | *.pfx 245 | *.publishsettings 246 | orleans.codegen.cs 247 | 248 | # Including strong name files can present a security risk 249 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 250 | #*.snk 251 | 252 | # Since there are multiple workflows, uncomment next line to ignore bower_components 253 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 254 | #bower_components/ 255 | 256 | # RIA/Silverlight projects 257 | Generated_Code/ 258 | 259 | # Backup & report files from converting an old project file 260 | # to a newer Visual Studio version. Backup files are not needed, 261 | # because we have git ;-) 262 | _UpgradeReport_Files/ 263 | Backup*/ 264 | UpgradeLog*.XML 265 | UpgradeLog*.htm 266 | ServiceFabricBackup/ 267 | *.rptproj.bak 268 | 269 | # SQL Server files 270 | *.mdf 271 | *.ldf 272 | *.ndf 273 | 274 | # Business Intelligence projects 275 | *.rdl.data 276 | *.bim.layout 277 | *.bim_*.settings 278 | *.rptproj.rsuser 279 | *- [Bb]ackup.rdl 280 | *- [Bb]ackup ([0-9]).rdl 281 | *- [Bb]ackup ([0-9][0-9]).rdl 282 | 283 | # Microsoft Fakes 284 | FakesAssemblies/ 285 | 286 | # GhostDoc plugin setting file 287 | *.GhostDoc.xml 288 | 289 | # Node.js Tools for Visual Studio 290 | .ntvs_analysis.dat 291 | node_modules/ 292 | 293 | # Visual Studio 6 build log 294 | *.plg 295 | 296 | # Visual Studio 6 workspace options file 297 | *.opt 298 | 299 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 300 | *.vbw 301 | 302 | # Visual Studio LightSwitch build output 303 | **/*.HTMLClient/GeneratedArtifacts 304 | **/*.DesktopClient/GeneratedArtifacts 305 | **/*.DesktopClient/ModelManifest.xml 306 | **/*.Server/GeneratedArtifacts 307 | **/*.Server/ModelManifest.xml 308 | _Pvt_Extensions 309 | 310 | # Paket dependency manager 311 | .paket/paket.exe 312 | paket-files/ 313 | 314 | # FAKE - F# Make 315 | .fake/ 316 | 317 | # CodeRush personal settings 318 | .cr/personal 319 | 320 | # Python Tools for Visual Studio (PTVS) 321 | __pycache__/ 322 | *.pyc 323 | 324 | # Cake - Uncomment if you are using it 325 | # tools/** 326 | # !tools/packages.config 327 | 328 | # Tabs Studio 329 | *.tss 330 | 331 | # Telerik's JustMock configuration file 332 | *.jmconfig 333 | 334 | # BizTalk build output 335 | *.btp.cs 336 | *.btm.cs 337 | *.odx.cs 338 | *.xsd.cs 339 | 340 | # OpenCover UI analysis results 341 | OpenCover/ 342 | 343 | # Azure Stream Analytics local run output 344 | ASALocalRun/ 345 | 346 | # MSBuild Binary and Structured Log 347 | *.binlog 348 | 349 | # NVidia Nsight GPU debugger configuration file 350 | *.nvuser 351 | 352 | # MFractors (Xamarin productivity tool) working folder 353 | .mfractor/ 354 | 355 | # Local History for Visual Studio 356 | .localhistory/ 357 | 358 | # BeatPulse healthcheck temp database 359 | healthchecksdb 360 | 361 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 362 | MigrationBackup/ 363 | 364 | # Ionide (cross platform F# VS Code tools) working folder 365 | .ionide/ 366 | 367 | # Fody - auto-generated XML schema 368 | FodyWeavers.xsd -------------------------------------------------------------------------------- /Docs/Resources/Jetbrains.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Docs/Resources/LevelSelection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Docs/Resources/LevelSelection.png -------------------------------------------------------------------------------- /Docs/Resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Docs/Resources/Logo.png -------------------------------------------------------------------------------- /Docs/Resources/RunSample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Docs/Resources/RunSample.png -------------------------------------------------------------------------------- /Docs/zh-cn/使用手册.md: -------------------------------------------------------------------------------- 1 | # 使用手册 2 | 3 | ## 使用 4 | 5 | 1. 如果你的模拟器不是Memu或者逍遥模拟器,请参考[程序配置](#程序配置)小节 6 | 2. 用模拟器打开选关界面,选择关卡,确认当前界面和图片上的差不多,**勾选代理作战**。 7 | ![选关界面](../Resources/LevelSelection.png) 8 | 3. 程序会提示输入指令。输入 `combat 2`,然后按回车。 9 | 4. 程序将会作战2次。 10 | 11 | 高级用法请在程序输入 `help` 以查看帮助信息。 12 | 13 | ## 程序配置 14 | 15 | 目前,程序配置文件是程序目录下的 `Auto Arknights CLI.config.yaml`。 16 | 如果你只是用Memu以外的的模拟器,请直接更改 `DeviceSerial`。一个典型的配置如下: 17 | 18 | ```yaml 19 | Remote: 20 | Mode: Adb # 目前只有 ADB 模式,别管 21 | CloseCommandLine: '"C:\Program Files\Microvirt\MEmu\memuc.exe" stop -i 0' # 用于关闭模拟器的命令行 22 | Adb: 23 | ExecutablePath: adb\adb.exe # 自定义 ADB 可执行文件路径,可以不动 24 | # 如果你只是用Memu以外的的模拟器,请直接更改DeviceSerial 25 | DeviceSerial: 127.0.0.1:21503 # 必须,可以是模拟器的 ADB 地址,也可以是 USB 设备序列号,参考下表 26 | ``` 27 | 28 | ## 常见模拟器 ADB 地址列表 29 | 30 | 注意,MuMu手游助手是**没有**ADB的,只有MuMu模拟器有。 31 | | 名称 | 地址 | 32 | | ----------------- | --------------- | 33 | | MuMu | 127.0.0.1:7555 | 34 | | Memu Play(逍遥) | 127.0.0.1:21503 | 35 | | Nox | 127.0.0.1:62001 | 36 | | 雷电 | 127.0.0.1:5555 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 CCRcmcpe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto Arknights 2 | 3 | ![Framework](https://img.shields.io/badge/.NET%206.0---?logo=C%20Sharp) 4 | ![Base Branch](https://img.shields.io/badge/Base%20Branch-dev--2.0.0-blue?logo=git) 5 | [![Build Status](https://rev-unit.visualstudio.com/Auto-Arknights/_apis/build/status/CCRcmcpe.Auto-Arknights?branchName=dev-2.0.0)](https://rev-unit.visualstudio.com/Auto-Arknights/_build/latest?definitionId=1&branchName=dev-2.0.0) 6 | 7 | ![刷关演示](Docs/Resources/RunSample.png) 8 | *用于自动化明日方舟部分操作的程序。* 9 | 10 | ## 开发现状 11 | 12 | 由于目前功能已经足够,懒得继续开发。如果明日方舟UI没有大幅更改,本程序将在可预见的将来稳健运行。 13 | 14 | ## 入门 15 | 16 | > [程序下载](https://github.com/CCRcmcpe/Auto-Arknights/releases) 17 | > [使用手册](Docs/zh-cn/使用手册.md) 18 | 19 | ### 运行最低要求 20 | 21 | * Windows 7 x64 或更高版本的操作系统。 22 | * .NET 6.0 运行时,[官方下载](https://dotnet.microsoft.com/download/dotnet/current/runtime)。 23 | 24 | ### 注意事项 25 | 26 | * 本程序不支持国服以外的明日方舟区服。 27 | * 原理上,使用本程序没有被判定为作弊的风险;但是作者不对使用此程序造成的任何损失负责。 28 | 29 | ## 关于 30 | 31 | ![Logo](Docs/Resources/Logo.png) 32 | 这个项目是实验性的,主要作个人学习用途,在使用中如果有问题欢迎提出 issue。 33 | 34 | ### 鸣谢 35 | 36 | JetBrains 37 | JetBrains 为本项目提供了免费的开源项目开发用许可证,这对本项目的开发有很大的帮助。 38 | -------------------------------------------------------------------------------- /Source/Auto Arknights.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29519.161 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auto Arknights Core", "Core\Auto Arknights Core.csproj", "{AF3D1DDF-8CC0-4A1C-A3D2-DB3875B8C3ED}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auto Arknights CLI", "CLI\Auto Arknights CLI.csproj", "{0C76DBB2-8CCF-4860-8C28-C542E74666BE}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.Core", "Test\Core\Test.Core.csproj", "{EB73EF68-9ED4-409F-9E2E-3A830E7A249A}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x64 = Debug|x64 16 | Release|Any CPU = Release|Any CPU 17 | Release|x64 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {AF3D1DDF-8CC0-4A1C-A3D2-DB3875B8C3ED}.Debug|Any CPU.ActiveCfg = Debug|x64 21 | {AF3D1DDF-8CC0-4A1C-A3D2-DB3875B8C3ED}.Debug|x64.ActiveCfg = Debug|x64 22 | {AF3D1DDF-8CC0-4A1C-A3D2-DB3875B8C3ED}.Debug|x64.Build.0 = Debug|x64 23 | {AF3D1DDF-8CC0-4A1C-A3D2-DB3875B8C3ED}.Release|Any CPU.ActiveCfg = Release|x64 24 | {AF3D1DDF-8CC0-4A1C-A3D2-DB3875B8C3ED}.Release|x64.ActiveCfg = Release|x64 25 | {AF3D1DDF-8CC0-4A1C-A3D2-DB3875B8C3ED}.Release|x64.Build.0 = Release|x64 26 | {0C76DBB2-8CCF-4860-8C28-C542E74666BE}.Debug|Any CPU.ActiveCfg = Debug|x64 27 | {0C76DBB2-8CCF-4860-8C28-C542E74666BE}.Debug|x64.ActiveCfg = Debug|x64 28 | {0C76DBB2-8CCF-4860-8C28-C542E74666BE}.Debug|x64.Build.0 = Debug|x64 29 | {0C76DBB2-8CCF-4860-8C28-C542E74666BE}.Release|Any CPU.ActiveCfg = Release|x64 30 | {0C76DBB2-8CCF-4860-8C28-C542E74666BE}.Release|x64.ActiveCfg = Release|x64 31 | {0C76DBB2-8CCF-4860-8C28-C542E74666BE}.Release|x64.Build.0 = Release|x64 32 | {EB73EF68-9ED4-409F-9E2E-3A830E7A249A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {EB73EF68-9ED4-409F-9E2E-3A830E7A249A}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {EB73EF68-9ED4-409F-9E2E-3A830E7A249A}.Debug|x64.ActiveCfg = Debug|Any CPU 35 | {EB73EF68-9ED4-409F-9E2E-3A830E7A249A}.Debug|x64.Build.0 = Debug|Any CPU 36 | {EB73EF68-9ED4-409F-9E2E-3A830E7A249A}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {EB73EF68-9ED4-409F-9E2E-3A830E7A249A}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {EB73EF68-9ED4-409F-9E2E-3A830E7A249A}.Release|x64.ActiveCfg = Release|Any CPU 39 | {EB73EF68-9ED4-409F-9E2E-3A830E7A249A}.Release|x64.Build.0 = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(SolutionProperties) = preSolution 42 | HideSolutionNode = FALSE 43 | EndGlobalSection 44 | GlobalSection(ExtensibilityGlobals) = postSolution 45 | SolutionGuid = {58448B8B-EA41-496B-B26E-AD09835AD4DD} 46 | EndGlobalSection 47 | EndGlobal 48 | -------------------------------------------------------------------------------- /Source/Auto Arknights.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | DO_NOT_SHOW -------------------------------------------------------------------------------- /Source/CLI/Auto Arknights CLI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | REVUnit.AutoArknights.CLI 6 | enable 7 | Exe 8 | x64 9 | false 10 | 11 | 12 | 13 | Rcmcpe 14 | Copyright © Rcmcpe 2021 15 | Auto Arknights CLI 16 | zh-CN 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | True 43 | True 44 | Resources.resx 45 | 46 | 47 | 48 | 49 | 50 | ResXFileCodeGenerator 51 | Resources.Designer.cs 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Source/CLI/Auto Arknights CLI.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  5 | Yes -------------------------------------------------------------------------------- /Source/CLI/Build-Artifact.ps1: -------------------------------------------------------------------------------- 1 | if (Test-Path .\artifact\) { Remove-Item -Recurse .\artifact\ } 2 | 3 | git version 4 | $publishCommand = 'dotnet publish -o .\artifact -c Release -p:PublishProfile=win-x64 -p:DebugType=none -p:DebugSymbols=false' 5 | if ($?) 6 | { 7 | $tag = git describe --tags --abbrev=0 8 | $version = $tag.Substring(1) 9 | $commit = git -c log.showSignature=false log --format=format:%h -n 1 10 | $infoVersion = "$version+$commit" 11 | $publishCommand += " -p:Version=$infoVersion" 12 | } 13 | Invoke-Expression $publishCommand -------------------------------------------------------------------------------- /Source/CLI/Cli.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CommandLine; 3 | using System.CommandLine.Invocation; 4 | using System.Diagnostics; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Configuration; 8 | using REVUnit.AutoArknights.CLI.Properties; 9 | using REVUnit.AutoArknights.Core; 10 | using REVUnit.Crlib.Extensions; 11 | using Serilog; 12 | using Console = System.Console; 13 | using Process = System.Diagnostics.Process; 14 | 15 | namespace REVUnit.AutoArknights.CLI 16 | { 17 | public class Cli 18 | { 19 | private readonly IConfiguration _config; 20 | private readonly RootCommand _rootCommand; 21 | 22 | private Game? _game; 23 | 24 | public Cli(IConfiguration config) 25 | { 26 | _config = config; 27 | 28 | _rootCommand = new RootCommand("Interactive mode") 29 | { 30 | new Option("--no-logo"), 31 | 32 | new Command("start") 33 | {Handler = CommandHandler.Create(StartGame)}, 34 | new Command("stop") 35 | {Handler = CommandHandler.Create(StopGame)}, 36 | new Command("combat") 37 | { 38 | new Argument("--times").Also(x => x.AddAlias("-t")), 39 | new Option("--level").Also(x => x.AddAlias("-l")), 40 | new Option("--wait").Also(x => x.AddAlias("-w")), 41 | new Option("--potions").Also(x => x.AddAlias("-p")), 42 | new Option("--originites").Also(x => x.AddAlias("-o")) 43 | }.Also(c => c.Handler = CommandHandler.Create(Combat)), 44 | new Command("claim") 45 | { 46 | new Command("task") 47 | {Handler = CommandHandler.Create(() => _game!.ClaimTasks())}, 48 | new Command("cp") 49 | {Handler = CommandHandler.Create(() => _game!.Infrastructure.ClaimCreditPoints())}, 50 | new Command("infra") 51 | {Handler = CommandHandler.Create(() => _game!.Infrastructure.ClaimProducts())} 52 | } 53 | }.Also(c => c.Handler = CommandHandler.Create(Interactive)); 54 | } 55 | 56 | public async Task Run(string[] args) 57 | { 58 | await _rootCommand.InvokeAsync(args); 59 | } 60 | 61 | private async Task AttachGame() 62 | { 63 | var adbDevice = new AdbDevice(_config["Adb:ExecutablePath"]); 64 | await adbDevice.Connect(_config["Adb:DeviceSerial"]); 65 | 66 | _game = await Game.FromDevice(adbDevice); 67 | } 68 | 69 | private async Task Combat(int times, string? levelName, bool wait, bool usePotions, int useOriginitesCount) 70 | { 71 | await _game!.Combat.Run(levelName == null ? null : Level.FromName(levelName), 72 | new LevelCombatSettings(times, wait, usePotions, useOriginitesCount)); 73 | } 74 | 75 | private async Task Interactive(bool noLogo) 76 | { 77 | if (!noLogo) 78 | { 79 | Console.WriteLine(Resources.Logo); 80 | Console.WriteLine(); 81 | await AttachGame(); 82 | Console.Clear(); 83 | } 84 | else 85 | { 86 | await AttachGame(); 87 | } 88 | 89 | while (true) 90 | { 91 | // ReSharper disable once LocalizableElement 92 | Console.Write("> "); 93 | string? args = Console.ReadLine(); 94 | if (string.IsNullOrWhiteSpace(args)) 95 | { 96 | Log.Warning("未输入指令"); 97 | continue; 98 | } 99 | 100 | await _rootCommand.InvokeAsync(args); 101 | } 102 | // ReSharper disable once FunctionNeverReturns 103 | } 104 | 105 | private static async Task RunCommandLine(string commandLine) 106 | { 107 | Process? process = 108 | Process.Start(new ProcessStartInfo("cmd.exe", "/c " + commandLine) {CreateNoWindow = true}); 109 | if (process == null) throw new Exception("未能启动 cmd"); 110 | 111 | var cts = new CancellationTokenSource(5000); 112 | Task waitForExit = process.WaitForExitAsync(cts.Token); 113 | await waitForExit; 114 | if (waitForExit.IsCanceled) 115 | { 116 | throw new Exception("命令行运行超时"); 117 | } 118 | } 119 | 120 | private Task StartGame() 121 | { 122 | //TODO Throw if config value empty 123 | return RunCommandLine(_config["Remote:StartCommandLine"]); 124 | } 125 | 126 | private Task StopGame() 127 | { 128 | return RunCommandLine(_config["Remote:CloseCommandLine"]); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /Source/CLI/Entry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Configuration; 6 | using REVUnit.AutoArknights.CLI.Properties; 7 | using Serilog; 8 | using Serilog.Sinks.SystemConsole.Themes; 9 | 10 | namespace REVUnit.AutoArknights.CLI 11 | { 12 | public static class Entry 13 | { 14 | private static IConfiguration? _config; 15 | private const string ConfigFilePath = "Auto Arknights CLI.config.yml"; 16 | 17 | [MemberNotNull(nameof(_config))] 18 | public static void InitConfig() 19 | { 20 | if (!File.Exists(ConfigFilePath)) 21 | { 22 | File.WriteAllBytes(ConfigFilePath, Resources.DefaultConfig); 23 | } 24 | 25 | _config = new ConfigurationBuilder() 26 | .SetBasePath(AppContext.BaseDirectory) 27 | .AddYamlFile(ConfigFilePath) 28 | .Build(); 29 | } 30 | 31 | public static void InitLogger() 32 | { 33 | Log.Logger = new LoggerConfiguration() 34 | .MinimumLevel.Verbose() 35 | #if DEBUG 36 | .WriteTo.Console(theme: AnsiConsoleTheme.Code) 37 | #else 38 | .WriteTo.Console(theme: AnsiConsoleTheme.Code, 39 | restrictedToMinimumLevel: _config.GetValue("Log:Level", Serilog.Events.LogEventLevel.Information)) 40 | #endif 41 | .WriteTo.Debug() 42 | .WriteTo.File($"Log/{DateTime.Now:yyyy-MM-dd HH.mm.ss}.log") 43 | .CreateLogger(); 44 | } 45 | 46 | public static async Task Main(string[] args) 47 | { 48 | InitConfig(); 49 | InitLogger(); 50 | 51 | var cli = new Cli(_config); 52 | #if DEBUG 53 | await cli.Run(args); 54 | #else 55 | try 56 | { 57 | await cli.Run(args); 58 | } 59 | catch (Exception e) 60 | { 61 | Log.Fatal(e, "出现致命错误"); 62 | Console.ReadKey(true); 63 | } 64 | #endif 65 | Log.CloseAndFlush(); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Source/CLI/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace REVUnit.AutoArknights.CLI.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("REVUnit.AutoArknights.CLI.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Byte[]. 65 | /// 66 | internal static byte[] DefaultConfig { 67 | get { 68 | object obj = ResourceManager.GetObject("DefaultConfig", resourceCulture); 69 | return ((byte[])(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized string similar to 75 | /// @@@#%% #/ 76 | /// @@@### @ @@@/ %%******=- @@@. ## 77 | /// @@@ @@@ @@@=== @@@/ (@@@ @@@ @@ 78 | /// @@@ @@@ @@@ =#@@@ @@@/ (@@@ @( @@@ (@* 79 | /// @@@& @@@ @@@&& @@@ @@@/ (@@@ @@@ @@@ @@% @@@ 80 | /// [rest of string was truncated]";. 81 | /// 82 | internal static string Logo { 83 | get { 84 | return ResourceManager.GetString("Logo", resourceCulture); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Source/CLI/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | 139 | 140 | 141 | 142 | 143 | 144 | ..\Resources\Auto Arknights CLI.config.yml;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 145 | 146 | 147 | -------------------------------------------------------------------------------- /Source/CLI/Resources/Auto Arknights CLI.config.yml: -------------------------------------------------------------------------------- 1 | Remote: 2 | Mode: Adb 3 | CloseCommandLine: '"C:\Program Files\Microvirt\MEmu\memuc.exe" stop -i 0' 4 | Adb: 5 | ExecutablePath: adb\adb.exe 6 | DeviceSerial: 127.0.0.1:21503 -------------------------------------------------------------------------------- /Source/CLI/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.101", 4 | "rollForward": "latestMinor" 5 | } 6 | } -------------------------------------------------------------------------------- /Source/Core/Adb/AdbWinApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Adb/AdbWinApi.dll -------------------------------------------------------------------------------- /Source/Core/Adb/AdbWinUsbApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Adb/AdbWinUsbApi.dll -------------------------------------------------------------------------------- /Source/Core/Adb/adb.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Adb/adb.exe -------------------------------------------------------------------------------- /Source/Core/AdbDevice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using OpenCvSharp; 12 | using Serilog; 13 | 14 | namespace REVUnit.AutoArknights.Core 15 | { 16 | public class AdbDevice : IDevice 17 | { 18 | private const int DefaultBufferSize = 1024; 19 | 20 | private int _serverPort; 21 | private bool _serverStarted; 22 | 23 | public AdbDevice(string executable) 24 | { 25 | Executable = executable; 26 | } 27 | 28 | public string Executable { get; set; } 29 | public string? TargetSerial { get; set; } 30 | 31 | public async Task Back() 32 | { 33 | await KeyEvent("KEYCODE_BACK"); 34 | } 35 | 36 | public async Task Click(Point point) 37 | { 38 | await ThrowIfServiceUnavailable(); 39 | await Execute($"shell input tap {point.X} {point.Y}"); 40 | } 41 | 42 | public async Task GetResolution() 43 | { 44 | await ThrowIfServiceUnavailable(); 45 | string result = Encoding.UTF8.GetString((await Execute("shell wm size")).stdOut); 46 | Match match = Regex.Match(result, @"Physical size: (\d+)x(\d+)"); 47 | if (!match.Success) throw new Exception("无法获取设备分辨率信息"); 48 | 49 | int a = int.Parse(match.Groups[1].Value); 50 | int b = int.Parse(match.Groups[2].Value); 51 | return new Size(Math.Max(a, b), Math.Min(a, b)); 52 | } 53 | 54 | public async Task GetScreenshot() 55 | { 56 | Mat mat = Cv2.ImDecode((await Execute("exec-out screencap -p", 5 * 1024 * 1024, 0)).stdOut, 57 | ImreadModes.Color); 58 | if (mat.Empty()) 59 | { 60 | throw new Exception("获取到空截图"); 61 | } 62 | 63 | return mat; 64 | } 65 | 66 | public async Task Connect(string targetSerial) 67 | { 68 | Log.Information("正在连接到 ADB 设备 {Target}", targetSerial); 69 | 70 | await KillConnectedProcesses(targetSerial); 71 | 72 | if (!_serverStarted) 73 | { 74 | StartServer(); 75 | } 76 | 77 | await Execute($"connect {targetSerial}", targeted: false); 78 | if (!await GetDeviceOnline()) 79 | { 80 | throw new Exception("重试失败,无法连接"); 81 | } 82 | 83 | TargetSerial = targetSerial; 84 | 85 | Log.Information("连接设备成功"); 86 | } 87 | 88 | // https://developer.android.com/reference/android/view/KeyEvent 89 | public async Task KeyEvent(string keyCodeOrName) 90 | { 91 | await ThrowIfServiceUnavailable(); 92 | await Execute($"shell input keyevent {keyCodeOrName}"); 93 | } 94 | 95 | public void StartServer() 96 | { 97 | Log.Information("正在启动 ADB 服务器"); 98 | 99 | int port = GetFreeTcpPort(); 100 | 101 | var adbServerProcess = new Process 102 | { 103 | StartInfo = 104 | new ProcessStartInfo(Executable, $"nodaemon server -P {port}") {CreateNoWindow = true}, 105 | EnableRaisingEvents = true 106 | }; 107 | 108 | if (!adbServerProcess.Start()) 109 | { 110 | throw new Exception("ADB 服务器未能正常启动"); 111 | } 112 | 113 | _serverPort = port; 114 | adbServerProcess.Exited += (_, _) => throw new Exception("ADB 服务器进程意外停止"); 115 | ChildProcessTracker.Track(adbServerProcess); 116 | 117 | Log.Information("已启动 ADB 服务器,端口:{Port}", _serverPort); 118 | 119 | _serverStarted = true; 120 | } 121 | 122 | private async Task<(byte[] stdOut, byte[] stdErr)> Execute(string arguments, 123 | int stdOutBufferSize = DefaultBufferSize, 124 | int stdErrBufferSize = DefaultBufferSize, 125 | bool waitForExit = false, bool targeted = true) 126 | { 127 | Log.Debug("正在执行 ADB 指令:{$Param}", arguments); 128 | 129 | string adbSystemArgs = $"-P {_serverPort} "; 130 | if (targeted) 131 | { 132 | adbSystemArgs += $"-s {TargetSerial} "; 133 | } 134 | 135 | arguments = adbSystemArgs + arguments; 136 | 137 | using var process = new Process 138 | { 139 | StartInfo = new ProcessStartInfo(Executable, arguments) 140 | { 141 | CreateNoWindow = true, 142 | RedirectStandardOutput = true, 143 | StandardOutputEncoding = Encoding.UTF8, 144 | RedirectStandardError = true, 145 | StandardErrorEncoding = Encoding.UTF8 146 | } 147 | }; 148 | 149 | process.Start(); 150 | ChildProcessTracker.Track(process); 151 | 152 | byte[]? stdOut = null; 153 | byte[]? stdErr = null; 154 | 155 | if (stdOutBufferSize > 0) 156 | stdOut = await ReadToEnd(process.StandardOutput.BaseStream, stdOutBufferSize); 157 | 158 | if (stdErrBufferSize > 0) 159 | stdErr = await ReadToEnd(process.StandardError.BaseStream, stdErrBufferSize); 160 | 161 | if (waitForExit) 162 | { 163 | using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); 164 | await process.WaitForExitAsync(cts.Token); 165 | } 166 | 167 | return (stdOut!, stdErr!); 168 | } 169 | 170 | private async Task GetDeviceOnline() 171 | { 172 | string state = Encoding.UTF8.GetString((await Execute("get-state", targeted: false)).stdOut).Trim(); 173 | Log.Debug("检测到设备状态: \"{State}\"", state); 174 | return state == "device"; 175 | } 176 | 177 | private static int GetFreeTcpPort() 178 | { 179 | var listener = new TcpListener(IPAddress.Loopback, 0); 180 | listener.Start(); 181 | int port = ((IPEndPoint) listener.LocalEndpoint).Port; 182 | listener.Stop(); 183 | return port; 184 | } 185 | 186 | private static async Task KillConnectedProcesses(string targetSerial) 187 | { 188 | var netstat = new Process 189 | { 190 | StartInfo = new ProcessStartInfo("netstat", "-no") 191 | {CreateNoWindow = true, RedirectStandardOutput = true} 192 | }; 193 | 194 | netstat.Start(); 195 | 196 | string? line; 197 | while ((line = await netstat.StandardOutput.ReadLineAsync()) != null) 198 | { 199 | string[] split = Regex.Split(line, @"\s+").Where(s => !string.IsNullOrWhiteSpace(s)).ToArray(); 200 | if (split.Length != 5 || split[2] != targetSerial) 201 | { 202 | continue; 203 | } 204 | 205 | int pid = int.Parse(split[4]); 206 | if (pid == 0) 207 | { 208 | continue; 209 | } 210 | 211 | var process = Process.GetProcessById(pid); 212 | try 213 | { 214 | process.Kill(); 215 | } 216 | catch (Exception e) 217 | { 218 | Log.Debug(e, "尝试杀死PID为{PID}的程序时出错", pid); 219 | } 220 | } 221 | 222 | var cts = new CancellationTokenSource(1000); 223 | await netstat.WaitForExitAsync(cts.Token); 224 | } 225 | 226 | private static async Task ReadToEnd(Stream stream, int bufferSize) 227 | { 228 | byte[] buffer = new byte[bufferSize]; 229 | 230 | int read; 231 | var totalLen = 0; 232 | while ((read = await stream.ReadAsync(buffer.AsMemory(totalLen, bufferSize - totalLen))) > 0) 233 | totalLen += read; 234 | 235 | Array.Resize(ref buffer, totalLen); 236 | return buffer; 237 | } 238 | 239 | private async Task ThrowIfServiceUnavailable() 240 | { 241 | if (!_serverStarted || !await GetDeviceOnline()) 242 | { 243 | throw new Exception("ADB 连接意外中断"); 244 | } 245 | } 246 | } 247 | } -------------------------------------------------------------------------------- /Source/Core/Assets/Combat/AutoDep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Combat/AutoDep.png -------------------------------------------------------------------------------- /Source/Core/Assets/Combat/Begin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Combat/Begin.png -------------------------------------------------------------------------------- /Source/Core/Assets/Combat/Start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Combat/Start.png -------------------------------------------------------------------------------- /Source/Core/Assets/Combat/TakeOver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Combat/TakeOver.png -------------------------------------------------------------------------------- /Source/Core/Assets/Combat/UseOriginite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Combat/UseOriginite.png -------------------------------------------------------------------------------- /Source/Core/Assets/General/Yes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/General/Yes.png -------------------------------------------------------------------------------- /Source/Core/Assets/Home/Friends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Home/Friends.png -------------------------------------------------------------------------------- /Source/Core/Assets/Home/Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Home/Settings.png -------------------------------------------------------------------------------- /Source/Core/Assets/Home/Tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Home/Tasks.png -------------------------------------------------------------------------------- /Source/Core/Assets/Info.json: -------------------------------------------------------------------------------- 1 | { 2 | "TargetResolution": { 3 | "Width": 1920, 4 | "Height": 1080 5 | } 6 | } -------------------------------------------------------------------------------- /Source/Core/Assets/Infra/VisitNext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Infra/VisitNext.png -------------------------------------------------------------------------------- /Source/Core/Assets/Infra/VisitNextGrey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Infra/VisitNextGrey.png -------------------------------------------------------------------------------- /Source/Core/Assets/Menu/Enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Menu/Enter.png -------------------------------------------------------------------------------- /Source/Core/Assets/Profile/FriendsList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Profile/FriendsList.png -------------------------------------------------------------------------------- /Source/Core/Assets/Profile/Visit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Profile/Visit.png -------------------------------------------------------------------------------- /Source/Core/Assets/Tasks/ClaimAll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Tasks/ClaimAll.png -------------------------------------------------------------------------------- /Source/Core/Assets/Tasks/Daily.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Tasks/Daily.png -------------------------------------------------------------------------------- /Source/Core/Assets/Tasks/Weekly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Tasks/Weekly.png -------------------------------------------------------------------------------- /Source/Core/Assets/Tesseract/TrainedData/chi_sim.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Tesseract/TrainedData/chi_sim.traineddata -------------------------------------------------------------------------------- /Source/Core/Assets/Tesseract/TrainedData/chi_sim_vert.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Tesseract/TrainedData/chi_sim_vert.traineddata -------------------------------------------------------------------------------- /Source/Core/Assets/Tesseract/TrainedData/eng.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Core/Assets/Tesseract/TrainedData/eng.traineddata -------------------------------------------------------------------------------- /Source/Core/Assets/Tesseract/UserPatterns/CurrentSanity: -------------------------------------------------------------------------------- 1 | \d\*/\d\* -------------------------------------------------------------------------------- /Source/Core/Assets/Tesseract/UserPatterns/RequiredSanity: -------------------------------------------------------------------------------- 1 | -\d* -------------------------------------------------------------------------------- /Source/Core/Auto Arknights Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | REVUnit.AutoArknights.Core 6 | enable 7 | x64 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PreserveNewest 23 | 24 | 25 | PreserveNewest 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Source/Core/Auto Arknights Core.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  5 | True 6 | True -------------------------------------------------------------------------------- /Source/Core/CV/Feature2DType.cs: -------------------------------------------------------------------------------- 1 | namespace REVUnit.AutoArknights.Core.CV 2 | { 3 | public enum Feature2DType 4 | { 5 | FastFreak, 6 | Sift 7 | } 8 | } -------------------------------------------------------------------------------- /Source/Core/CV/FeatureDetector.Cache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | using OpenCvSharp; 7 | 8 | namespace REVUnit.AutoArknights.Core.CV 9 | { 10 | public class CacheLoadException : Exception 11 | { 12 | public CacheLoadException(string cacheFilePath, string node, Exception? innerException = null) : 13 | base($"Error loading node {node} in cache file {cacheFilePath}", innerException) 14 | { 15 | } 16 | } 17 | 18 | public partial class FeatureDetector 19 | { 20 | private class Cache : IDisposable 21 | { 22 | private readonly string _cacheDirPath; 23 | private readonly Dictionary _dict = new(); 24 | 25 | public Cache(string cacheDirPath) 26 | { 27 | _cacheDirPath = cacheDirPath; 28 | Directory.CreateDirectory(cacheDirPath); 29 | 30 | foreach (string cacheFile in Directory.EnumerateFiles(cacheDirPath, "*.json.gz")) 31 | { 32 | using var storage = new FileStorage(cacheFile, FileStorage.Modes.Read); 33 | 34 | T Read(string node, Func reader) 35 | { 36 | FileNode fileNode = storage![node] ?? throw new CacheLoadException(cacheFile, node); 37 | try 38 | { 39 | return reader(fileNode); 40 | } 41 | catch (Exception e) 42 | { 43 | throw new CacheLoadException(cacheDirPath, node, e); 44 | } 45 | } 46 | 47 | KeyPoint[] keyPoints = Read("Keypoints", x => x.ReadKeyPoints()); 48 | Mat descriptors = Read("Descriptors", x => x.ReadMat()); 49 | int originWidth = Read("MatWidth", x => x.ReadInt()); 50 | int originHeight = Read("MatHeight", x => x.ReadInt()); 51 | Feature2DType type = Read("Type", x => Enum.Parse(x.ReadString())); 52 | 53 | _dict.Add(Path.GetFileNameWithoutExtension(cacheFile), 54 | new MatFeature(keyPoints, descriptors, originWidth, originHeight, type)); 55 | } 56 | } 57 | 58 | public MatFeature? this[Mat mat] 59 | { 60 | get => Get(mat); 61 | set => Add(mat, value ?? throw new ArgumentNullException(nameof(value))); 62 | } 63 | 64 | public void Dispose() 65 | { 66 | foreach (MatFeature matFeature in _dict.Values) matFeature.Dispose(); 67 | } 68 | 69 | private void Add(Mat mat, MatFeature feature) 70 | { 71 | string md5 = GetMd5(mat); 72 | 73 | _dict.Add(md5, feature); 74 | 75 | using var storage = 76 | new FileStorage(Path.Combine(_cacheDirPath, $"{md5}.json.gz"), FileStorage.Modes.Write); 77 | storage.Write("Keypoints", feature.KeyPoints); 78 | storage.Write("Descriptors", feature.Descriptors); 79 | storage.Write("OriginWidth", mat.Width); 80 | storage.Write("OriginHeight", mat.Height); 81 | storage.Write("Type", feature.Type.ToString()); 82 | } 83 | 84 | private MatFeature? Get(Mat mat) 85 | { 86 | _dict.TryGetValue(GetMd5(mat), out MatFeature? result); 87 | return result; 88 | } 89 | 90 | private static unsafe string GetMd5(Mat mat) 91 | { 92 | long total = mat.Total(); 93 | var data = new Span(mat.Data.ToPointer(), (int) total); 94 | 95 | return GetMd5(data); 96 | } 97 | 98 | private static string GetMd5(Span data) 99 | { 100 | byte[] hash = MD5.HashData(data); 101 | 102 | var sb = new StringBuilder(); 103 | foreach (byte b in hash) 104 | { 105 | sb.Append(b.ToString("x2")); 106 | } 107 | 108 | return sb.ToString(); 109 | } 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /Source/Core/CV/FeatureDetector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenCvSharp; 3 | using OpenCvSharp.Features2D; 4 | using OpenCvSharp.XFeatures2D; 5 | 6 | namespace REVUnit.AutoArknights.Core.CV 7 | { 8 | public partial class FeatureDetector : IDisposable 9 | { 10 | private readonly Cache? _cache; 11 | private readonly Lazy _fast = new(() => FastFeatureDetector.Create()); 12 | private readonly Lazy _freak = new(() => FREAK.Create()); 13 | private readonly Lazy _sift = new(() => SIFT.Create()); 14 | 15 | public FeatureDetector(string? cacheDirPath = null) 16 | { 17 | if (cacheDirPath != null) _cache = new Cache(cacheDirPath); 18 | } 19 | 20 | public void Dispose() 21 | { 22 | _cache?.Dispose(); 23 | DisposeIfCreated(_fast); 24 | DisposeIfCreated(_freak); 25 | DisposeIfCreated(_sift); 26 | } 27 | 28 | public MatFeature Detect(Mat mat, Feature2DType type) 29 | { 30 | var (detector, descriptor) = GetFeature2D(type); 31 | 32 | KeyPoint[] keyPoints = detector.Detect(mat); 33 | 34 | var descriptors = new Mat(); 35 | descriptor.Compute(mat, ref keyPoints, descriptors); 36 | 37 | return new MatFeature(keyPoints, descriptors, mat.Width, mat.Height, type); 38 | } 39 | 40 | public MatFeature DetectCached(Mat mat, Feature2DType type) 41 | { 42 | if (_cache == null) throw new InvalidOperationException("必须提供缓存保存文件夹才能使用缓存"); 43 | 44 | MatFeature? result = _cache[mat]; 45 | if (result != null) return result; 46 | 47 | result = Detect(mat, type); 48 | _cache[mat] = result; 49 | 50 | return result; 51 | } 52 | 53 | private static void DisposeIfCreated(Lazy lazyFeature2D) 54 | { 55 | if (lazyFeature2D.IsValueCreated) 56 | { 57 | lazyFeature2D.Value.Dispose(); 58 | } 59 | } 60 | 61 | private (Feature2D detector, Feature2D descriptor) GetFeature2D(Feature2DType feature2DType) 62 | { 63 | return feature2DType switch 64 | { 65 | Feature2DType.FastFreak => (_fast.Value, _freak.Value), 66 | Feature2DType.Sift => (_sift.Value, _sift.Value), 67 | _ => throw new ArgumentOutOfRangeException(nameof(feature2DType), feature2DType, null) 68 | }; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /Source/Core/CV/FeatureMatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using OpenCvSharp; 5 | 6 | namespace REVUnit.AutoArknights.Core.CV 7 | { 8 | public class FeatureMatcher : IDisposable 9 | { 10 | private readonly BFMatcher _matcher = new(); 11 | public float NearestNeighborThreshold { get; set; } = 0.75f; 12 | 13 | public void Dispose() 14 | { 15 | _matcher.Dispose(); 16 | } 17 | 18 | public (int matchCount, Quadrilateral32 region) Match(MatFeature modelF, MatFeature sceneF) 19 | { 20 | if (modelF.Type != sceneF.Type) 21 | throw new ArgumentException($"{nameof(modelF)}和{nameof(sceneF)}的类型不匹配"); 22 | if (modelF.KeyPoints.Length == 0 || modelF.Descriptors.Empty() || sceneF.KeyPoints.Length == 0 || 23 | sceneF.Descriptors.Empty()) 24 | return default; 25 | 26 | DMatch[][] matches = _matcher.KnnMatch(modelF.Descriptors, sceneF.Descriptors, 2); 27 | 28 | using var mask = new Mat(matches.Length, 1, MatType.CV_8U); 29 | var mPoints = new List(); 30 | var sPoints = new List(); 31 | 32 | for (var i = 0; i < matches.Length; i++) 33 | { 34 | DMatch[] dim = matches[i]; 35 | DMatch a = dim[0]; 36 | DMatch b = dim[1]; 37 | if (a.Distance < NearestNeighborThreshold * b.Distance) 38 | { 39 | mPoints.Add(modelF.KeyPoints[a.QueryIdx].Pt); 40 | sPoints.Add(sceneF.KeyPoints[a.TrainIdx].Pt); 41 | mask.Set(i, true); 42 | } 43 | else 44 | { 45 | mask.Set(i, false); 46 | } 47 | } 48 | 49 | if (mask.CountNonZero() < 4) 50 | { 51 | return default; 52 | } 53 | 54 | int nonZero = VoteForSizeAndOrientation(modelF.KeyPoints, sceneF.KeyPoints, matches, mask, 1.5f, 20); 55 | if (nonZero < 4) return (nonZero, Quadrilateral32.Empty); 56 | 57 | using Mat homography = Cv2.FindHomography(InputArray.Create(mPoints), InputArray.Create(sPoints), 58 | HomographyMethods.Ransac); 59 | 60 | if (homography.Empty()) return default; 61 | 62 | Point2f[] mCorners = 63 | { 64 | new(0, 0), new(modelF.MatWidth, 0), new(modelF.MatWidth, modelF.MatHeight), new(0, modelF.MatHeight) 65 | }; 66 | 67 | Point2f[] mCornersT = Cv2.PerspectiveTransform(mCorners, homography); 68 | 69 | // if (circumRect.Width < 10 || circumRect.Height < 10 || circumRect.X < 0 && circumRect.Y < 0 || 70 | // circumRect.Height > sceneF.MatHeight && circumRect.Width > sceneF.MatWidth) 71 | // return default; 72 | return (nonZero, Quadrilateral32.FromVertices(mCornersT)); 73 | } 74 | 75 | private static int VoteForSizeAndOrientation(KeyPoint[] modelKeyPoints, KeyPoint[] sceneKeyPoints, 76 | DMatch[][] matches, Mat mask, float scaleIncrement, 77 | int rotationBins) 78 | { 79 | var logScale = new List(); 80 | var rotations = new List(); 81 | double maxS = -1.0e-10f; 82 | double minS = 1.0e10f; 83 | 84 | for (var i = 0; i < mask.Rows; i++) 85 | { 86 | if (!mask.At(i)) continue; 87 | 88 | KeyPoint modelKeyPoint = modelKeyPoints[i]; 89 | KeyPoint sceneKeyPoint = sceneKeyPoints[matches[i][0].TrainIdx]; 90 | double s = Math.Log10(sceneKeyPoint.Size / modelKeyPoint.Size); 91 | logScale.Add((float) s); 92 | maxS = s > maxS ? s : maxS; 93 | minS = s < minS ? s : minS; 94 | 95 | double r = sceneKeyPoint.Angle - modelKeyPoint.Angle; 96 | r = r < 0.0f ? r + 360.0f : r; 97 | rotations.Add((float) r); 98 | } 99 | 100 | var scaleBinSize = (int) Math.Ceiling((maxS - minS) / Math.Log10(scaleIncrement)); 101 | if (scaleBinSize < 2) scaleBinSize = 2; 102 | 103 | float[] scaleRanges = {(float) minS, (float) (minS + scaleBinSize + Math.Log10(scaleIncrement))}; 104 | 105 | using var flagsMat = new Mat(logScale.Count, 1); 106 | using var hist = new Mat(); 107 | 108 | int[] histSize = {scaleBinSize, rotationBins}; 109 | int[] channels = {0, 1}; 110 | Rangef[] ranges = {new(scaleRanges[0], scaleRanges[1]), new(rotations.Min(), rotations.Max())}; 111 | 112 | using var scalesMat = new Mat(logScale.Count, 1, logScale.ToArray()); 113 | using var rotationsMat = new Mat(rotations.Count, 1, rotations.ToArray()); 114 | Mat[] scalesAndRotations = {scalesMat, rotationsMat}; 115 | 116 | Cv2.CalcHist(scalesAndRotations, channels, null, hist, 2, histSize, ranges); 117 | Cv2.MinMaxLoc(hist, out _, out double maxVal); 118 | 119 | Cv2.Threshold(hist, hist, maxVal * 0.5, 0, ThresholdTypes.Tozero); 120 | Cv2.CalcBackProject(scalesAndRotations, channels, hist, flagsMat, ranges); 121 | 122 | return flagsMat.CountNonZero(); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /Source/Core/CV/FeatureRegistration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenCvSharp; 3 | 4 | namespace REVUnit.AutoArknights.Core.CV 5 | { 6 | public class FeatureRegistration : ImageRegistration, IDisposable 7 | { 8 | private readonly bool _useCache; 9 | 10 | public FeatureRegistration(string? cacheDirPath = null) 11 | { 12 | _useCache = cacheDirPath != null; 13 | FeatureDetector = new FeatureDetector(cacheDirPath); 14 | FeatureMatcher = new FeatureMatcher(); 15 | } 16 | 17 | public FeatureDetector FeatureDetector { get; } 18 | public FeatureMatcher FeatureMatcher { get; } 19 | 20 | 21 | public void Dispose() 22 | { 23 | FeatureDetector.Dispose(); 24 | FeatureMatcher.Dispose(); 25 | } 26 | 27 | public override RegistrationResult[] Register(Mat model, Mat scene, int minMatchCount) 28 | { 29 | // TODO implement minMatchCount 30 | return new[] {Register(model, scene, Feature2DType.FastFreak)}; 31 | } 32 | 33 | public RegistrationResult Register(Mat model, Mat scene, Feature2DType type) 34 | { 35 | MatFeature? sceneFeature = null; 36 | MatFeature? modelFeature = null; 37 | try 38 | { 39 | modelFeature = _useCache 40 | ? FeatureDetector.DetectCached(model, type) 41 | : FeatureDetector.Detect(model, type); 42 | sceneFeature = FeatureDetector.Detect(scene, type); 43 | (double matchCount, Quadrilateral32 region) = FeatureMatcher.Match(modelFeature, sceneFeature); 44 | return new RegistrationResult(region, matchCount > 4 ? 1 : 0); 45 | } 46 | finally 47 | { 48 | if (!_useCache) modelFeature?.Dispose(); 49 | sceneFeature?.Dispose(); 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Source/Core/CV/ImageLocator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | REVUnit.ImageLocator 6 | enable 7 | 8 | 9 | 10 | Rcmcpe 11 | REVUnit 12 | en-US 13 | ImageLocator 14 | An image matching library based on OpenCV. 15 | REVUnit ©2020 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Source/Core/CV/ImageRegistration.cs: -------------------------------------------------------------------------------- 1 | using OpenCvSharp; 2 | 3 | namespace REVUnit.AutoArknights.Core.CV 4 | { 5 | public abstract class ImageRegistration 6 | { 7 | public abstract RegistrationResult[] Register(Mat model, Mat scene, int minMatchCount); 8 | } 9 | } -------------------------------------------------------------------------------- /Source/Core/CV/MatFeature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenCvSharp; 3 | 4 | namespace REVUnit.AutoArknights.Core.CV 5 | { 6 | public class MatFeature : IDisposable 7 | { 8 | public readonly Mat Descriptors; 9 | public readonly KeyPoint[] KeyPoints; 10 | public readonly int MatHeight; 11 | public readonly int MatWidth; 12 | public readonly Feature2DType Type; 13 | 14 | public MatFeature(KeyPoint[] keyPoints, Mat descriptors, int matWidth, 15 | int matHeight, Feature2DType type) 16 | { 17 | KeyPoints = keyPoints; 18 | Descriptors = descriptors; 19 | MatWidth = matWidth; 20 | MatHeight = matHeight; 21 | Type = type; 22 | } 23 | 24 | public void Dispose() 25 | { 26 | Descriptors.Dispose(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Source/Core/CV/Ocr.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using OpenCvSharp; 5 | using Tesseract; 6 | using Rect = OpenCvSharp.Rect; 7 | 8 | namespace REVUnit.AutoArknights.Core.CV 9 | { 10 | public class TextBlock 11 | { 12 | public TextBlock(string text, float confidence, Rect? rect = null) 13 | { 14 | Text = text; 15 | Confidence = confidence; 16 | Rect = rect; 17 | } 18 | 19 | public float Confidence { get; } 20 | public Rect? Rect { get; } 21 | public string Text { get; } 22 | } 23 | 24 | public static class Ocr 25 | { 26 | private static readonly TesseractEngine Engine; 27 | 28 | private const string TesseractDataPath = @".\Assets\Tesseract"; 29 | 30 | static Ocr() 31 | { 32 | Engine = new TesseractEngine(Path.Combine(TesseractDataPath, "TrainedData"), "chi_sim+eng", 33 | EngineMode.LstmOnly); 34 | Engine.SetVariable("debug_file", "/dev/null"); 35 | } 36 | 37 | public static TextBlock Single(Mat image, string? patternName = null) 38 | { 39 | if (patternName != null) 40 | Engine.SetVariable("user_patterns_file", Path.Combine(TesseractDataPath, "UserPatterns", patternName)); 41 | 42 | using Page page = Engine.Process(image.ToPix(), PageSegMode.SingleLine); 43 | 44 | if (patternName != null) 45 | Engine.SetVariable("user_patterns_file", string.Empty); 46 | 47 | using ResultIterator iter = page.GetIterator(); 48 | string text = iter.GetText(PageIteratorLevel.Block); 49 | float confidence = iter.GetConfidence(PageIteratorLevel.Block); 50 | return new TextBlock(text, confidence); 51 | } 52 | 53 | public static TextBlock[] Sparse(Mat image) 54 | { 55 | using Page page = Engine.Process(image.ToPix(), PageSegMode.SparseText); 56 | using ResultIterator iter = page.GetIterator(); 57 | var results = new List(); 58 | while (iter.Next(PageIteratorLevel.Block)) 59 | { 60 | string text = iter.GetText(PageIteratorLevel.Block); 61 | float confidence = iter.GetConfidence(PageIteratorLevel.Block); 62 | if (!iter.TryGetBoundingBox(PageIteratorLevel.Block, out Tesseract.Rect rectT)) 63 | { 64 | throw new Exception(); 65 | } 66 | 67 | Rect rect = rectT.ToCvRect(); 68 | results.Add(new TextBlock(text, confidence, rect)); 69 | } 70 | 71 | return results.ToArray(); 72 | } 73 | 74 | private static Rect ToCvRect(this Tesseract.Rect rect) 75 | { 76 | return new(rect.X1, rect.Y1, rect.Width, rect.Height); 77 | } 78 | 79 | private static Pix ToPix(this Mat mat) 80 | { 81 | return Pix.LoadFromMemory(mat.ToBytes()); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /Source/Core/CV/Quadrilateral32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenCvSharp; 3 | 4 | namespace REVUnit.AutoArknights.Core.CV 5 | { 6 | /// 7 | /// Represents a convex quadrilateral, using 32-bit floating point numbers. 8 | /// 9 | public struct Quadrilateral32 10 | { 11 | public Point2f TopLeft; 12 | public Point2f TopRight; 13 | public Point2f BottomRight; 14 | public Point2f BottomLeft; 15 | 16 | public Quadrilateral32(Point2f topLeft, Point2f topRight, Point2f bottomRight, Point2f bottomLeft) 17 | { 18 | TopLeft = topLeft; 19 | TopRight = topRight; 20 | BottomRight = bottomRight; 21 | BottomLeft = bottomLeft; 22 | } 23 | 24 | public static readonly Quadrilateral32 Empty = new(); 25 | 26 | public static Quadrilateral32 FromVertices(params Point2f[] vertices) 27 | { 28 | return new(vertices[0], vertices[1], vertices[2], vertices[3]); 29 | } 30 | 31 | public static Quadrilateral32 FromRect(Rect rect) 32 | { 33 | return FromVertices(new Point(rect.Left, rect.Top), new Point(rect.Right, rect.Top), new Point(rect.Right, 34 | rect.Bottom), new Point(rect.Left, rect.Bottom)); 35 | } 36 | 37 | public Rect BoundingRectangle => 38 | Rect.FromLTRB((int) MathF.Round(MathF.Min(TopLeft.X, BottomLeft.X)), 39 | (int) MathF.Round(MathF.Min(TopLeft.Y, TopRight.Y)), 40 | (int) MathF.Round(MathF.Max(TopRight.X, BottomRight.X)), 41 | (int) MathF.Round(MathF.Max(BottomLeft.Y, BottomRight.Y))); 42 | 43 | public Point2f[] Vertices => new[] {TopLeft, TopRight, BottomRight, BottomLeft}; 44 | 45 | public Point2f VertexCentroid => (TopLeft + TopRight + BottomRight + BottomLeft).Multiply(1.0f / 4.0f); 46 | 47 | public Quadrilateral32 ScaleTo(float factor) 48 | { 49 | Point2f r = VertexCentroid.Multiply(1 - factor); 50 | return new Quadrilateral32(TopLeft.Multiply(factor) + r, TopRight.Multiply(factor) + r, 51 | BottomRight.Multiply(factor) + r, BottomLeft.Multiply(factor) + r); 52 | } 53 | 54 | public Point PickRandomPoint() 55 | { 56 | // Brute force algorithm. 57 | 58 | Rect rect = BoundingRectangle; 59 | var random = new Random(); 60 | 61 | Point point; 62 | do 63 | { 64 | point = new Point(random.Next(rect.X, rect.X + rect.Width), 65 | random.Next(rect.Y, rect.Y + rect.Height)); 66 | } while (!IsPointInPolygon(point, Vertices)); 67 | 68 | return point; 69 | } 70 | 71 | private static unsafe bool IsPointInPolygon(Point2f point, Point2f[] vertices) 72 | { 73 | static void Swap(T* a, T* b) where T : unmanaged 74 | { 75 | T t = *a; 76 | *a = *b; 77 | *b = t; 78 | } 79 | 80 | // Split polygons along set of x axes 81 | 82 | int verticeCount = vertices.Length; 83 | 84 | Span v = stackalloc Point2f[verticeCount]; 85 | for (var i = 0; i < verticeCount; i++) 86 | { 87 | v[i] = vertices[i]; 88 | } 89 | 90 | float* len = stackalloc float[3]; 91 | 92 | int spCount = verticeCount - 2; 93 | SizePlanePair* sp = stackalloc SizePlanePair[spCount]; 94 | int pCount = 3 * spCount; 95 | PlaneSet* p = stackalloc PlaneSet[pCount]; 96 | PlaneSet* pOrigin = p; 97 | 98 | float v0X = v[0].X; 99 | float v0Y = v[0].Y; 100 | 101 | for (int p1 = 1, p2 = 2; p2 < verticeCount; p1++, p2++) 102 | { 103 | p->Vx = v0Y - v[p1].Y; 104 | p->Vy = v[p1].X - v0X; 105 | p->C = p->Vx * v0X + p->Vy * v0Y; 106 | len[0] = p->Vx * p->Vx + p->Vy * p->Vy; 107 | p->IsExterior = p1 == 1; 108 | /* Sort triangles by areas, so compute (twice) the area here */ 109 | sp[p1 - 1].PPlaneSet = p; 110 | sp[p1 - 1].Size = 111 | v[0].X * v[p1].Y + 112 | v[p1].X * v[p2].Y + 113 | v[p2].X * v[0].Y - 114 | v[p1].X * v[0].Y - 115 | v[p2].X * v[p1].Y - 116 | v[0].X * v[p2].Y; 117 | p++; 118 | 119 | p->Vx = v[p1].Y - v[p2].Y; 120 | p->Vy = v[p2].X - v[p1].X; 121 | p->C = p->Vx * v[p1].X + p->Vy * v[p1].Y; 122 | len[1] = p->Vx * p->Vx + p->Vy * p->Vy; 123 | p->IsExterior = true; 124 | p++; 125 | 126 | p->Vx = v[p2].Y - v0Y; 127 | p->Vy = v0X - v[p2].X; 128 | p->C = p->Vx * v[p2].X + p->Vy * v[p2].Y; 129 | len[2] = p->Vx * p->Vx + p->Vy * p->Vy; 130 | p->IsExterior = p2 == verticeCount - 1; 131 | 132 | /* find an average point which must be inside of the triangle */ 133 | float tx1 = (v0X + v[p1].X + v[p2].X) / 3.0f; 134 | float ty1 = (v0Y + v[p1].Y + v[p2].Y) / 3.0f; 135 | 136 | /* check sense and reverse if test point is not thought to be inside 137 | * first triangle 138 | */ 139 | if (p->Vx * tx1 + p->Vy * ty1 >= p->C) 140 | { 141 | /* back up to start of plane set */ 142 | p -= 2; 143 | /* point is thought to be outside, so reverse sense of edge 144 | * normals so that it is correctly considered inside. 145 | */ 146 | for (var i = 0; i < 3; i++) 147 | { 148 | *p = -*p; 149 | p++; 150 | } 151 | } 152 | else 153 | { 154 | p++; 155 | } 156 | 157 | /* sort the planes based on the edge lengths */ 158 | p -= 3; 159 | for (var i = 0; i < 2; i++) 160 | { 161 | for (int j = i + 1; j < 3; j++) 162 | { 163 | if (len[i] < len[j]) 164 | { 165 | Swap(p + i, p + j); 166 | Swap(len + i, len + j); 167 | } 168 | } 169 | } 170 | 171 | p += 3; 172 | } 173 | 174 | SizePlanePair[] spHeap = new SizePlanePair[spCount]; 175 | for (var i = 0; i < spCount; i++) 176 | { 177 | spHeap[i] = sp[i]; 178 | } 179 | 180 | /* sort the triangles based on their areas */ 181 | Array.Sort(spHeap); 182 | 183 | p = pOrigin; 184 | /* make the plane sets match the sorted order */ 185 | for (var i = 0; i < spCount; i++) 186 | { 187 | for (var j = 0; j < 3; j++) 188 | { 189 | Swap(p + j, spHeap[i].PPlaneSet); 190 | } 191 | } 192 | 193 | // Check point for inside of three "planes" formed by triangle edges 194 | 195 | float tx = point.X; 196 | float ty = point.Y; 197 | 198 | for (int i = verticeCount - 1; i >= 0; i--) 199 | { 200 | if (p->Vx * tx + p->Vy * ty < p->C) 201 | { 202 | p++; 203 | if (p->Vx * tx + p->Vy * ty < p->C) 204 | { 205 | p++; 206 | /* note: we make the third edge have a slightly different 207 | * equality condition, since this third edge is in fact 208 | * the next triangle's first edge. Not fool-proof, but 209 | * it doesn't hurt (better would be to keep track of the 210 | * triangle's area sign so we would know which kind of 211 | * triangle this is). Note that edge sorting nullifies 212 | * this special inequality, too. 213 | */ 214 | if (p->Vx * tx + p->Vy * ty <= p->C) 215 | { 216 | /* point is inside polygon */ 217 | return true; 218 | } 219 | /* check if outside exterior edge */ 220 | 221 | if (p->IsExterior) 222 | { 223 | return false; 224 | } 225 | 226 | p++; 227 | } 228 | else 229 | { 230 | /* check if outside exterior edge */ 231 | if (p->IsExterior) return false; 232 | /* get past last two plane tests */ 233 | p += 2; 234 | } 235 | } 236 | else 237 | { 238 | /* check if outside exterior edge */ 239 | if (p->IsExterior) return false; 240 | /* get past all three plane tests */ 241 | p += 3; 242 | } 243 | } 244 | 245 | /* for convex, if we make it to here, all triangles were missed */ 246 | return false; 247 | } 248 | 249 | private struct PlaneSet 250 | { 251 | public float Vx, Vy, C; /* edge equation vx*X + vy*Y + c = 0 */ 252 | public bool IsExterior; /* TRUE == exterior edge of polygon */ 253 | 254 | public PlaneSet(float vx, float vy, float c, bool isExterior) 255 | { 256 | Vx = vx; 257 | Vy = vy; 258 | C = c; 259 | IsExterior = isExterior; 260 | } 261 | 262 | public static PlaneSet operator -(PlaneSet p) 263 | { 264 | return new(-p.Vx, -p.Vy, -p.C, p.IsExterior); 265 | } 266 | } 267 | 268 | private unsafe struct SizePlanePair : IComparable 269 | { 270 | public float Size; 271 | public PlaneSet* PPlaneSet; 272 | 273 | public int CompareTo(SizePlanePair other) 274 | { 275 | return Size.CompareTo(other.Size); 276 | } 277 | } 278 | } 279 | } -------------------------------------------------------------------------------- /Source/Core/CV/RegistrationResult.cs: -------------------------------------------------------------------------------- 1 | namespace REVUnit.AutoArknights.Core.CV 2 | { 3 | public class RegistrationResult 4 | { 5 | public RegistrationResult(Quadrilateral32 region, double confidence) 6 | { 7 | Region = region; 8 | Confidence = confidence; 9 | } 10 | 11 | public double Confidence { get; } 12 | 13 | public Quadrilateral32 Region { get; } 14 | } 15 | } -------------------------------------------------------------------------------- /Source/Core/CV/TemplateRegistration.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using OpenCvSharp; 4 | 5 | namespace REVUnit.AutoArknights.Core.CV 6 | { 7 | public class TemplateRegistration : ImageRegistration 8 | { 9 | public override RegistrationResult[] Register(Mat model, Mat scene, int minMatchCount) 10 | { 11 | Mat diff = scene.MatchTemplate(model, TemplateMatchModes.CCoeffNormed); 12 | var matches = new List<(Rect rect, double confidence)>(); 13 | 14 | for (var i = 0; i < minMatchCount; i++) 15 | { 16 | diff.MinMaxLoc(out _, out double maxVal, out _, out Point maxLoc); 17 | matches.Add((new Rect(maxLoc, model.Size()), maxVal)); 18 | diff.At(maxLoc.Y, maxLoc.X) = 0; 19 | } 20 | 21 | return matches.Select(match => 22 | new RegistrationResult(Quadrilateral32.FromRect(match.rect), match.confidence)) 23 | .OrderBy(match => match.Confidence).ToArray(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Source/Core/ChildProcessTracker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.Runtime.InteropServices; 5 | 6 | // ReSharper disable InconsistentNaming 7 | 8 | namespace REVUnit.AutoArknights.Core 9 | { 10 | internal static class ChildProcessTracker 11 | { 12 | private const int JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000; 13 | private const int JobObjectExtendedLimitInformation = 9; 14 | 15 | public static void Track(Process process) 16 | { 17 | IntPtr jobHandle = CreateJobObject(IntPtr.Zero, Guid.NewGuid().ToString()); 18 | 19 | var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION {LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE}; 20 | var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION {BasicLimitInformation = info}; 21 | 22 | int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); 23 | IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); 24 | 25 | try 26 | { 27 | Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); 28 | if (!SetInformationJobObject(jobHandle, JobObjectExtendedLimitInformation, extendedInfoPtr, 29 | (uint) length)) 30 | { 31 | throw new Win32Exception(Marshal.GetLastWin32Error()); 32 | } 33 | } 34 | finally 35 | { 36 | Marshal.FreeHGlobal(extendedInfoPtr); 37 | } 38 | 39 | AssignProcessToJobObject(jobHandle, process.Handle); 40 | } 41 | 42 | [DllImport("kernel32.dll", SetLastError = true)] 43 | private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); 44 | 45 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] 46 | private static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string? name); 47 | 48 | [DllImport("kernel32.dll")] 49 | private static extern bool SetInformationJobObject(IntPtr job, int infoType, 50 | IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength); 51 | } 52 | 53 | // ReSharper disable All 54 | 55 | [StructLayout(LayoutKind.Sequential)] 56 | internal struct IO_COUNTERS 57 | { 58 | public ulong ReadOperationCount; 59 | public ulong WriteOperationCount; 60 | public ulong OtherOperationCount; 61 | public ulong ReadTransferCount; 62 | public ulong WriteTransferCount; 63 | public ulong OtherTransferCount; 64 | } 65 | 66 | [StructLayout(LayoutKind.Sequential)] 67 | internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION 68 | { 69 | public long PerProcessUserTimeLimit; 70 | public long PerJobUserTimeLimit; 71 | public uint LimitFlags; 72 | public UIntPtr MinimumWorkingSetSize; 73 | public UIntPtr MaximumWorkingSetSize; 74 | public uint ActiveProcessLimit; 75 | public UIntPtr Affinity; 76 | public uint PriorityClass; 77 | public uint SchedulingClass; 78 | } 79 | 80 | [StructLayout(LayoutKind.Sequential)] 81 | internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION 82 | { 83 | public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; 84 | public IO_COUNTERS IoInfo; 85 | public UIntPtr ProcessMemoryLimit; 86 | public UIntPtr JobMemoryLimit; 87 | public UIntPtr PeakProcessMemoryUsed; 88 | public UIntPtr PeakJobMemoryUsed; 89 | } 90 | } -------------------------------------------------------------------------------- /Source/Core/CombatModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | using System.Threading.Tasks; 4 | using Polly; 5 | using Polly.Retry; 6 | using Serilog; 7 | 8 | namespace REVUnit.AutoArknights.Core 9 | { 10 | public class CombatModuleSettings 11 | { 12 | public CombatModuleSettings(int intervalAfterLevelComplete = 5 * 1000, 13 | int intervalBeforeVerifyInLevel = 50 * 1000) 14 | { 15 | IntervalAfterLevelComplete = intervalAfterLevelComplete; 16 | IntervalBeforeVerifyInLevel = intervalBeforeVerifyInLevel; 17 | } 18 | 19 | public int IntervalAfterLevelComplete { get; } 20 | public int IntervalBeforeVerifyInLevel { get; } 21 | } 22 | 23 | public class CombatModule 24 | { 25 | private static readonly Regex CurrentSanityRegex = 26 | new(@"(?\d+)\s*\/\s*(?\d+)", RegexOptions.Compiled); 27 | 28 | private readonly AsyncRetryPolicy _getCurrentSanityPolicy; 29 | private readonly Interactor _i; 30 | private Sanity? _lastGetSanityResult; 31 | private int _requiredSanity; 32 | 33 | internal CombatModule(Interactor interactor) 34 | { 35 | _i = interactor; 36 | _getCurrentSanityPolicy = Policy.HandleResult(sanity => 37 | { 38 | if (_lastGetSanityResult == null) return false; 39 | int dSanity = _lastGetSanityResult.Value - sanity.Value; 40 | 41 | return Math.Abs(sanity.Max - _lastGetSanityResult.Max) > 1 // 理智上限变动大于1 42 | || dSanity > 0 // 没有嗑药 43 | && Math.Abs(_requiredSanity - dSanity) > 10; // 且理智对于一般情况下的刷关后理智差大于10 44 | }).WaitAndRetryAsync(2, _ => TimeSpan.FromSeconds(2)); 45 | } 46 | 47 | public CombatModuleSettings Settings { get; set; } = new(); 48 | 49 | public async Task Run(Level? level, LevelCombatSettings settings) 50 | { 51 | if (level != null) 52 | { 53 | throw new NotImplementedException(); 54 | } 55 | 56 | await RunCurrentSelectedLevel(settings); 57 | } 58 | 59 | private async Task GetCurrentSanity() 60 | { 61 | PolicyResult policyResult = await _getCurrentSanityPolicy.ExecuteAndCaptureAsync(async () => 62 | { 63 | string text = await _i.Ocr(RelativeArea.CurrentSanityText, "CurrentSanity"); 64 | Match match = CurrentSanityRegex.Match(text); 65 | if (!(int.TryParse(match.Groups["current"].Value, out int current) && 66 | int.TryParse(match.Groups["max"].Value, out int max))) 67 | { 68 | throw new Exception("无法识别理智值"); 69 | } 70 | 71 | return new Sanity(current, max); 72 | }); 73 | 74 | Sanity currentSanity; 75 | if (policyResult.Outcome == OutcomeType.Successful) 76 | { 77 | currentSanity = policyResult.Result; 78 | } 79 | else if (policyResult.FinalException != null) 80 | { 81 | throw policyResult.FinalException; 82 | } 83 | else 84 | { 85 | Log.Warning("OCR识别结果似乎有误"); 86 | currentSanity = policyResult.FinalHandledResult; 87 | } 88 | 89 | _lastGetSanityResult = currentSanity; 90 | return currentSanity; 91 | } 92 | 93 | private async Task GetRequiredSanity() 94 | { 95 | string text = await _i.Ocr(RelativeArea.RequiredSanityText, "RequiredSanity"); 96 | if (!int.TryParse(text[1..], out int requiredSanity)) throw new Exception(); 97 | 98 | Log.Information("检测到此关卡需要[{RequiredSanity}]理智", requiredSanity); 99 | return requiredSanity; 100 | } 101 | 102 | private async Task RunCurrentSelectedLevel(LevelCombatSettings settings) 103 | { 104 | var currentTimes = 0; 105 | int targetTimes = settings.RepeatTimes; 106 | 107 | if (targetTimes <= 0) 108 | { 109 | _requiredSanity = await GetRequiredSanity(); 110 | } 111 | 112 | Func> continueCondition = targetTimes switch 113 | { 114 | -1 => async () => 115 | { 116 | if ((await GetCurrentSanity()).Value < _requiredSanity) 117 | { 118 | Log.Information("正在等待理智恢复..."); 119 | while ((await GetCurrentSanity()).Value < _requiredSanity) 120 | { 121 | await Task.Delay(10000); 122 | } 123 | 124 | Log.Information("...理智恢复完成"); 125 | } 126 | 127 | return true; 128 | }, 129 | 0 => async () => 130 | { 131 | Sanity currentSanity = await GetCurrentSanity(); 132 | Log.Information("检测到当前理智为[{CurrentSanity}]", currentSanity); 133 | return currentSanity.Value >= _requiredSanity; 134 | }, 135 | // ReSharper disable once AccessToModifiedClosure 136 | _ => () => Task.FromResult(currentTimes < targetTimes) 137 | }; 138 | 139 | while (await continueCondition()) 140 | { 141 | if (targetTimes > 0) 142 | Log.Information("开始第[{CurrentTimes}/{times}]次刷关", currentTimes + 1, targetTimes); 143 | else 144 | Log.Information("开始第{CurrentTimes}次刷关", currentTimes + 1); 145 | 146 | await RunCurrentSelectedLevel(); 147 | 148 | if (targetTimes > 0) 149 | Log.Information("关卡完成,目前已刷关[{currentTimes}/{Times}]次", ++currentTimes, targetTimes); 150 | else 151 | Log.Information("关卡完成,目前已刷关{CurrentTimes}次", ++currentTimes); 152 | } 153 | } 154 | 155 | private async Task RunCurrentSelectedLevel() 156 | { 157 | await _i.ClickFor("Combat/Begin"); 158 | await Task.Delay(1000); 159 | 160 | await _i.ClickFor("Combat/Start"); 161 | await Task.Delay(Settings.IntervalBeforeVerifyInLevel); 162 | 163 | if (!await _i.TestAppear("Combat/TakeOver")) 164 | { 165 | Log.Warning("未检测到代理指挥正常运行迹象!"); 166 | Log.Warning("请检查是否在正常代理作战,如果正常,请增加检测代理正常前等待的时间(现在为{WaitTime}ms),以避免假警告出现", 167 | Settings.IntervalBeforeVerifyInLevel); 168 | } 169 | 170 | while (await _i.TestAppear("Combat/TakeOver")) 171 | { 172 | await Task.Delay(5000); 173 | } 174 | 175 | await Task.Delay(Settings.IntervalAfterLevelComplete); 176 | 177 | do 178 | { 179 | await _i.Click(RelativeArea.LevelCompletedScreenCloseClick); 180 | await Task.Delay(5000); 181 | } while (!await _i.TestAppear("Combat/Begin")); 182 | } 183 | } 184 | } -------------------------------------------------------------------------------- /Source/Core/Game.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace REVUnit.AutoArknights.Core 6 | { 7 | public class Game 8 | { 9 | private readonly Interactor _i; 10 | 11 | private Game(Interactor interactor) 12 | { 13 | _i = interactor; 14 | Combat = new CombatModule(interactor); 15 | Infrastructure = new InfrastructureModule(this, interactor); 16 | } 17 | 18 | public CombatModule Combat { get; } 19 | public InfrastructureModule Infrastructure { get; } 20 | 21 | public async Task BackToMainScreen() 22 | { 23 | while (true) 24 | { 25 | using (var cts = new CancellationTokenSource()) 26 | { 27 | Task clickYes = Task.Run(async () => 28 | { 29 | if (await _i.TestAppear("General/Yes")) 30 | { 31 | await _i.ClickFor("General/Yes"); 32 | } 33 | }, cts.Token); 34 | if (await _i.TestAppear("Home/Settings")) 35 | { 36 | cts.Cancel(); 37 | return; 38 | } 39 | 40 | await clickYes; 41 | } 42 | 43 | _i.Back(); 44 | await Task.Delay(500); 45 | } 46 | } 47 | 48 | public async Task ClaimTasks() 49 | { 50 | async Task TabClaim() 51 | { 52 | if (await _i.TestAppear("Tasks/ClaimAll")) 53 | { 54 | await _i.ClickFor("Tasks/ClaimAll"); 55 | await Task.Delay(1000); 56 | await _i.Click(RelativeArea.LowerBottom); 57 | } 58 | } 59 | 60 | await BackToMainScreen(); 61 | await _i.Click(RelativeArea.TasksButton); 62 | 63 | await Task.Delay(1000); 64 | if (!await _i.TestAppear("Tasks/Daily")) 65 | { 66 | throw new Exception("Tasks interface not entered"); 67 | } 68 | 69 | await TabClaim(); 70 | 71 | await _i.ClickFor("Tasks/Weekly"); 72 | await Task.Delay(1000); 73 | await TabClaim(); 74 | await Task.Delay(200); 75 | 76 | _i.Back(); 77 | } 78 | 79 | public static async Task FromDevice(IDevice device) 80 | { 81 | return new(await Interactor.FromDevice(device)); 82 | } 83 | 84 | public async Task Recurit() 85 | { 86 | await BackToMainScreen(); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /Source/Core/IDevice.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using OpenCvSharp; 3 | 4 | namespace REVUnit.AutoArknights.Core 5 | { 6 | public interface IDevice 7 | { 8 | Task Back(); 9 | Task Click(Point point); 10 | Task GetResolution(); 11 | Task GetScreenshot(); 12 | } 13 | } -------------------------------------------------------------------------------- /Source/Core/ImageAsset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.Json; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Caching.Memory; 6 | using OpenCvSharp; 7 | using Polly; 8 | using Polly.Caching; 9 | using Polly.Caching.Memory; 10 | 11 | namespace REVUnit.AutoArknights.Core 12 | { 13 | public class AssetLoadException : Exception 14 | { 15 | public AssetLoadException(string key) : base($"无法载入资源: {key}") 16 | { 17 | } 18 | } 19 | 20 | public class ImageAsset 21 | { 22 | private static readonly AsyncCachePolicy GetAssetCachePolicy = 23 | Policy.CacheAsync(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions())), 24 | TimeSpan.MaxValue /* Currently, even if all those images are cached, it won't take a huge amount of memory. */); 25 | 26 | static ImageAsset() 27 | { 28 | using FileStream fileStream = File.Open(@"Assets\Info.json", FileMode.Open); 29 | using JsonDocument json = JsonDocument.Parse(fileStream); 30 | #pragma warning disable CA1507 // Use nameof to express symbol names 31 | JsonElement element = json.RootElement.GetProperty("TargetResolution"); 32 | #pragma warning restore CA1507 // Use nameof to express symbol names 33 | int width = element.GetProperty("Width").GetInt32(); 34 | int height = element.GetProperty("Height").GetInt32(); 35 | TargetResolution = new Size(width, height); 36 | } 37 | 38 | public ImageAsset(string key, Mat image) 39 | { 40 | Key = key; 41 | Image = image; 42 | } 43 | 44 | public Mat Image { get; } 45 | 46 | public string Key { get; } 47 | 48 | public static Size TargetResolution { get; } 49 | 50 | public static Task Get(string assetExpr, Size actualResolution) 51 | { 52 | return GetAssetCachePolicy.ExecuteAsync(async context => 53 | { 54 | string assetFilePath = GetFilePath(context.OperationKey); 55 | if (!File.Exists(assetFilePath)) throw new AssetLoadException(assetExpr); 56 | 57 | Mat image = await Utils.Imread(assetFilePath); 58 | if (image.Empty()) throw new AssetLoadException(assetExpr); 59 | AdaptResolution(image, actualResolution); 60 | 61 | return new ImageAsset(assetExpr, image); 62 | }, new Context(assetExpr)); 63 | } 64 | 65 | private static void AdaptResolution(Mat image, Size actualResolution) 66 | { 67 | if (actualResolution == TargetResolution) return; 68 | Size size = image.Size(); 69 | double ratio = (double) actualResolution.Height / TargetResolution.Height; 70 | var normalizedSize = new Size(size.Width * ratio, size.Height * ratio); 71 | bool upscale = size.Height < normalizedSize.Height; 72 | Cv2.Resize(image, image, normalizedSize, interpolation: upscale 73 | ? InterpolationFlags.Cubic 74 | : InterpolationFlags.Area); 75 | } 76 | 77 | private static string GetFilePath(string assetExpr) 78 | { 79 | return Path.Combine("Assets", assetExpr) + ".png"; 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /Source/Core/InfrastructureModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace REVUnit.AutoArknights.Core 5 | { 6 | public class InfrastructureModule 7 | { 8 | private readonly Game _game; 9 | private readonly Interactor _i; 10 | 11 | internal InfrastructureModule(Game game, Interactor interactor) 12 | { 13 | _game = game; 14 | _i = interactor; 15 | } 16 | 17 | public async Task ClaimCreditPoints() 18 | { 19 | await _game.BackToMainScreen(); 20 | 21 | await _i.ClickFor("Home/Friends", RegistrationType.FeatureMatching); 22 | await Task.Delay(5000); 23 | 24 | await _i.ClickFor("Profile/FriendsList"); 25 | await Task.Delay(2000); 26 | 27 | await _i.ClickFor("Profile/Visit"); 28 | await Task.Delay(5000); 29 | 30 | if (!await _i.TestAppear("Infra/VisitNext")) return; 31 | 32 | async Task WaitForAllCpClaimed() 33 | { 34 | while (!await _i.TestAppear("Infra/VisitNextGrey")) 35 | { 36 | await Task.Delay(2000); 37 | } 38 | } 39 | 40 | Task waitForCpCollected = WaitForAllCpClaimed(); 41 | while (!waitForCpCollected.IsCompleted) 42 | { 43 | await _i.Click(RelativeArea.VisitNextButton); 44 | } 45 | 46 | await _game.BackToMainScreen(); 47 | } 48 | 49 | public void ClaimProducts() 50 | { 51 | throw new NotImplementedException(); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Source/Core/Interactor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Caching.Memory; 4 | using OpenCvSharp; 5 | using Polly; 6 | using Polly.Caching; 7 | using Polly.Caching.Memory; 8 | using Polly.Retry; 9 | using REVUnit.AutoArknights.Core.CV; 10 | using Serilog; 11 | 12 | namespace REVUnit.AutoArknights.Core 13 | { 14 | public enum RegistrationType 15 | { 16 | TemplateMatching, 17 | FeatureMatching 18 | } 19 | 20 | public class ClickForException : Exception 21 | { 22 | public ClickForException(string message) : base(message) 23 | { 24 | } 25 | 26 | public ClickForException(string? message, Exception? innerException) : base(message, innerException) 27 | { 28 | } 29 | } 30 | 31 | internal class Interactor 32 | { 33 | private static readonly int[] ClickForPolicyRetryInterval = {100, 200, 500, 1000, 1200}; 34 | 35 | private static readonly AsyncRetryPolicy ClickForPolicy = Policy.Handle().WaitAndRetryAsync( 36 | 5, 37 | retryCount => TimeSpan.FromMilliseconds(ClickForPolicyRetryInterval[retryCount - 1]), 38 | (_, timeSpan, retryCount, context) => Log.Debug("尝试点击 {ImageKey} 第{RetryCount}次失败,等待{SleepDuration}ms后重试", 39 | context.OperationKey, retryCount, timeSpan.TotalMilliseconds)); 40 | 41 | 42 | private const double ConfidenceThreshold = 0.8; 43 | 44 | private static readonly FeatureRegistration FeatureRegistration = new("Cache"); 45 | 46 | private static readonly AsyncCachePolicy LocateImageCachePolicy = 47 | Policy.CacheAsync(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions())), 48 | TimeSpan.FromMilliseconds(100)); 49 | 50 | private static readonly Random Random = new(); 51 | 52 | private static readonly AsyncCachePolicy ScreenshotCachePolicy = 53 | Policy.CacheAsync(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions())), 54 | TimeSpan.FromMilliseconds(100)); 55 | 56 | private static readonly TemplateRegistration TemplateRegistration = new(); 57 | 58 | private readonly IDevice _device; 59 | private readonly Size _deviceResolution; 60 | 61 | private Interactor(IDevice device, Size deviceResolution) 62 | { 63 | _device = device; 64 | _deviceResolution = deviceResolution; 65 | } 66 | 67 | public void Back() 68 | { 69 | _device.Back(); 70 | } 71 | 72 | public Task Click(RelativeArea area) 73 | { 74 | return Click(area.For(_deviceResolution)); 75 | } 76 | 77 | public Task Click(Rect rect, bool randomize = true) 78 | { 79 | return Click(randomize ? PickRandomPoint(rect) : GetCenter(rect), !randomize); 80 | } 81 | 82 | public Task Click(Quadrilateral32 quadrilateral32, bool randomize = true) 83 | { 84 | return Click(randomize ? PickRandomPoint(quadrilateral32) : quadrilateral32.VertexCentroid.ToPoint(), 85 | !randomize); 86 | } 87 | 88 | public Task Click(Point point, bool randomize = true) 89 | { 90 | return _device.Click(randomize ? RandomOffset(point) : point); 91 | } 92 | 93 | public async Task ClickFor(string assetExpr, 94 | RegistrationType registrationType = RegistrationType.TemplateMatching) 95 | { 96 | await ClickFor(await GetImageAsset(assetExpr), registrationType); 97 | } 98 | 99 | public static async Task FromDevice(IDevice device) 100 | { 101 | Size resolution = await device.GetResolution(); 102 | return new Interactor(device, resolution); 103 | } 104 | 105 | public async Task Ocr(RelativeArea area, string? patternName = null) 106 | { 107 | Mat screenshot = await GetScreenshot(); 108 | return await Task.Run(() => 109 | { 110 | using Mat sub = area.Of(screenshot); 111 | TextBlock textBlock = CV.Ocr.Single(sub, patternName); 112 | if (textBlock.Confidence < ConfidenceThreshold) 113 | { 114 | Log.Debug("OCR 结果可能有误(置信度:{Confidence})", textBlock.Confidence); 115 | } 116 | 117 | return textBlock.Text; 118 | }); 119 | } 120 | 121 | public async Task TestAppear(string assetExpr, RegistrationType registrationType = 122 | RegistrationType.TemplateMatching) 123 | { 124 | ImageAsset asset = await ImageAsset.Get(assetExpr, _deviceResolution); 125 | RegistrationResult result = await LocateImage(asset, registrationType); 126 | 127 | return IsSuccessful(result); 128 | } 129 | 130 | private async Task ClickFor(ImageAsset asset, 131 | RegistrationType registrationType = RegistrationType.TemplateMatching) 132 | { 133 | PolicyResult policyResult = 134 | await ClickForPolicy.ExecuteAndCaptureAsync(async context => 135 | { 136 | RegistrationResult result = await LocateImage(asset, registrationType); 137 | if (!IsSuccessful(result)) 138 | { 139 | throw new ClickForException($"无法定位 {context.OperationKey}"); 140 | } 141 | 142 | return result; 143 | }, new Context(asset.Key)); 144 | if (policyResult.Outcome == OutcomeType.Successful) 145 | { 146 | await Click(policyResult.Result.Region); 147 | } 148 | else 149 | { 150 | throw new ClickForException($"尝试点击 {asset.Key} 最终失败", policyResult.FinalException); 151 | } 152 | } 153 | 154 | private static Point GetCenter(Rect rect) 155 | { 156 | return new(rect.X + rect.Width / 2, rect.Y + rect.Height / 2); 157 | } 158 | 159 | private Task GetImageAsset(string assetExpr) 160 | { 161 | return ImageAsset.Get(assetExpr, _deviceResolution); 162 | } 163 | 164 | private Task GetScreenshot() 165 | { 166 | return ScreenshotCachePolicy.ExecuteAsync(_ => _device.GetScreenshot(), new Context(string.Empty)); 167 | } 168 | 169 | private static bool IsSuccessful(RegistrationResult result) 170 | { 171 | return result.Confidence > ConfidenceThreshold; 172 | } 173 | 174 | private async Task LocateImage(ImageAsset image, RegistrationType registrationType = 175 | RegistrationType.TemplateMatching) 176 | { 177 | return (await LocateImage(image, 1, registrationType))[0]; 178 | } 179 | 180 | private Task LocateImage(ImageAsset image, int minMatchCount = 1, 181 | RegistrationType registrationType = RegistrationType.TemplateMatching) 182 | { 183 | ImageRegistration imageRegistration = registrationType switch 184 | { 185 | RegistrationType.TemplateMatching => TemplateRegistration, 186 | RegistrationType.FeatureMatching => FeatureRegistration, 187 | _ => throw new ArgumentOutOfRangeException(nameof(registrationType), registrationType, null) 188 | }; 189 | return LocateImageCachePolicy.ExecuteAsync( 190 | async _ => imageRegistration.Register(image.Image, await GetScreenshot(), minMatchCount), 191 | new Context(image.Key)); 192 | } 193 | 194 | private static Point PickRandomPoint(Rect rect) 195 | { 196 | int randX = Random.Next((int) (rect.Width * 0.1), (int) (rect.Width * 0.9)) + rect.X; 197 | int randY = Random.Next((int) (rect.Height * 0.1), (int) (rect.Height * 0.9)) + rect.Y; 198 | return new Point(randX, randY); 199 | } 200 | 201 | private static Point PickRandomPoint(Quadrilateral32 quadrilateral32) 202 | { 203 | Quadrilateral32 downScaled = quadrilateral32.ScaleTo(0.8f); 204 | return downScaled.PickRandomPoint(); 205 | } 206 | 207 | private static Point RandomOffset(Point point) 208 | { 209 | return new(Math.Abs(Random.Next(-5, 5) + point.X), Math.Abs(Random.Next(-5, 5) + point.Y)); 210 | } 211 | } 212 | } -------------------------------------------------------------------------------- /Source/Core/Level.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace REVUnit.AutoArknights.Core 4 | { 5 | public class Level 6 | { 7 | private Level() 8 | { 9 | } 10 | 11 | public static Level FromName(string name) 12 | { 13 | throw new NotImplementedException(); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Source/Core/LevelCombatSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace REVUnit.AutoArknights.Core 4 | { 5 | public class LevelCombatSettings 6 | { 7 | public LevelCombatSettings(int repeatTimes, bool waitWhenNoSanity, bool useSanityPotions, 8 | int useOriginitesCount) 9 | { 10 | RepeatTimes = repeatTimes; 11 | WaitWhenNoSanity = waitWhenNoSanity; 12 | UseSanityPotions = useSanityPotions; 13 | UseOriginitesCount = useOriginitesCount; 14 | } 15 | 16 | public int RepeatTimes { get; } 17 | public int UseOriginitesCount { get; } 18 | public bool UseSanityPotions { get; } 19 | public bool WaitWhenNoSanity { get; } 20 | 21 | public override string ToString() 22 | { 23 | return string.Concat( 24 | RepeatTimes switch 25 | { 26 | -1 or 0 => "理智不足终止", 27 | _ => $"到达次数 {RepeatTimes} 终止" 28 | }, 29 | WaitWhenNoSanity 30 | ? " 等待自然恢复" 31 | : string.Empty, 32 | UseSanityPotions 33 | ? " 使用理智药剂" 34 | : string.Empty, 35 | UseOriginitesCount switch 36 | { 37 | -1 => " 无限使用源石", 38 | 0 => " 不使用源石", 39 | >0 => $" 使用最多{UseOriginitesCount}颗源石", 40 | _ => throw new ArgumentOutOfRangeException() 41 | }); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Source/Core/RelativeArea.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenCvSharp; 3 | 4 | namespace REVUnit.AutoArknights.Core 5 | { 6 | public readonly struct RelativeArea 7 | { 8 | // 系数 9 | public readonly double CLeft; 10 | public readonly double CRight; 11 | public readonly double CTop; 12 | public readonly double CBottom; 13 | 14 | public RelativeArea(double cLeft, double cTop, double cRight, double cBottom) 15 | { 16 | CLeft = cLeft; 17 | CTop = cTop; 18 | CRight = cRight; 19 | CBottom = cBottom; 20 | } 21 | 22 | public Rect For(Size size) 23 | { 24 | int refW = size.Width; 25 | int refH = size.Height; 26 | var left = (int) Math.Round(refW * CLeft); 27 | var top = (int) Math.Round(refH * CTop); 28 | var right = (int) Math.Round(refW * CRight); 29 | var bottom = (int) Math.Round(refH * CBottom); 30 | 31 | if (right > refW) right = refW; 32 | if (bottom > refH) bottom = refH; 33 | 34 | return Rect.FromLTRB(left, top, right, bottom); 35 | } 36 | 37 | public Mat Of(Mat super) 38 | { 39 | return super.Clone(For(super.Size())); 40 | } 41 | 42 | #region 常量 43 | 44 | public static RelativeArea All { get; } = new(0, 0, 1, 1); 45 | public static RelativeArea CurrentSanityText { get; } = Ref1080P(1672, 23, 1919, 97); 46 | public static RelativeArea RequiredSanityText { get; } = Ref1080P(1763, 1014, 1841, 1053); 47 | public static RelativeArea LevelCompletedScreenCloseClick { get; } = Ref1080P(100, 100, 1820, 600); 48 | public static RelativeArea ReceiveTaskRewardButton { get; } = Ref1080P(1488, 167, 1863, 258); 49 | public static RelativeArea VisitNextButton { get; } = Ref1080P(1661, 883, 1906, 1000); 50 | public static RelativeArea LowerBottom { get; } = Ref1080P(0, 720, 1920, 1080); 51 | 52 | // public static RelativeArea WeeklyTasksTab { get; } = Ref1080P(1121, 24, 1423, 88); 53 | 54 | public static RelativeArea TasksButton { get; } = Ref1080P(1139, 859, 1189, 909); 55 | 56 | // 开发参考分辨率 57 | public const double Rw = 1920.0; 58 | public const double Rh = 1080.0; 59 | 60 | private static RelativeArea Ref1080P(int left, int top, int right, int bottom) 61 | { 62 | return new(left / Rw, top / Rh, right / Rw, bottom / Rh); 63 | } 64 | 65 | #endregion 66 | } 67 | } -------------------------------------------------------------------------------- /Source/Core/Sanity.cs: -------------------------------------------------------------------------------- 1 | namespace REVUnit.AutoArknights.Core 2 | { 3 | public class Sanity 4 | { 5 | public readonly int Max; 6 | public readonly int Value; 7 | 8 | public Sanity(int value, int max) 9 | { 10 | Value = value; 11 | Max = max; 12 | } 13 | 14 | public override string ToString() 15 | { 16 | return $"{Value}/{Max}"; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Source/Core/Utils.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using OpenCvSharp; 4 | 5 | namespace REVUnit.AutoArknights.Core 6 | { 7 | internal static class Utils 8 | { 9 | // public static void Sleep(double seconds) 10 | // { 11 | // Thread.Sleep(TimeSpan.FromSeconds(seconds)); 12 | // } 13 | 14 | public static Task Imread(string path, ImreadModes mode = ImreadModes.Color) 15 | { 16 | return Task.Run(() => Cv2.ImDecode(File.ReadAllBytes(path), mode)); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Source/Test/Core/Data/FeatureRegistrationTest/StandardRegister/1/model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Test/Core/Data/FeatureRegistrationTest/StandardRegister/1/model.png -------------------------------------------------------------------------------- /Source/Test/Core/Data/FeatureRegistrationTest/StandardRegister/1/scene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Test/Core/Data/FeatureRegistrationTest/StandardRegister/1/scene.png -------------------------------------------------------------------------------- /Source/Test/Core/Data/FeatureRegistrationTest/StandardRegister/2/model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Test/Core/Data/FeatureRegistrationTest/StandardRegister/2/model.png -------------------------------------------------------------------------------- /Source/Test/Core/Data/FeatureRegistrationTest/StandardRegister/2/scene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCRcmcpe/Auto-Arknights/5fb5017739495735abd91e00696be3b7f08d9732/Source/Test/Core/Data/FeatureRegistrationTest/StandardRegister/2/scene.png -------------------------------------------------------------------------------- /Source/Test/Core/FeatureRegistrationTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using OpenCvSharp; 5 | using REVUnit.AutoArknights.Core.CV; 6 | using Xunit; 7 | using Xunit.Abstractions; 8 | 9 | namespace REVUnit.AutoArknights.Core.Test 10 | { 11 | public class FeatureRegistrationTest 12 | { 13 | private static readonly FeatureRegistration FeatureRegistration = new(); 14 | private readonly ITestOutputHelper _output; 15 | 16 | public FeatureRegistrationTest(ITestOutputHelper output) 17 | { 18 | _output = output; 19 | } 20 | 21 | public static IEnumerable GetTest1Data() 22 | { 23 | return Directory.EnumerateDirectories(@"Data\FeatureRegistrationTest\StandardRegister").Select(s => 24 | new object[] 25 | {new Mat(Path.Combine(s, "model.png")), new Mat(Path.Combine(s, "scene.png"))}); 26 | } 27 | 28 | [MemberData(nameof(GetTest1Data))] 29 | [Theory] 30 | public void StandardRegister(Mat model, Mat scene) 31 | { 32 | RegistrationResult[] results = FeatureRegistration.Register(model, scene, 1); 33 | Assert.Single(results); 34 | RegistrationResult result = results[0]; 35 | Assert.InRange(result.Confidence, 0.8, 1); 36 | 37 | Mat diagram = scene.Clone(); 38 | 39 | Quadrilateral32 region = result.Region.ScaleTo(0.8f); 40 | for (var i = 0; i < 1000; i++) 41 | { 42 | Point point = region.PickRandomPoint(); 43 | diagram.DrawMarker(point, Scalar.Red); 44 | } 45 | 46 | diagram.Polylines(new[] {region.Vertices.Select(p => p.ToPoint())}, true, Scalar.Red, 2, 47 | LineTypes.AntiAlias); 48 | Cv2.ImEncode(".png", diagram, out byte[] bytes); 49 | 50 | string tmpFilePath = Path.GetTempFileName(); 51 | File.WriteAllBytes(tmpFilePath, bytes); 52 | string diagramFilePath = Path.ChangeExtension(tmpFilePath, "png"); 53 | File.Move(tmpFilePath, diagramFilePath); 54 | 55 | _output.WriteLine($"Diagram written to {diagramFilePath}"); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Source/Test/Core/Test.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0-windows 5 | false 6 | REVUnit.AutoArknights.Core.Test 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | PreserveNewest 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - '*' 5 | paths: 6 | exclude: 7 | - '**/*.md' 8 | 9 | jobs: 10 | - job: CI 11 | displayName: CI Build 12 | timeoutInMinutes: 10 13 | pool: 14 | vmImage: windows-latest 15 | steps: 16 | - checkout: self 17 | displayName: Checkout 18 | - task: UseDotNet@2 19 | displayName: Setup .NET 20 | inputs: 21 | packageType: 'sdk' 22 | useGlobalJson: true 23 | workingDirectory: 'Source/CLI' 24 | - task: PowerShell@2 25 | displayName: Build 26 | inputs: 27 | filePath: 'Source/CLI/Build-Artifact.ps1' 28 | pwsh: true 29 | workingDirectory: 'Source/CLI' 30 | - task: PublishPipelineArtifact@1 31 | displayName: Publish 32 | inputs: 33 | targetPath: 'Source/CLI/artifact' 34 | artifactName: Auto Arknights CLI --------------------------------------------------------------------------------