├── .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 |
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 | 
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 | 
4 | 
5 | [](https://rev-unit.visualstudio.com/Auto-Arknights/_build/latest?definitionId=1&branchName=dev-2.0.0)
6 |
7 | 
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 | 
32 | 这个项目是实验性的,主要作个人学习用途,在使用中如果有问题欢迎提出 issue。
33 |
34 | ### 鸣谢
35 |
36 |
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