├── .gitignore
├── BeatmapInfo
└── Beatmap.cs
├── DefaultLanguage.cs
├── Helper
├── HardwareInformationHelper.cs
├── OsuStatusHelper.cs
└── PathHelper.cs
├── LICENSE
├── Language
├── en-US
│ └── OsuRTDataProvider.DefaultLanguage.lang
├── ja-JP
│ └── OsuRTDataProvider.DefaultLanguage.lang
└── zh-CN
│ └── OsuRTDataProvider.DefaultLanguage.lang
├── Listen
├── ErrorStatisticsResult.cs
├── HitEvent.cs
├── OSUListenerManager.cs
├── PlayMode.cs
└── ProvideData.cs
├── Memory
├── BeatmapOffsetInfo.cs
├── LogHelper.cs
├── OsuBeatmapFinder.cs
├── OsuFinderBase.cs
├── OsuHitEventFinder.cs
├── OsuInternalStatus.cs
├── OsuModeFinder.cs
├── OsuPlayFinder.cs
├── OsuStatusFinder.cs
├── OsuVersionCompareInfoAttribute.cs
└── SigScan.cs
├── Mods
└── ModsInfo.cs
├── OsuRTDataProvider.csproj
├── OsuRTDataProviderPlugin.cs
├── Properties
└── AssemblyInfo.cs
├── README-CN.md
├── README.md
├── Setting.cs
├── UpdateChecker.cs
└── Utils.cs
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 |
9 | # Build results
10 |
11 | [Dd]ebug/
12 | [Rr]elease/
13 | x64/
14 | build/
15 | [Bb]in/
16 | [Oo]bj/
17 |
18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
19 | !packages/*/build/
20 |
21 | # MSTest test Results
22 | [Tt]est[Rr]esult*/
23 | [Bb]uild[Ll]og.*
24 |
25 | *_i.c
26 | *_p.c
27 | *.ilk
28 | *.meta
29 | *.obj
30 | *.pch
31 | *.pdb
32 | *.pgc
33 | *.pgd
34 | *.rsp
35 | *.sbr
36 | *.tlb
37 | *.tli
38 | *.tlh
39 | *.tmp
40 | *.tmp_proj
41 | *.log
42 | *.vspscc
43 | *.vssscc
44 | .builds
45 | *.pidb
46 | *.log
47 | *.scc
48 | *.json
49 |
50 | # Visual C++ cache files
51 | ipch/
52 | *.aps
53 | *.ncb
54 | *.opensdf
55 | *.sdf
56 | *.cachefile
57 |
58 | # Visual Studio profiler
59 | *.psess
60 | *.vsp
61 | *.vspx
62 |
63 | # Guidance Automation Toolkit
64 | *.gpState
65 |
66 | # ReSharper is a .NET coding add-in
67 | _ReSharper*/
68 | *.[Rr]e[Ss]harper
69 |
70 | # TeamCity is a build add-in
71 | _TeamCity*
72 |
73 | # DotCover is a Code Coverage Tool
74 | *.dotCover
75 |
76 | # NCrunch
77 | *.ncrunch*
78 | .*crunch*.local.xml
79 |
80 | # Installshield output folder
81 | [Ee]xpress/
82 |
83 | # DocProject is a documentation generator add-in
84 | DocProject/buildhelp/
85 | DocProject/Help/*.HxT
86 | DocProject/Help/*.HxC
87 | DocProject/Help/*.hhc
88 | DocProject/Help/*.hhk
89 | DocProject/Help/*.hhp
90 | DocProject/Help/Html2
91 | DocProject/Help/html
92 |
93 | # Click-Once directory
94 | publish/
95 |
96 | # Publish Web Output
97 | *.Publish.xml
98 | *.pubxml
99 |
100 | # NuGet Packages Directory
101 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
102 | #packages/
103 |
104 | # Windows Azure Build Output
105 | csx
106 | *.build.csdef
107 |
108 | # Windows Store app package directory
109 | AppPackages/
110 |
111 | # Others
112 | sql/
113 | *.Cache
114 | ClientBin/
115 | [Ss]tyle[Cc]op.*
116 | ~$*
117 | *~
118 | *.dbmdl
119 | *.[Pp]ublish.xml
120 | *.pfx
121 | *.publishsettings
122 |
123 | # RIA/Silverlight projects
124 | Generated_Code/
125 |
126 | # Backup & report files from converting an old project file to a newer
127 | # Visual Studio version. Backup files are not needed, because we have git ;-)
128 | _UpgradeReport_Files/
129 | Backup*/
130 | UpgradeLog*.XML
131 | UpgradeLog*.htm
132 |
133 | # SQL Server files
134 | App_Data/*.mdf
135 | App_Data/*.ldf
136 |
137 | # =========================
138 | # Windows detritus
139 | # =========================
140 |
141 | # Windows image file caches
142 | Thumbs.db
143 | ehthumbs.db
144 |
145 | # Folder config file
146 | Desktop.ini
147 |
148 | # Recycle Bin used on file shares
149 | $RECYCLE.BIN/
150 |
151 | # Mac crap
152 | .DS_Store
153 |
154 | .vs/
155 | .vscode/
156 |
157 | OtherPlugins/MemoryReader/
158 | /.idea/.idea.OsuRTDataProvider.dir/.idea
159 | /OsuRTDataProvider.sln
160 | /OsuRTDataProviderVisualStudio.sln
161 | /ObseletedBeatmapFinderVersionDetector.cs
162 | /BranchBackup.txt
163 |
--------------------------------------------------------------------------------
/BeatmapInfo/Beatmap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using static OsuRTDataProvider.DefaultLanguage;
5 |
6 | namespace OsuRTDataProvider.BeatmapInfo
7 | {
8 | public class Beatmap
9 | {
10 | public int BeatmapID { get; private set; }
11 |
12 | public string DownloadLink
13 | {
14 | get
15 | {
16 | if (BeatmapID != 0) return $"http://osu.ppy.sh/b/{BeatmapID}";
17 | return LANG_BEATMAP_NOT_FOUND;
18 | }
19 | }
20 |
21 | ///
22 | /// If BeatmapSetID > 0. Return beatmap's download link.
23 | ///
24 | public string DownloadLinkSet
25 | {
26 | get
27 | {
28 | if (BeatmapSetID > 0) return $"http://osu.ppy.sh/s/{BeatmapSetID}";
29 | return LANG_BEATMAP_NOT_FOUND;
30 | }
31 | }
32 |
33 | private int m_beatmap_set_id = -1;
34 |
35 | ///
36 | /// Return set id.
37 | /// If no found return -1;
38 | ///
39 | public int BeatmapSetID
40 | {
41 | get
42 | {
43 | if (m_beatmap_set_id > 0) return m_beatmap_set_id;
44 |
45 | if (Folder.Length > 0)
46 | {
47 | string name = Folder;
48 | int len = name.IndexOf(' ');
49 | if (len != -1)
50 | {
51 | string id = name.Substring(0, len);
52 |
53 | if (int.TryParse(id, out m_beatmap_set_id))
54 | return m_beatmap_set_id;
55 | }
56 | }
57 | return -1;
58 | }
59 | private set => m_beatmap_set_id = value;
60 | }
61 |
62 | public string Version { get; private set; } = string.Empty;
63 | public string Difficulty => Version;
64 | public string Creator { get; private set; } = string.Empty;
65 | public string Artist { get; private set; } = string.Empty;
66 | public string ArtistUnicode { get; private set; } = string.Empty;
67 | public string Title { get; private set; } = string.Empty;
68 | public string TitleUnicode { get; private set; } = string.Empty;
69 | public string AudioFilename { get; private set; } = string.Empty;
70 | public string BackgroundFilename { get; private set; } = string.Empty;
71 | public string VideoFilename { get; private set; } = string.Empty;
72 |
73 | ///
74 | /// Return the first of all possible beatmap set paths.
75 | /// If not found.return string.Empty.
76 | ///
77 | public string Folder { get; private set; } = string.Empty;
78 |
79 | public int OsuClientID { get; private set; }
80 | public string Filename { get; private set; } = string.Empty;
81 | public string FilenameFull { get; private set; } = string.Empty;
82 |
83 | private static readonly Beatmap s_empty = new Beatmap(0, -1, -1, null);
84 | public static Beatmap Empty => s_empty;
85 |
86 | public Beatmap(int osu_id, int set_id, int id, FileStream fs)
87 | {
88 | BeatmapSetID = set_id;
89 | OsuClientID = osu_id;
90 | BeatmapID = id;
91 |
92 | if (fs != null)
93 | {
94 | Folder = Path.GetDirectoryName(fs.Name);
95 | Filename = Path.GetFileName(fs.Name);
96 | FilenameFull = fs.Name;
97 |
98 | using (var sr = new StreamReader(fs))
99 | {
100 | string block = "";
101 |
102 | while(!sr.EndOfStream)
103 | {
104 | string line = sr.ReadLine().Trim();
105 | if (line.StartsWith("[")&&line.EndsWith("]"))
106 | {
107 | block = line;
108 | }
109 | else if(block== "[General]"||block== "[Metadata]")
110 | {
111 | foreach(var prop in typeof(Beatmap).GetProperties())
112 | {
113 | if (line.StartsWith($"{prop.Name}:"))
114 | {
115 | object val=GetPropertyValue(line);
116 | if (prop.PropertyType == typeof(int))
117 | val = int.Parse(val as string);
118 | if (prop.PropertyType == typeof(double))
119 | val = double.Parse(val as string);
120 | prop.SetValue(this, val);
121 | }
122 | }
123 | }
124 | else if(block=="[Events]")
125 | {
126 | if(line.StartsWith("Video"))
127 | {
128 | var breaked=line.Split(',');
129 | VideoFilename = breaked[2].Replace("\"","").Trim();
130 | }
131 | else if(line.StartsWith("0,")&&string.IsNullOrEmpty(BackgroundFilename))
132 | {
133 | var breaked = line.Split(',');
134 | BackgroundFilename = breaked[2].Replace("\"", "").Trim();
135 | }
136 | }
137 | }
138 | }
139 | }
140 | }
141 |
142 | public static bool operator ==(Beatmap a, Beatmap b)
143 | {
144 | if(a is null && b is null)
145 | {
146 | return true;
147 | }
148 | if(a is null && !(b is null)||
149 | !(a is null) && b is null)
150 | {
151 | return false;
152 | }
153 | return a.Equals(b);
154 | }
155 |
156 | public static bool operator !=(Beatmap a, Beatmap b)
157 | {
158 | if (a is null && b is null)
159 | {
160 | return false;
161 | }
162 | if (a is null && !(b is null) ||
163 | !(a is null) && b is null)
164 | {
165 | return true;
166 | }
167 | return !a.Equals(b);
168 | }
169 |
170 | private static string GetPropertyValue(string line)
171 | {
172 | int pos = line.IndexOf(':');
173 | return line.Substring(pos + 1).Trim();
174 | }
175 |
176 | public override bool Equals(object obj)
177 | {
178 | if (obj is Beatmap beatmap)
179 | {
180 | return BeatmapID == beatmap.BeatmapID &&
181 | BeatmapSetID == beatmap.BeatmapSetID &&
182 | Difficulty == beatmap.Difficulty &&
183 | Creator == beatmap.Creator &&
184 | Artist == beatmap.Artist &&
185 | Title == beatmap.Title &&
186 | Filename == beatmap.Filename;
187 | }
188 | return false;
189 | }
190 |
191 | public override int GetHashCode()
192 | {
193 | var hashCode = -173464191;
194 | hashCode = hashCode * -1521134295 + BeatmapID.GetHashCode();
195 | hashCode = hashCode * -1521134295 + BeatmapSetID.GetHashCode();
196 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Difficulty);
197 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Creator);
198 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Artist);
199 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Title);
200 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Filename);
201 | return hashCode;
202 | }
203 | }
204 | }
--------------------------------------------------------------------------------
/DefaultLanguage.cs:
--------------------------------------------------------------------------------
1 | using Sync.Tools;
2 | using Sync.Tools.ConfigurationAttribute;
3 |
4 | namespace OsuRTDataProvider
5 | {
6 | public class DefaultLanguage : I18nProvider
7 | {
8 | public static LanguageElement LANG_OSU_NOT_FOUND = "[ID:{0}]Not found osu!.exe process";
9 | public static LanguageElement LANG_OSU_FOUND = "[ID:{0}]Found osu!.exe process";
10 | public static LanguageElement LANG_TOURNEY_HINT = "Tourney Mode: {0}";
11 |
12 | public static LanguageElement CHECK_GOTO_RELEASE_PAGE_HINT = "Enter \"ortdp releases\" to open the releases page in your browser.";
13 | public static LanguageElement LANG_CHECK_ORTDP_UPDATE = "Found a new version of OsuRTDataProvider({0})";
14 | public static LanguageElement LANG_INIT_STATUS_FINDER_FAILED = "[ID:{0}]Init StatusFinder Failed! Retry after {1} seconds";
15 | public static LanguageElement LANG_INIT_STATUS_FINDER_SUCCESS = "[ID:{0}]Init StatusFinder Success!";
16 | public static LanguageElement LANG_INIT_PLAY_FINDER_FAILED = "[ID:{0}]Init PlayFinder Failed! Retry after {1} seconds";
17 | public static LanguageElement LANG_INIT_PLAY_FINDER_SUCCESS = "[ID:{0}]Init PlayFinder Success!";
18 | public static LanguageElement LANG_INIT_BEATMAP_FINDER_FAILED = "[ID:{0}]Init BeatmapFinder Failed! Retry after {1} seconds";
19 | public static LanguageElement LANG_INIT_BEATMAP_FINDER_SUCCESS = "[ID:{0}]Init BeatmapFinder Success!";
20 | public static LanguageElement LANG_INIT_MODE_FINDER_FAILED = "[ID:{0}]Init ModeFinder Failed! Retry after {1} seconds";
21 | public static LanguageElement LANG_INIT_MODE_FINDER_SUCCESS = "[ID:{0}]Init ModeFinder Success!";
22 | public static LanguageElement LANG_INIT_HIT_EVENT_SUCCESS = "[ID:{0}]Init HitEventFinder Success!";
23 | public static LanguageElement LANG_INIT_HIT_EVENT_FAIL = "[ID:{0}]Init HitEventFinder Failed! Retry after {1} seconds";
24 |
25 | public static LanguageElement LANG_BEATMAP_NOT_FOUND = "Beatmap not found";
26 |
27 | public static GuiLanguageElement ListenInterval = "Listen interval(ms)";
28 | public static GuiLanguageElement EnableTourneyMode = "Tourney mode";
29 | public static GuiLanguageElement TeamSize = "Team size";
30 | public static GuiLanguageElement DebugMode = "Debug mode";
31 | public static GuiLanguageElement ForceOsuSongsDirectory = "Force OSU! songs directory";
32 | public static GuiLanguageElement GameMode = "Game Mode";
33 | public static GuiLanguageElement DisableProcessNotFoundInformation = "Disable OSU! process not found information";
34 | public static GuiLanguageElement EnableModsChangedAtListening = "Enable Mods Changed At Listening(Experimental)";
35 | }
36 | }
--------------------------------------------------------------------------------
/Helper/HardwareInformationHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Management;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace OsuRTDataProvider.Helper
9 | {
10 | static class HardwareInformationHelper
11 | {
12 | public static string GetPhysicalMemory()
13 | {
14 | ManagementScope oMs = new ManagementScope();
15 | ObjectQuery oQuery = new ObjectQuery("SELECT Capacity FROM Win32_PhysicalMemory");
16 | ManagementObjectSearcher oSearcher = new ManagementObjectSearcher(oMs, oQuery);
17 | ManagementObjectCollection oCollection = oSearcher.Get();
18 |
19 | long MemSize = 0;
20 | long mCap = 0;
21 |
22 | // In case more than one Memory sticks are installed
23 | foreach (ManagementObject obj in oCollection)
24 | {
25 | mCap = Convert.ToInt64(obj["Capacity"]);
26 | MemSize += mCap;
27 | }
28 | MemSize = (MemSize / 1024) / 1024;
29 | return MemSize.ToString() + "MB";
30 | }
31 |
32 | public static String GetProcessorInformation()
33 | {
34 | ManagementClass mc = new ManagementClass("win32_processor");
35 | ManagementObjectCollection moc = mc.GetInstances();
36 | String info = String.Empty;
37 | foreach (ManagementObject mo in moc)
38 | {
39 | string name = (string)mo["Name"];
40 |
41 | info = name + ", " + (string)mo["Caption"] + ", " + (string)mo["SocketDesignation"];
42 |
43 | break;
44 | }
45 | return info;
46 | }
47 |
48 | public static string GetOSInformation()
49 | {
50 | ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem");
51 | foreach (ManagementObject wmi in searcher.Get())
52 | {
53 | try
54 | {
55 | return ((string)wmi["Caption"]).Trim() + ", " + (string)wmi["Version"] + ", " + (string)wmi["OSArchitecture"];
56 | }
57 | catch { }
58 | }
59 | return "BIOS Maker: Unknown";
60 | }
61 |
62 | private static void Print(string str)
63 | {
64 | #if !DEBUG
65 | Sync.Tools.IO.FileLogger.Write($"{str}");
66 | #else
67 | Sync.Tools.IO.CurrentIO.Write($"{str}");
68 | #endif
69 | }
70 |
71 | public static void PrintHardwareInformation()
72 | {
73 | Print($"CLI: {Environment.Version}");
74 | Print($"OS: {GetOSInformation()}");
75 | Print($"CPU: {GetProcessorInformation()}");
76 | Print($"Memory: {GetPhysicalMemory()} Total");
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Helper/OsuStatusHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using OsuRTDataProvider.Listen;
7 |
8 | using static OsuRTDataProvider.Listen.OsuListenerManager;
9 |
10 | namespace OsuRTDataProvider.Helper
11 | {
12 | public static class OsuStatusHelper
13 | {
14 | public static bool IsListening(OsuStatus status)
15 | {
16 | const OsuStatus listen = OsuStatus.SelectSong | OsuStatus.MatchSetup | OsuStatus.Lobby | OsuStatus.Idle;
17 | return (listen & status) == status;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Helper/PathHelper.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text;
3 |
4 | namespace OsuRTDataProvider.Helper
5 | {
6 | internal static class PathHelper
7 | {
8 | public static string WindowsPathStrip(string entry)
9 | {
10 | StringBuilder builder = new StringBuilder(entry);
11 | foreach (char c in Path.GetInvalidFileNameChars())
12 | builder.Replace(c.ToString(), string.Empty);
13 | builder.Replace(".", string.Empty);
14 | return builder.ToString();
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/Language/en-US/OsuRTDataProvider.DefaultLanguage.lang:
--------------------------------------------------------------------------------
1 | [en-US]
2 | LANG_OSU_NOT_FOUND=[ID:{0}]Cannot find osu!.exe process
3 | LANG_OSU_FOUND=[ID:{0}]Found osu!.exe process
4 | LANG_INIT_PLAY_FINDER_FAILED=[ID:{0}]Init PlayFinder Failed! Retry after {1} seconds
5 | LANG_INIT_PLAY_FINDER_SUCCESS=[ID:{0}]Init PlayFinder Success!
6 | LANG_INIT_STATUS_FINDER_FAILED=[ID:{0}]Init StatusFinder Failed! Retry after {1} seconds
7 | LANG_INIT_STATUS_FINDER_SUCCESS=[ID:{0}]Init StatusFinder Success!
8 | LANG_INIT_BEATMAP_FINDER_FAILED=[ID:{0}]Init BeatmapFinder Failed! Retry after {1} seconds
9 | LANG_INIT_BEATMAP_FINDER_SUCCESS=[ID:{0}]Init BeatmapFinder Success!
10 | LANG_INIT_MODE_FINDER_FAILED=[ID:{0}]Init ModeFinder Failed! Retry after {1} seconds
11 | LANG_INIT_MODE_FINDER_SUCCESS=[ID:{0}]Init ModeFinder Success!
12 | LANG_INIT_HIT_EVENT_SUCCESS=[ID:{0}]Init HitEventFinder Success!
13 | LANG_INIT_HIT_EVENT_FAIL=[ID:{0}]Init HitEventFinder Failed! Retry after {1} seconds
14 | LANG_BEATMAP_NOT_FOUND=Beatmap not found
15 | ListenInterval=Listen interval(ms)
16 | EnableTourneyMode=Tourney mode
17 | TeamSize=Team size
18 | DebugMode=Debug mode
19 | ForceOsuSongsDirectory=Force OSU! songs directory
20 |
--------------------------------------------------------------------------------
/Language/ja-JP/OsuRTDataProvider.DefaultLanguage.lang:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OsuSync/OsuRTDataProvider/09eac48de7086e527b4228eeaa4a788742d8a1e1/Language/ja-JP/OsuRTDataProvider.DefaultLanguage.lang
--------------------------------------------------------------------------------
/Language/zh-CN/OsuRTDataProvider.DefaultLanguage.lang:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OsuSync/OsuRTDataProvider/09eac48de7086e527b4228eeaa4a788742d8a1e1/Language/zh-CN/OsuRTDataProvider.DefaultLanguage.lang
--------------------------------------------------------------------------------
/Listen/ErrorStatisticsResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace OsuRTDataProvider.Listen
8 | {
9 | public struct ErrorStatisticsResult : IEquatable
10 | {
11 | public static ErrorStatisticsResult Empty => new ErrorStatisticsResult();
12 | public double ErrorMin, ErrorMax;
13 | public double UnstableRate;
14 |
15 | public bool Equals(ErrorStatisticsResult other)
16 | {
17 | return Math.Abs(other.ErrorMin - ErrorMin) < 1e-6 &&
18 | Math.Abs(other.ErrorMax - ErrorMax) < 1e-6 &&
19 | Math.Abs(other.UnstableRate - UnstableRate) < 1e-6;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Listen/HitEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace OsuRTDataProvider.Listen
8 | {
9 | public enum PlayType
10 | {
11 | Playing = 0,
12 | Replay = 1,
13 |
14 | Unknown = -1
15 | }
16 |
17 | [Flags]
18 | public enum KeysDownFlags
19 | {
20 | M1 = 1,
21 | M2 = 2,
22 | K1 = 4,
23 | K2 = 8,
24 | Smoke = 16,
25 | }
26 |
27 | public class HitEvent
28 | {
29 | public int TimeStamp { get; }
30 | public KeysDownFlags KeysDown => (KeysDownFlags)Z;
31 | public float X { get; }
32 | public float Y { get; }
33 | private int Z;
34 |
35 |
36 | public HitEvent(float x, float y, int z, int timeStamp)
37 | {
38 | this.X = x;
39 | this.Y = y;
40 | this.Z = z;
41 | this.TimeStamp = timeStamp;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Listen/OSUListenerManager.cs:
--------------------------------------------------------------------------------
1 | using OsuRTDataProvider.BeatmapInfo;
2 | using OsuRTDataProvider.Helper;
3 | using OsuRTDataProvider.Memory;
4 | using OsuRTDataProvider.Mods;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.ComponentModel;
8 | using System.Diagnostics;
9 | using System.IO;
10 | using System.Linq;
11 | using System.Management;
12 | using System.Text;
13 | using System.Threading;
14 | using System.Threading.Tasks;
15 | using static OsuRTDataProvider.DefaultLanguage;
16 |
17 | namespace OsuRTDataProvider.Listen
18 | {
19 | public class OsuListenerManager
20 | {
21 | [Flags]
22 | public enum OsuStatus:UInt64
23 | {
24 | NoFoundProcess = 1ul<<0,
25 | Unkonwn = 1ul<<1,
26 | SelectSong = 1ul<<2,
27 | Playing = 1ul<<3,
28 | Editing = 1ul<<4,
29 | Rank = 1ul<<5,
30 | MatchSetup = 1ul<<6,
31 | Lobby = 1ul<<7,
32 | Idle = 1ul<<8,
33 | }
34 |
35 | private static Dictionary s_game_mode_map = new Dictionary(4)
36 | {
37 | ["Osu"] = OsuPlayMode.Osu,
38 | ["Taiko"] = OsuPlayMode.Taiko,
39 | ["CatchTheBeat"] = OsuPlayMode.CatchTheBeat,
40 | ["Mania"] = OsuPlayMode.Mania,
41 | };
42 |
43 | static private List> s_listen_update_list = new List>();
44 | static private Task s_listen_task;
45 | static private bool s_stop_flag = false;
46 |
47 | #region Event
48 |
49 | public delegate void OnPlayModeChangedEvt(OsuPlayMode last, OsuPlayMode mode);
50 |
51 | public delegate void OnStatusChangedEvt(OsuStatus last_status, OsuStatus status);
52 |
53 | public delegate void OnBeatmapChangedEvt(Beatmap map);
54 |
55 | public delegate void OnHealthPointChangedEvt(double hp);
56 |
57 | public delegate void OnAccuracyChangedEvt(double acc);
58 |
59 | public delegate void OnComboChangedEvt(int combo);
60 |
61 | public delegate void OnModsChangedEvt(ModsInfo mods);
62 |
63 | public delegate void OnPlayingTimeChangedEvt(int ms);
64 |
65 | public delegate void OnHitCountChangedEvt(int hit);
66 |
67 | public delegate void OnErrorStatisticsChangedEvt(ErrorStatisticsResult result);
68 |
69 | public delegate void OnPlayerChangedEvt(string player);
70 |
71 | public delegate void OnHitEventsChangedEvt(PlayType playType, List hitEvents);
72 |
73 | ///
74 | /// Available in Playing and Linsten.
75 | /// If too old beatmap, map.ID = -1.
76 | ///
77 | public event Action OnScoreChanged;
78 |
79 | ///
80 | /// Available in Linsten.
81 | ///
82 | public event OnPlayModeChangedEvt OnPlayModeChanged;
83 |
84 | ///
85 | /// Available in Playing and Linsten.
86 | /// If too old beatmap, map.ID = -1.
87 | ///
88 | public event OnBeatmapChangedEvt OnBeatmapChanged;
89 |
90 | ///
91 | /// Available in Playing.
92 | ///
93 | public event OnHealthPointChangedEvt OnHealthPointChanged;
94 |
95 | ///
96 | /// Available in Playing.
97 | ///
98 | public event OnAccuracyChangedEvt OnAccuracyChanged;
99 |
100 | ///
101 | /// Available in Playing.
102 | ///
103 | public event OnComboChangedEvt OnComboChanged;
104 |
105 | ///
106 | /// Available in Playing.
107 | /// if OsuStatus turns Listen , mods = ModsInfo.Empty
108 | ///
109 | public event OnModsChangedEvt OnModsChanged;
110 |
111 | ///
112 | /// Available in Playing and Listen.
113 | ///
114 | public event OnPlayingTimeChangedEvt OnPlayingTimeChanged;
115 |
116 | ///
117 | /// Available in Playing.
118 | ///
119 | public event OnHitCountChangedEvt OnCount300Changed;
120 |
121 | ///
122 | /// Available in Playing.
123 | ///
124 | public event OnHitCountChangedEvt OnCount100Changed;
125 |
126 | ///
127 | /// Available in Playing.
128 | ///
129 | public event OnHitCountChangedEvt OnCount50Changed;
130 |
131 | ///
132 | /// Mania: 300g
133 | ///
134 | public event OnHitCountChangedEvt OnCountGekiChanged;
135 |
136 | ///
137 | /// Mania: 200
138 | ///
139 | public event OnHitCountChangedEvt OnCountKatuChanged;
140 |
141 | ///
142 | /// Available in Playing.
143 | ///
144 | public event OnHitCountChangedEvt OnCountMissChanged;
145 |
146 | ///
147 | /// Get Game Status.
148 | ///
149 | public event OnStatusChangedEvt OnStatusChanged;
150 |
151 | ///
152 | /// Get ErrorStatistics(UnstableRate and Error Hit).
153 | ///
154 | public event OnErrorStatisticsChangedEvt OnErrorStatisticsChanged;
155 |
156 | ///
157 | /// Get player name in playing.
158 | ///
159 | public event OnPlayerChangedEvt OnPlayerChanged;
160 |
161 | ///
162 | /// Get play type and hit events in playing.
163 | ///
164 | public event OnHitEventsChangedEvt OnHitEventsChanged;
165 | #endregion Event
166 |
167 | private Process m_osu_process;
168 |
169 | private OsuPlayFinder m_play_finder = null;
170 | private OsuStatusFinder m_status_finder = null;
171 | private OsuBeatmapFinder m_beatmap_finder = null;
172 | private OsuPlayModeFinder m_mode_finder = null;
173 | private OsuHitEventFinder m_hit_event_finder = null;
174 |
175 | #region last status
176 |
177 | private OsuStatus m_last_osu_status = OsuStatus.Unkonwn;
178 | private string m_last_playername = string.Empty;
179 | private OsuPlayMode m_last_mode = OsuPlayMode.Unknown;
180 | private Beatmap m_last_beatmap = Beatmap.Empty;
181 | private ModsInfo m_last_mods = ModsInfo.Empty;
182 |
183 | private double m_last_hp = 0;
184 | private double m_last_acc = 0;
185 | private ErrorStatisticsResult m_last_error_statistics = ErrorStatisticsResult.Empty;
186 | private int m_last_combo = 0;
187 | private int m_playing_time = 0;
188 | private int m_last_300 = 0;
189 | private int m_last_100 = 0;
190 | private int m_last_50 = 0;
191 | private int m_last_miss = 0;
192 | private int m_last_geki = 0;
193 | private int m_last_katu = 0;
194 |
195 |
196 | private int m_last_score = 0;
197 |
198 | private List m_hit_events = new List();
199 | private PlayType m_play_type = PlayType.Unknown;
200 |
201 | #endregion last status
202 |
203 | private readonly bool m_is_tourney = false;
204 | private readonly int m_osu_id = 0;
205 |
206 | #region OsuRTDataProviderThread
207 |
208 | static OsuListenerManager()
209 | {
210 | s_stop_flag = false;
211 |
212 | //Listen Thread
213 | s_listen_task = Task.Run(() =>
214 | {
215 | Thread.CurrentThread.Name = "OsuRTDataProviderThread";
216 | Thread.Sleep(2000);
217 | while (!s_stop_flag)
218 | {
219 | for (int i = 0; i < s_listen_update_list.Count; i++)
220 | {
221 | var action = s_listen_update_list[i];
222 | action.Item2();
223 | }
224 |
225 | Thread.Sleep(Setting.ListenInterval);
226 | }
227 | });
228 | }
229 | #endregion
230 |
231 | public OsuListenerManager(bool tourney = false, int osuid = 0)
232 | {
233 | m_is_tourney = tourney;
234 | m_osu_id = osuid;
235 | }
236 |
237 | public void Start()
238 | {
239 | s_listen_update_list.Add(new Tuple(m_osu_id, ListenLoopUpdate));
240 | }
241 |
242 | public void Stop()
243 | {
244 | var tuple = s_listen_update_list.Where(t => t.Item1 == m_osu_id).FirstOrDefault();
245 | s_listen_update_list.Remove(tuple);
246 |
247 | if (s_listen_update_list.Count == 0)
248 | {
249 | s_stop_flag = true;
250 | s_listen_task.Wait();
251 | }
252 | }
253 |
254 | #region Get Current Data
255 |
256 | private bool HasMask(ProvideDataMask mask, ProvideDataMask h)
257 | {
258 | return (mask & h) == h;
259 | }
260 |
261 | public ProvideData GetCurrentData(ProvideDataMask mask)
262 | {
263 | ProvideData data = new ProvideData();
264 |
265 | data.ClientID = m_osu_id;
266 | data.Status = m_last_osu_status;
267 |
268 | data.PlayMode = OsuPlayMode.Unknown;
269 | data.Beatmap = Beatmap.Empty;
270 | data.Mods = ModsInfo.Empty;
271 |
272 | data.Combo = 0;
273 | data.Count300 = 0;
274 | data.Count100 = 0;
275 | data.Count50 = 0;
276 | data.CountMiss = 0;
277 | data.CountGeki = 0;
278 | data.CountKatu = 0;
279 | data.HealthPoint = 0;
280 | data.Accuracy = 0;
281 | data.Time = 0;
282 | data.Score = 0;
283 |
284 | data.PlayType = PlayType.Unknown;
285 | data.HitEvents = new List();
286 |
287 | if (HasMask(mask, ProvideDataMask.Beatmap))
288 | {
289 | if (OnBeatmapChanged == null) OnBeatmapChanged += (t) => { };
290 | data.Beatmap = m_last_beatmap;
291 | }
292 |
293 | if (HasMask(mask, ProvideDataMask.HealthPoint))
294 | {
295 | if (OnHealthPointChanged == null) OnHealthPointChanged += (t) => { };
296 | data.HealthPoint = m_last_hp;
297 | }
298 |
299 | if (HasMask(mask, ProvideDataMask.Accuracy))
300 | {
301 | if (OnAccuracyChanged == null) OnAccuracyChanged += (t) => { };
302 | data.Accuracy = m_last_acc;
303 | }
304 |
305 | if (HasMask(mask, ProvideDataMask.Combo))
306 | {
307 | if (OnComboChanged == null) OnComboChanged += (t) => { };
308 | data.Combo = m_last_combo;
309 | }
310 |
311 | if (HasMask(mask, ProvideDataMask.Count300))
312 | {
313 | if (OnCount300Changed == null) OnCount300Changed += (t) => { };
314 | data.Count300 = m_last_300;
315 | }
316 |
317 | if (HasMask(mask, ProvideDataMask.Count100))
318 | {
319 | if (OnCount100Changed == null) OnCount100Changed += (t) => { };
320 | data.Count100 = m_last_100;
321 | }
322 |
323 | if (HasMask(mask, ProvideDataMask.Count50))
324 | {
325 | if (OnCount50Changed == null) OnCount50Changed += (t) => { };
326 | data.Count50 = m_last_50;
327 | }
328 |
329 | if (HasMask(mask, ProvideDataMask.CountMiss))
330 | {
331 | if (OnCountMissChanged == null) OnCountMissChanged += (t) => { };
332 | data.CountMiss = m_last_miss;
333 | }
334 |
335 | if (HasMask(mask, ProvideDataMask.CountGeki))
336 | {
337 | if (OnCountGekiChanged == null) OnCountGekiChanged += (t) => { };
338 | data.CountGeki = m_last_geki;
339 | }
340 |
341 | if (HasMask(mask, ProvideDataMask.CountKatu))
342 | {
343 | if (OnCountKatuChanged == null) OnCountKatuChanged += (t) => { };
344 | data.CountKatu = m_last_katu;
345 | }
346 |
347 | if (HasMask(mask, ProvideDataMask.Time))
348 | {
349 | if (OnPlayingTimeChanged == null) OnPlayingTimeChanged += (t) => { };
350 | data.Time = m_playing_time;
351 | }
352 |
353 | if (HasMask(mask, ProvideDataMask.Mods))
354 | {
355 | if (OnModsChanged == null) OnModsChanged += (t) => { };
356 | data.Mods = m_last_mods;
357 | }
358 |
359 | if (HasMask(mask, ProvideDataMask.GameMode))
360 | {
361 | if (OnPlayModeChanged == null) OnPlayModeChanged += (t, t2) => { };
362 | data.PlayMode = m_last_mode;
363 | }
364 |
365 | if (HasMask(mask, ProvideDataMask.Score))
366 | {
367 | if (OnScoreChanged == null) OnScoreChanged += (s) => { };
368 | data.Score = m_last_score;
369 | }
370 |
371 | if (HasMask(mask, ProvideDataMask.ErrorStatistics))
372 | {
373 | if (OnErrorStatisticsChanged == null) OnErrorStatisticsChanged += (e) => { };
374 | data.ErrorStatistics = m_last_error_statistics;
375 | }
376 |
377 | if (HasMask(mask, ProvideDataMask.Playername))
378 | {
379 | if (OnPlayerChanged == null) OnPlayerChanged += (e) => { };
380 | data.Playername = m_last_playername;
381 | }
382 |
383 | if (HasMask(mask, ProvideDataMask.HitEvent))
384 | {
385 | if (OnHitEventsChanged == null) OnHitEventsChanged += (t, l) => { };
386 | data.PlayType = m_play_type;
387 | data.HitEvents = m_hit_events;
388 | }
389 |
390 | return data;
391 | }
392 |
393 | #endregion Get Current Data
394 |
395 | #region Init Finder
396 | private const long RETRY_INTERVAL = 3000;
397 |
398 | private Dictionary finder_timer_dict = new Dictionary();
399 | private T InitFinder(string success_fmt, string failed_fmt) where T : OsuFinderBase
400 | {
401 | if (!finder_timer_dict.ContainsKey(typeof(T)))
402 | finder_timer_dict.Add(typeof(T), 0);
403 |
404 | T finder = null;
405 | long timer = finder_timer_dict[typeof(T)];
406 |
407 | if (timer % RETRY_INTERVAL == 0)
408 | {
409 | finder = typeof(T).GetConstructors()[0].Invoke(new object[] { m_osu_process }) as T;
410 | if (finder.TryInit())
411 | {
412 | timer = 0;
413 | Logger.Info(string.Format(success_fmt, m_osu_id));
414 | return finder;
415 | }
416 |
417 | finder = null;
418 | Logger.Error(string.Format(failed_fmt, m_osu_id, RETRY_INTERVAL / 1000));
419 | }
420 | timer += Setting.ListenInterval;
421 | finder_timer_dict[typeof(T)] = timer;
422 | return finder;
423 | }
424 | #endregion
425 |
426 | #region Find Osu Setting
427 | private long find_osu_process_timer = 0;
428 | private const long FIND_OSU_RETRY_INTERVAL = 5000;
429 |
430 | private void FindOsuProcess()
431 | {
432 | if (find_osu_process_timer > FIND_OSU_RETRY_INTERVAL)
433 | {
434 | find_osu_process_timer = 0;
435 | Process[] process_list;
436 |
437 | process_list = Process.GetProcessesByName("osu!");
438 |
439 | if (s_stop_flag) return;
440 | if (process_list.Length != 0)
441 | {
442 | if (m_is_tourney)
443 | {
444 | foreach (var p in process_list)
445 | {
446 | if (p.MainWindowTitle.Contains($"Client {m_osu_id}"))
447 | {
448 | m_osu_process = p;
449 | break;
450 | }
451 | }
452 | }
453 | else
454 | {
455 | m_osu_process = process_list[0];
456 | }
457 |
458 | if (m_osu_process != null)
459 | {
460 | FindSongPathAndUsername();
461 | Logger.Info(string.Format(LANG_OSU_FOUND, m_osu_id));
462 | return;
463 | }
464 | }
465 | find_osu_process_timer = 0;
466 | if (!Setting.DisableProcessNotFoundInformation)
467 | Logger.Error(string.Format(LANG_OSU_NOT_FOUND, m_osu_id));
468 | }
469 | find_osu_process_timer += Setting.ListenInterval;
470 | }
471 |
472 | private void FindSongPathAndUsername()
473 | {
474 | string osu_path = "";
475 | find_osu_filename:
476 | try
477 | {
478 | osu_path = Path.GetDirectoryName(m_osu_process.MainModule.FileName);
479 | }
480 | catch (Win32Exception e)
481 | {
482 | if(Setting.DebugMode)
483 | Logger.Warn($"Win32Exception: {e.ToString()}");
484 | Logger.Warn("Can't get osu path, Retry after 2 seconds.");
485 | Thread.Sleep(2000);
486 | goto find_osu_filename;
487 | }
488 | string osu_config_file = Path.Combine(osu_path, $"osu!.{PathHelper.WindowsPathStrip(Environment.UserName)}.cfg");
489 | string song_path;
490 |
491 |
492 | try
493 | {
494 | using (var fs = File.OpenRead(osu_config_file))
495 | using (var sr = new StreamReader(fs))
496 | {
497 | while (!sr.EndOfStream)
498 | {
499 | string line = sr.ReadLine();
500 |
501 | if (line.StartsWith("BeatmapDirectory"))
502 | {
503 | if (Directory.Exists(Setting.ForceOsuSongsDirectory))
504 | {
505 | Setting.SongsPath = Setting.ForceOsuSongsDirectory;
506 | }
507 | else
508 | {
509 | Logger.Info($"ForceOsuSongsDirectory: {Setting.ForceOsuSongsDirectory}");
510 | Logger.Info($"The ForceOsuSongsDirectory does not exist, try searching for the songs path.");
511 | song_path = line.Split('=')[1].Trim();
512 | if (Path.IsPathRooted(song_path))
513 | Setting.SongsPath = song_path;
514 | else
515 | Setting.SongsPath = Path.Combine(osu_path, song_path);
516 | }
517 | }
518 | else if (line.StartsWith("Username"))
519 | {
520 | Setting.Username = line.Split('=')[1].Trim();
521 | }
522 | else if (line.StartsWith("LastVersion")&&!line.StartsWith("LastVersionPermissionsFailed"))
523 | {
524 | Setting.OsuVersion = line.Split('=')[1].Trim();
525 | Logger.Info($"OSU Client Verison:{Setting.OsuVersion} ORTDP Version:{OsuRTDataProviderPlugin.VERSION}");
526 | }
527 | }
528 | }
529 | }
530 | catch (Exception e)
531 | {
532 | Logger.Error($"Exception: {e.ToString()}");
533 | }
534 |
535 | if (string.IsNullOrWhiteSpace(Setting.SongsPath))
536 | {
537 | Logger.Warn($"Search failed, use default songs path.");
538 | Setting.SongsPath = Path.Combine(osu_path,"Songs");
539 | }
540 | Logger.Info($"Osu Path: {osu_path}");
541 | Logger.Info($"Beatmap Path: {Setting.SongsPath}");
542 | }
543 | #endregion
544 |
545 | private void ListenLoopUpdate()
546 | {
547 | OsuStatus status = GetCurrentOsuStatus();
548 |
549 | if (status == OsuStatus.NoFoundProcess)
550 | {
551 | m_osu_process = null;
552 | m_play_finder = null;
553 | m_status_finder = null;
554 | m_beatmap_finder = null;
555 | m_mode_finder = null;
556 | m_hit_event_finder = null;
557 |
558 | FindOsuProcess();
559 | }
560 |
561 | //Waiting for osu to start
562 | if (status != OsuStatus.NoFoundProcess && status != OsuStatus.Unkonwn)
563 | {
564 | //Wait for player to playing
565 | if (status == OsuStatus.Playing)
566 | {
567 | if (m_play_finder == null)
568 | {
569 | m_play_finder = InitFinder(LANG_INIT_PLAY_FINDER_SUCCESS, LANG_INIT_PLAY_FINDER_FAILED);
570 | }
571 |
572 | if (Setting.GameMode == "Auto" && m_mode_finder == null)
573 | {
574 | m_mode_finder = InitFinder(LANG_INIT_MODE_FINDER_SUCCESS, LANG_INIT_MODE_FINDER_FAILED);
575 | }
576 |
577 | if (m_hit_event_finder == null)
578 | {
579 | m_hit_event_finder = InitFinder(LANG_INIT_HIT_EVENT_SUCCESS, LANG_INIT_HIT_EVENT_FAIL);
580 | }
581 | }
582 |
583 | if (m_beatmap_finder == null)
584 | {
585 | m_beatmap_finder = InitFinder(LANG_INIT_BEATMAP_FINDER_SUCCESS, LANG_INIT_BEATMAP_FINDER_FAILED);
586 | }
587 |
588 | if (m_mode_finder != null)
589 | {
590 | OsuPlayMode mode = OsuPlayMode.Osu;
591 |
592 | if (OnPlayModeChanged != null) mode = m_mode_finder.GetMode();
593 |
594 | if (m_last_mode != mode)
595 | OnPlayModeChanged?.Invoke(m_last_mode, mode);
596 |
597 | m_last_mode = mode;
598 | }
599 | else
600 | {
601 | if (Setting.GameMode != "Auto")
602 | {
603 | if (s_game_mode_map.TryGetValue(Setting.GameMode, out var mode))
604 | if (m_last_mode != mode)
605 | OnPlayModeChanged?.Invoke(m_last_mode, mode);
606 |
607 | m_last_mode = mode;
608 | }
609 | }
610 |
611 | if (OnHitEventsChanged != null && m_hit_event_finder != null)
612 | {
613 | // Hit events should work with playing time
614 | if (OnPlayingTimeChanged == null) OnPlayingTimeChanged += (t) => { };
615 |
616 | bool hasChanged;
617 | m_hit_event_finder.GetHitEvents(status, m_playing_time, out m_play_type, out m_hit_events, out hasChanged);
618 | if (hasChanged)
619 | {
620 | OnHitEventsChanged?.Invoke(m_play_type, m_hit_events);
621 | }
622 | }
623 |
624 | if (m_play_finder != null)
625 | {
626 | Beatmap beatmap = Beatmap.Empty;
627 | ModsInfo mods = ModsInfo.Empty;
628 | ErrorStatisticsResult error_statistics = ErrorStatisticsResult.Empty;
629 | int cb = 0;
630 | int pt = 0;
631 | int n300 = 0;
632 | int n100 = 0;
633 | int n50 = 0;
634 | int ngeki = 0;
635 | int nkatu = 0;
636 | int nmiss = 0;
637 | int score = 0;
638 | double hp = 0.0;
639 | double acc = 0.0;
640 | string playername = Setting.Username;
641 |
642 | try
643 | {
644 | if (OnPlayingTimeChanged != null) pt = m_play_finder.GetPlayingTime();
645 | if (OnBeatmapChanged != null) beatmap = m_beatmap_finder.GetCurrentBeatmap(m_osu_id);
646 | if (Setting.EnableModsChangedAtListening && status != OsuStatus.Playing)
647 | if (OnModsChanged != null) mods = m_play_finder.GetCurrentModsAtListening();
648 |
649 | if (beatmap != Beatmap.Empty && beatmap != m_last_beatmap)
650 | {
651 | OnBeatmapChanged?.Invoke(beatmap);
652 | }
653 |
654 | if (status == OsuStatus.Playing)
655 | {
656 | if (OnErrorStatisticsChanged != null) error_statistics = m_play_finder.GetUnstableRate();
657 | if (OnModsChanged != null) mods = m_play_finder.GetCurrentMods();
658 | if (OnComboChanged != null) cb = m_play_finder.GetCurrentCombo();
659 | if (OnCount300Changed != null) n300 = m_play_finder.Get300Count();
660 | if (OnCount100Changed != null) n100 = m_play_finder.Get100Count();
661 | if (OnCount50Changed != null) n50 = m_play_finder.Get50Count();
662 | if (OnCountGekiChanged != null) ngeki = m_play_finder.GetGekiCount();
663 | if (OnCountKatuChanged != null) nkatu = m_play_finder.GetKatuCount();
664 | if (OnCountMissChanged != null) nmiss = m_play_finder.GetMissCount();
665 | if (OnAccuracyChanged != null) acc = m_play_finder.GetCurrentAccuracy();
666 | if (OnHealthPointChanged != null) hp = m_play_finder.GetCurrentHP();
667 | if (OnScoreChanged != null) score = m_play_finder.GetCurrentScore();
668 | if (OnPlayerChanged != null) playername = m_play_finder.GetCurrentPlayerName();
669 | }
670 |
671 | if (status != m_last_osu_status)
672 | OnStatusChanged?.Invoke(m_last_osu_status, status);
673 |
674 | if (mods != ModsInfo.Empty && !ModsInfo.VaildMods(mods))
675 | mods = m_last_mods;
676 |
677 | if (mods != m_last_mods)
678 | OnModsChanged?.Invoke(mods);
679 |
680 | if (hp != m_last_hp)
681 | OnHealthPointChanged?.Invoke(hp);
682 |
683 | if (acc != m_last_acc)
684 | OnAccuracyChanged?.Invoke(acc);
685 |
686 | if (!error_statistics.Equals(m_last_error_statistics))
687 | OnErrorStatisticsChanged?.Invoke(error_statistics);
688 |
689 | if (playername != m_last_playername)
690 | OnPlayerChanged?.Invoke(playername); // Added null check
691 |
692 | if (score != m_last_score)
693 | OnScoreChanged?.Invoke(score);
694 |
695 | if (n300 != m_last_300)
696 | OnCount300Changed?.Invoke(n300);
697 |
698 | if (n100 != m_last_100)
699 | OnCount100Changed?.Invoke(n100);
700 |
701 | if (n50 != m_last_50)
702 | OnCount50Changed?.Invoke(n50);
703 |
704 | if (ngeki != m_last_geki)
705 | OnCountGekiChanged?.Invoke(ngeki);
706 |
707 | if (nkatu != m_last_katu)
708 | OnCountKatuChanged?.Invoke(nkatu);
709 |
710 | if (nmiss != m_last_miss)
711 | OnCountMissChanged?.Invoke(nmiss);
712 |
713 | if (cb != m_last_combo)
714 | OnComboChanged?.Invoke(cb);
715 |
716 | if (pt != m_playing_time)
717 | OnPlayingTimeChanged?.Invoke(pt);
718 | }
719 | catch (Exception e)
720 | {
721 | Logger.Error(e.ToString());
722 | }
723 |
724 | m_last_beatmap = beatmap;
725 | m_last_mods = mods;
726 | m_last_hp = hp;
727 | m_last_acc = acc;
728 | m_last_error_statistics = error_statistics;
729 | m_last_combo = cb;
730 | m_playing_time = pt;
731 | m_last_300 = n300;
732 | m_last_100 = n100;
733 | m_last_50 = n50;
734 | m_last_geki = ngeki;
735 | m_last_katu = nkatu;
736 | m_last_miss = nmiss;
737 | m_last_score = score;
738 | m_last_osu_status = status;
739 | m_last_playername = playername;
740 | }
741 | }
742 | }
743 | private OsuStatus GetCurrentOsuStatus()
744 | {
745 | try
746 | {
747 | if (m_osu_process == null) return OsuStatus.NoFoundProcess;
748 | if (m_osu_process.HasExited == true) return OsuStatus.NoFoundProcess;
749 |
750 | if (m_status_finder == null)
751 | {
752 | m_status_finder = InitFinder(
753 | LANG_INIT_STATUS_FINDER_SUCCESS,
754 | LANG_INIT_STATUS_FINDER_FAILED
755 | );
756 | return OsuStatus.Unkonwn;
757 | }
758 | }
759 | catch (Win32Exception e)
760 | {
761 | Logger.Error($":{e.Message}");
762 | }
763 |
764 | OsuInternalStatus mode = m_status_finder.GetCurrentOsuModes();
765 | switch (mode)
766 | {
767 | case OsuInternalStatus.Edit:
768 | return OsuStatus.Editing;
769 |
770 | case OsuInternalStatus.Play:
771 | return OsuStatus.Playing;
772 |
773 | case OsuInternalStatus.RankingTagCoop:
774 | case OsuInternalStatus.RankingTeam:
775 | case OsuInternalStatus.RankingVs:
776 | case OsuInternalStatus.Rank:
777 | return OsuStatus.Rank;
778 |
779 | case OsuInternalStatus.BeatmapImport:
780 | case OsuInternalStatus.Menu:
781 | case OsuInternalStatus.OnlineSelection:
782 | return OsuStatus.Idle;
783 |
784 | case OsuInternalStatus.MatchSetup:
785 | return OsuStatus.MatchSetup;
786 |
787 | case OsuInternalStatus.Lobby:
788 | return OsuStatus.Lobby;
789 |
790 | case OsuInternalStatus.SelectEdit:
791 | case OsuInternalStatus.SelectMulti:
792 | case OsuInternalStatus.SelectPlay:
793 | return OsuStatus.SelectSong;
794 |
795 | default:
796 | return OsuStatus.Unkonwn;
797 | }
798 | }
799 | }
800 | }
801 |
--------------------------------------------------------------------------------
/Listen/PlayMode.cs:
--------------------------------------------------------------------------------
1 | namespace OsuRTDataProvider.Listen
2 | {
3 | public enum OsuPlayMode
4 | {
5 | Osu = 0,
6 | Taiko = 1,
7 | CatchTheBeat = 2,
8 | Mania = 3,
9 |
10 | Unknown = -1
11 | }
12 | }
--------------------------------------------------------------------------------
/Listen/ProvideData.cs:
--------------------------------------------------------------------------------
1 | using OsuRTDataProvider.BeatmapInfo;
2 | using OsuRTDataProvider.Mods;
3 | using System;
4 | using System.Collections.Generic;
5 | using static OsuRTDataProvider.Listen.OsuListenerManager;
6 |
7 | namespace OsuRTDataProvider.Listen
8 | {
9 | [Flags]
10 | public enum ProvideDataMask : uint
11 | {
12 | HealthPoint = 1u << 0,
13 | Accuracy = 1u << 1,
14 | Combo = 1u << 2,
15 | Count300 = 1u << 3,
16 | Count100 = 1u << 4,
17 | Count50 = 1u << 5,
18 | CountMiss = 1u << 6,
19 | CountGeki = 1u << 7,
20 | CountKatu = 1u << 8,
21 | Time = 1u << 9,
22 | Mods = 1u << 10,
23 | GameMode = 1u << 11,
24 | Beatmap = 1u << 12,
25 | Score = 1u << 13,
26 | ErrorStatistics = 1u<<14,
27 | Playername = 1u<<15,
28 | HitEvent = 1u << 16,
29 |
30 | HitCount = Count300 | Count100 | Count50 | CountMiss | CountGeki | CountKatu,
31 | }
32 |
33 | public class ProvideData
34 | {
35 | public int ClientID;
36 | public OsuStatus Status;
37 | public string Playername;
38 |
39 | public OsuPlayMode PlayMode;
40 | public Beatmap Beatmap;
41 | public ModsInfo Mods;
42 | public ErrorStatisticsResult ErrorStatistics;
43 |
44 | public double HealthPoint;
45 | public double Accuracy;
46 | public int Combo;
47 | public int Count300;
48 | public int Count100;
49 | public int Count50;
50 | public int CountMiss;
51 | public int CountGeki;
52 | public int CountKatu;
53 | public int Time;
54 | public int Score;
55 |
56 | public PlayType PlayType;
57 | public List HitEvents;
58 | }
59 | }
--------------------------------------------------------------------------------
/Memory/BeatmapOffsetInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Dynamic;
4 | using System.Linq;
5 | using System.Reflection;
6 |
7 | namespace OsuRTDataProvider.Memory
8 | {
9 |
10 | public class BeatmapOffsetInfo
11 | {
12 | public enum CompareCondition
13 | {
14 | None,
15 | Older,
16 | OlderOrEquals,
17 | Equals,
18 | NewerOrEquals,
19 | Newer
20 | }
21 |
22 | public double Version { get; set; }
23 | public int BeatmapAddressOffset { get; set; }
24 | public int BeatmapSetAddressOffset { get; set; }
25 | public int BeatmapFolderAddressOffset { get; set; }
26 | public int BeatmapFileNameAddressOffset { get; set; }
27 | public CompareCondition VersionCompareCondition { get; set; }
28 | public void AddOffset(BeatmapOffsetInfo beatmapOffsetInfo)
29 | {
30 | BeatmapAddressOffset += beatmapOffsetInfo.BeatmapAddressOffset;
31 | BeatmapFolderAddressOffset += beatmapOffsetInfo.BeatmapFolderAddressOffset;
32 | BeatmapSetAddressOffset += beatmapOffsetInfo.BeatmapSetAddressOffset;
33 | BeatmapFileNameAddressOffset += beatmapOffsetInfo.BeatmapFileNameAddressOffset;
34 | }
35 |
36 | public void SetOffset(BeatmapOffsetInfo beatmapOffsetInfo)
37 | {
38 | BeatmapAddressOffset = beatmapOffsetInfo.BeatmapAddressOffset;
39 | BeatmapFolderAddressOffset = beatmapOffsetInfo.BeatmapFolderAddressOffset;
40 | BeatmapSetAddressOffset = beatmapOffsetInfo.BeatmapSetAddressOffset;
41 | BeatmapFileNameAddressOffset = beatmapOffsetInfo.BeatmapFileNameAddressOffset;
42 | }
43 |
44 |
45 | public override string ToString() => $"{VersionCompareCondition} " +
46 | $"{Version} " +
47 | $"BeatmapAddress: {BeatmapAddressOffset} " +
48 | $"BeatmapFolder: {BeatmapAddressOffset} " +
49 | $"BeatmapSet: {BeatmapSetAddressOffset} " +
50 | $"BeatmapFileName: {BeatmapFileNameAddressOffset}";
51 |
52 | public static BeatmapOffsetInfo MatchVersion(double version)
53 | {
54 | Dictionary propertyForVersions = new Dictionary();
55 | Type t = typeof(BeatmapOffsetInfo);
56 | var methods = t.GetProperties(BindingFlags.Static | BindingFlags.Public);
57 | foreach (var method in methods)
58 | {
59 | if (!method.IsDefined(typeof(OsuVersionCompareInfoAttribute), false))
60 | {
61 | continue;
62 | }
63 |
64 | var attr = method.GetCustomAttribute();
65 |
66 | propertyForVersions.Add(attr, method);
67 | }
68 | propertyForVersions = propertyForVersions.OrderByDescending(item => item.Key.OsuVersion).
69 | ToDictionary(item => item.Key,item => item.Value);
70 |
71 | bool versionMatched = false;
72 | foreach (var compareInfo in propertyForVersions)
73 | {
74 | double comparedVersion = compareInfo.Key.OsuVersion;
75 | switch (compareInfo.Key.CompareCondition)
76 | {
77 | case CompareCondition.Older:
78 | versionMatched = comparedVersion < version;
79 | break;
80 | case CompareCondition.OlderOrEquals:
81 | versionMatched = comparedVersion <= version;
82 | break;
83 | case CompareCondition.Newer:
84 | versionMatched = comparedVersion > version;
85 | break;
86 | case CompareCondition.NewerOrEquals:
87 | versionMatched = comparedVersion <= version;
88 | break;
89 | case CompareCondition.Equals:
90 | //https://www.jetbrains.com/help/resharper/2022.1/CompareOfFloatsByEqualityOperator.html
91 | versionMatched = Math.Abs(comparedVersion - version) < 0.00001;
92 | break;
93 | }
94 |
95 | if (versionMatched)
96 | {
97 | return (BeatmapOffsetInfo)compareInfo.Value.GetValue(null);
98 | }
99 | }
100 |
101 |
102 | return new BeatmapOffsetInfo {Version = version};
103 | }
104 |
105 | [OsuVersionCompareInfo(20190816, CompareCondition.Older)]
106 | public static BeatmapOffsetInfo Version20190816 { get; } = new BeatmapOffsetInfo
107 | {
108 | Version = 20190816,
109 | BeatmapFileNameAddressOffset = -4,
110 | BeatmapSetAddressOffset = -4,
111 | BeatmapAddressOffset = -4,
112 | BeatmapFolderAddressOffset = -4,
113 | VersionCompareCondition = CompareCondition.Older
114 | };
115 |
116 |
117 | [OsuVersionCompareInfo(20211014, CompareCondition.NewerOrEquals)]
118 | public static BeatmapOffsetInfo Version20211014 { get; } = new BeatmapOffsetInfo
119 | {
120 | Version = 20211014,
121 | BeatmapFileNameAddressOffset = 0,
122 | BeatmapSetAddressOffset = 0,
123 | BeatmapAddressOffset = 4,
124 | BeatmapFolderAddressOffset = 0,
125 | VersionCompareCondition = CompareCondition.NewerOrEquals
126 | };
127 |
128 | [OsuVersionCompareInfo(20220406.3, CompareCondition.NewerOrEquals)]
129 | public static BeatmapOffsetInfo Version202204063 { get; } = new BeatmapOffsetInfo
130 | {
131 | Version = 20220406.3,
132 | BeatmapFileNameAddressOffset = 8,
133 | BeatmapSetAddressOffset = 0,
134 | BeatmapAddressOffset = 0,
135 | BeatmapFolderAddressOffset = 4,
136 | VersionCompareCondition = CompareCondition.NewerOrEquals
137 | };
138 |
139 |
140 | }
141 | }
--------------------------------------------------------------------------------
/Memory/LogHelper.cs:
--------------------------------------------------------------------------------
1 | using Sync.Tools;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Security.Cryptography;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace OsuRTDataProvider.Memory
10 | {
11 | static class LogHelper
12 | {
13 | public static void LogToFile(string plainText)
14 | {
15 | /*
16 | string msg = plainText;
17 | #if DEBUG
18 | ISyncOutput output = IO.CurrentIO;
19 | #else
20 | ISyncOutput output = IO.FileLogger;
21 | #endif
22 |
23 | output.Write($"{msg}");
24 | */
25 |
26 | //now is directly output to default in DebugMode
27 | Logger.Debug(plainText);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Memory/OsuBeatmapFinder.cs:
--------------------------------------------------------------------------------
1 | using OsuRTDataProvider.BeatmapInfo;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Globalization;
6 | using System.IO;
7 | using System.Text;
8 |
9 | namespace OsuRTDataProvider.Memory
10 | {
11 | internal class OsuBeatmapFinder : OsuFinderBase
12 | {
13 |
14 | private static readonly string s_beatmap_pattern = "\x74\x24\x8B\x0D\x0\x0\x0\x0\x85\xC9\x74\x1A";
15 |
16 | private static readonly string s_beatmap_mask = "xxxx????xxxx";
17 |
18 | private static readonly int s_beatmap_offset = 0xc4;
19 | private static readonly int s_beatmap_set_offset = 0xc8;
20 |
21 | private static readonly int s_beatmap_folder_offset = 0x74;
22 | private static readonly int s_beatmap_filename_offset = 0x8c;
23 |
24 | private BeatmapOffsetInfo CurrentOffset { get; } = new BeatmapOffsetInfo()
25 | {
26 | BeatmapAddressOffset = s_beatmap_offset,
27 | BeatmapSetAddressOffset = s_beatmap_set_offset,
28 | BeatmapFolderAddressOffset = s_beatmap_folder_offset,
29 | BeatmapFileNameAddressOffset = s_beatmap_filename_offset
30 | };
31 |
32 | private const int MAX_RETRY_COUNT = 10;
33 |
34 | private IntPtr m_beatmap_address;
35 |
36 | public OsuBeatmapFinder(Process osu) : base(osu)
37 | {
38 | var versionBeatmapOffset = BeatmapOffsetInfo.MatchVersion(Setting.CurrentOsuVersionValue);
39 | CurrentOffset.AddOffset(versionBeatmapOffset);
40 | Logger.Info($"applied offset for osu!version({Setting.CurrentOsuVersionValue.ToString(CultureInfo.InvariantCulture)}) : {versionBeatmapOffset}");
41 | }
42 |
43 |
44 | public override bool TryInit()
45 | {
46 | bool success = false;
47 | SigScan.Reload();
48 | {
49 | //Find Beatmap ID Address
50 | m_beatmap_address = SigScan.FindPattern(StringToByte(s_beatmap_pattern), s_beatmap_mask, 4);
51 | LogHelper.LogToFile($"Beatmap Base Address (0):0x{(int)m_beatmap_address:X8}");
52 |
53 | success = TryReadIntPtrFromMemory(m_beatmap_address, out m_beatmap_address);
54 | LogHelper.LogToFile($"Beatmap Base Address (1):0x{(int)m_beatmap_address:X8}");
55 | }
56 | SigScan.ResetRegion();
57 |
58 | if (m_beatmap_address == IntPtr.Zero)
59 | success = false;
60 |
61 | return success;
62 | }
63 |
64 | public Beatmap GetCurrentBeatmap(int osu_id)
65 | {
66 | TryReadIntPtrFromMemory(m_beatmap_address, out IntPtr cur_beatmap_address);
67 | TryReadIntFromMemory(cur_beatmap_address + CurrentOffset.BeatmapAddressOffset, out int id);
68 | TryReadIntFromMemory(cur_beatmap_address + CurrentOffset.BeatmapSetAddressOffset, out int set_id);
69 |
70 | string filename = GetCurrentBeatmapFilename();
71 | string folder = GetCurrentBeatmapFolder();
72 |
73 | Beatmap beatmap = Beatmap.Empty;
74 |
75 | try
76 | {
77 | if (!(string.IsNullOrWhiteSpace(filename) || string.IsNullOrWhiteSpace(folder)))
78 | {
79 | string folder_full = Path.Combine(Setting.SongsPath, folder);
80 | string filename_full = Path.Combine(folder_full, filename);
81 | using (var fs = File.OpenRead(filename_full))
82 | {
83 | beatmap = new Beatmap(osu_id, set_id, id, fs);
84 | }
85 | }
86 | }
87 | catch (Exception e)
88 | {
89 | StringBuilder sb = new StringBuilder();
90 |
91 | sb.AppendLine("------------- ORTDP(Exception)--------------- ");
92 | sb.AppendLine(e.ToString());
93 |
94 | if (Setting.DebugMode)
95 | {
96 | sb.AppendLine("--------------ORTDP(Detail)-----------------");
97 | sb.AppendLine($"Songs Path:{Setting.SongsPath}");
98 | sb.AppendLine($"Filename:{filename}");
99 | sb.AppendLine($"Folder:{folder}");
100 | sb.AppendLine($"BeatmapID:{id}");
101 | sb.AppendLine($"BeatmapSetID:{set_id}");
102 | sb.AppendLine("--------------------------------------------");
103 | }
104 |
105 | Logger.Warn(sb.ToString());
106 | }
107 |
108 | return beatmap;
109 | }
110 |
111 | #region Beatmap Info
112 |
113 | private string GetCurrentBeatmapFolder()
114 | {
115 | TryReadIntPtrFromMemory(m_beatmap_address, out var cur_beatmap_address);
116 | bool success = TryReadStringFromMemory(cur_beatmap_address + CurrentOffset.BeatmapFolderAddressOffset, out string str);
117 | if (!success) return "";
118 | return str;
119 | }
120 |
121 | private string GetCurrentBeatmapFilename()
122 | {
123 | TryReadIntPtrFromMemory(m_beatmap_address, out var cur_beatmap_address);
124 | bool success = TryReadStringFromMemory(cur_beatmap_address + CurrentOffset.BeatmapFileNameAddressOffset, out string str);
125 | if (!success) return "";
126 | return str;
127 | }
128 |
129 | #endregion Beatmap Info
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Memory/OsuFinderBase.cs:
--------------------------------------------------------------------------------
1 | using Sync.Tools;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Runtime.InteropServices;
6 | using System.Security.Cryptography;
7 | using System.Text;
8 |
9 | namespace OsuRTDataProvider.Memory
10 | {
11 | internal abstract class OsuFinderBase
12 | {
13 | protected SigScan SigScan { get; private set; }
14 | protected Process OsuProcess { get; private set; }
15 |
16 | private int max_bytes_length = 4096;
17 | private byte[] _bytes_buf = new byte[4096];
18 |
19 | private const int STRING_BUFFER_LENGTH_MAX = 4096;
20 | private byte[] _string_bytes_buf = new byte[4096];
21 |
22 | public int ReadBufferLengthMax
23 | {
24 | get => max_bytes_length;
25 | set
26 | {
27 | max_bytes_length = value;
28 | _bytes_buf = new byte[value];
29 | }
30 | }
31 |
32 | public OsuFinderBase(Process process)
33 | {
34 | OsuProcess = process;
35 | SigScan = new SigScan(OsuProcess);
36 | }
37 |
38 | private List _a = new List(64);
39 |
40 | protected byte[] StringToByte(string s)
41 | {
42 | _a.Clear();
43 | foreach (var c in s) _a.Add((byte)c);
44 | return _a.ToArray();
45 | }
46 |
47 | private byte[] _number_buf = new byte[8];
48 |
49 | protected bool TryReadIntPtrFromMemory(IntPtr address, out IntPtr value)
50 | {
51 | int ret_size_ptr = 0;
52 | value = IntPtr.Zero;
53 |
54 | if (SigScan.ReadProcessMemory(OsuProcess.Handle, address, _number_buf, sizeof(int), out ret_size_ptr))
55 | {
56 | value = (IntPtr)BitConverter.ToInt32(_number_buf, 0);
57 | return true;
58 | }
59 | return false;
60 | }
61 |
62 | protected bool TryReadIntFromMemory(IntPtr address, out int value)
63 | {
64 | int ret_size_ptr = 0;
65 | value = 0;
66 |
67 | if (SigScan.ReadProcessMemory(OsuProcess.Handle, address, _number_buf, sizeof(int), out ret_size_ptr))
68 | {
69 | value = BitConverter.ToInt32(_number_buf, 0);
70 | return true;
71 | }
72 | return false;
73 | }
74 |
75 | protected bool TryReadShortFromMemory(IntPtr address, out ushort value)
76 | {
77 | int ret_size_ptr = 0;
78 | value = 0;
79 |
80 | if (SigScan.ReadProcessMemory(OsuProcess.Handle, address, _number_buf, sizeof(ushort), out ret_size_ptr))
81 | {
82 | value = BitConverter.ToUInt16(_number_buf, 0);
83 | return true;
84 | }
85 | return false;
86 | }
87 |
88 | protected bool TryReadDoubleFromMemory(IntPtr address, out double value)
89 | {
90 | int ret_size_ptr = 0;
91 | value = double.NaN;
92 |
93 | if (SigScan.ReadProcessMemory(OsuProcess.Handle, address, _number_buf, sizeof(double), out ret_size_ptr))
94 | {
95 | value = BitConverter.ToDouble(_number_buf, 0);
96 | return true;
97 | }
98 | return false;
99 | }
100 |
101 | protected bool TryReadSingleFromMemory(IntPtr address, out float value)
102 | {
103 | int ret_size_ptr = 0;
104 | value = float.NaN;
105 |
106 | if (SigScan.ReadProcessMemory(OsuProcess.Handle, address, _number_buf, sizeof(float), out ret_size_ptr))
107 | {
108 | value = BitConverter.ToSingle(_number_buf, 0);
109 | return true;
110 | }
111 | return false;
112 | }
113 |
114 | protected bool TryReadStringFromMemory(IntPtr address, out string str)
115 | {
116 | str = null;
117 | TryReadIntPtrFromMemory(address, out IntPtr str_base);
118 |
119 | try
120 | {
121 | if (!TryReadIntFromMemory(str_base + 0x4, out int len))
122 | return false;
123 |
124 | len *= 2;
125 | if (len > STRING_BUFFER_LENGTH_MAX || len <= 0) return false;
126 |
127 | if (SigScan.ReadProcessMemory(OsuProcess.Handle, str_base + 0x8, _string_bytes_buf, (uint)len, out int ret_size))
128 | {
129 | if (len == ret_size)
130 | {
131 | str = Encoding.Unicode.GetString(_string_bytes_buf, 0, len);
132 | return true;
133 | }
134 | }
135 | }
136 | catch (Exception)
137 | {
138 | return false;
139 | }
140 |
141 | return false;
142 | }
143 |
144 | protected bool TryReadListFromMemory(IntPtr address, out List list)where T:struct
145 | {
146 | list = null;
147 | int type_size = Marshal.SizeOf();
148 | TryReadIntPtrFromMemory(address, out IntPtr list_ptr);
149 |
150 | try
151 | {
152 | if(!TryReadIntFromMemory(list_ptr + 0xc, out int len))
153 | return false;
154 | if (len <= 0) return false;
155 |
156 | int bytes = len * type_size;
157 | if (bytes > ReadBufferLengthMax)
158 | {
159 | ReadBufferLengthMax = (int)(bytes * 1.5);
160 | }
161 |
162 | TryReadIntPtrFromMemory(list_ptr + 0x4, out var array_ptr);
163 |
164 | if (SigScan.ReadProcessMemory(OsuProcess.Handle, array_ptr + 0x8, _bytes_buf, (uint)bytes, out int ret_size))
165 | {
166 | if (bytes == ret_size)
167 | {
168 | T[] data = new T[len];
169 | Buffer.BlockCopy(_bytes_buf, 0, data, 0, bytes);
170 | list = new List(data);
171 | return true;
172 | }
173 | }
174 | }
175 | catch (Exception)
176 | {
177 | return false;
178 | }
179 |
180 | return false;
181 | }
182 |
183 | public abstract bool TryInit();
184 | }
185 | }
--------------------------------------------------------------------------------
/Memory/OsuHitEventFinder.cs:
--------------------------------------------------------------------------------
1 | using OsuRTDataProvider.Listen;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using static OsuRTDataProvider.Listen.OsuListenerManager;
6 |
7 | namespace OsuRTDataProvider.Memory
8 | {
9 |
10 | internal class OsuHitEventFinder : OsuFinderBase
11 | {
12 | private OsuReplayHitEventFinder replay;
13 | private OsuPlayingHitEventFinder playing;
14 | private OsuStatus preStatus = OsuStatus.Unkonwn;
15 | private long preTime = long.MaxValue;
16 | private PlayType currentPlayType = PlayType.Unknown;
17 | public static readonly List EMPTY_EVENTS = new List();
18 |
19 | public OsuHitEventFinder(Process process) : base(process)
20 | {
21 | replay = new OsuReplayHitEventFinder(process);
22 | playing = new OsuPlayingHitEventFinder(process);
23 | }
24 |
25 | public override bool TryInit()
26 | {
27 | bool success = true;
28 | success &= replay.TryInit();
29 | success &= playing.TryInit();
30 | return success;
31 | }
32 |
33 | public void GetHitEvents(OsuStatus osuStatus, long playTime, out PlayType playType, out List hitEvents, out bool hasChanged)
34 | {
35 | hasChanged = false;
36 | playType = PlayType.Unknown;
37 | hitEvents = EMPTY_EVENTS;
38 | if (osuStatus != OsuStatus.Playing)
39 | {
40 | hasChanged = this.preStatus == OsuStatus.Playing;
41 | if (hasChanged) Logger.Debug($"Hit events changed due to osustatus: {OsuStatus.Playing} -> {osuStatus}");
42 |
43 | this.preStatus = osuStatus;
44 | this.currentPlayType = PlayType.Unknown;
45 | return;
46 | }
47 |
48 | if (this.preStatus != OsuStatus.Playing || preTime > playTime)
49 | {
50 | replay.Clear();
51 | playing.Clear();
52 | if (preStatus != OsuStatus.Playing)
53 | Logger.Debug($"Hit events changed due to osustatus: {preStatus} -> {OsuStatus.Playing}");
54 | else
55 | Logger.Debug($"Hit events changed due to playing time: {preTime} -> {playTime}");
56 | hasChanged = true;
57 | }
58 | this.preStatus = OsuStatus.Playing;
59 | this.preTime = playTime;
60 |
61 | if (currentPlayType == PlayType.Replay || currentPlayType == PlayType.Unknown)
62 | {
63 | hasChanged |= replay.GetEvents(out hitEvents);
64 | currentPlayType = hitEvents.Count == 0 ? PlayType.Unknown : PlayType.Replay;
65 | }
66 |
67 | if (currentPlayType == PlayType.Playing || currentPlayType == PlayType.Unknown)
68 | {
69 | hasChanged |= playing.GetEvents(out hitEvents);
70 | currentPlayType = hitEvents.Count == 0 ? PlayType.Unknown : PlayType.Playing;
71 | }
72 |
73 | playType = currentPlayType;
74 | }
75 | }
76 |
77 | internal class OsuReplayHitEventFinder : BaseOsuHitEventFinder
78 | {
79 | // D9 5D C0 EB 4E A1 ?? ?? ?? ?? 8B 48 34 4E
80 | // 74 4D A1 ?? ?? ?? ?? 8B 58 34 8D 46 FF
81 | // A1 ?? ?? ?? ?? 8B 40 34 8B 70 0C
82 | // 75 0E 33 D2 89 15 ?? ?? ?? ?? 89 15
83 | internal override string[] pattern => new string[] {
84 | "\xD9\x5D\xC0\xEB\x4E\xA1\x00\x00\x00\x00\x8B\x48\x34\x4E",
85 | "\x74\x4D\xA1\x00\x00\x00\x00\x8B\x58\x34\x8D\x46\xFF",
86 | "\xA1\x00\x00\x00\x00\x8B\x40\x34\x8B\x70\x0C",
87 | "\x75\x0E\x33\xD2\x89\x15\x0\x0\x0\x0\x89\x15"
88 | };
89 |
90 | internal override string[] mask => new string[] {
91 | "xxxxxx????xxxx", "xxx????xxxxxx", "x????xxxxxx", "xxxxxx????xx"
92 | };
93 |
94 | internal override int[] offset => new int[] { 6, 3, 1, 6 };
95 |
96 | internal override string name => "Replay";
97 |
98 | public OsuReplayHitEventFinder(Process osu) : base(osu)
99 | {
100 | }
101 | }
102 |
103 | internal class OsuPlayingHitEventFinder : BaseOsuHitEventFinder
104 | {
105 | // 83 7E 60 00 74 2C A1 ?? ?? ?? ?? 8B 50 1C 8B 4A 04
106 | // 5D C3 A1 ?? ?? ?? ?? 8B 50 1C 8B 4A 04
107 | internal override string[] pattern => new string[] {
108 | "\x83\x7E\x60\x00\x74\x2C\xA1\x00\x00\x00\x00\x8B\x50\x1C\x8B\x4A\x04",
109 | "\x5D\xC3\xA1\x00\x00\x00\x00\x8B\x50\x1C\x8B\x4A\x04"
110 | };
111 |
112 | internal override string[] mask => new string[] {
113 | "xxxxxxx????xxxxxx", "xxx????xxxxxx"
114 | };
115 |
116 | internal override int[] offset => new int[] { 7, 3 };
117 |
118 | internal override string name => "Playing";
119 |
120 | public OsuPlayingHitEventFinder(Process osu) : base(osu)
121 | {
122 | }
123 | }
124 |
125 | internal abstract class BaseOsuHitEventFinder : OsuFinderBase
126 | {
127 |
128 | IntPtr[] Addresses = new IntPtr[5] { IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero };
129 | int[] PreOffsets = new int[4] { -1, -1, -1, -1 };
130 |
131 | internal abstract string[] pattern { get; }
132 | internal abstract string[] mask { get; }
133 | internal abstract int[] offset { get; }
134 | internal abstract string name { get; }
135 |
136 | private List CurrentEvents = new List();
137 |
138 | public int GetOffset(int offsetDepth, int index)
139 | {
140 | switch (offsetDepth)
141 | {
142 | case 0: return 0x34;
143 | case 1: return 0x4;
144 | case 2: return 0x8 + index * 0x4;
145 | case 3: return 0;
146 | default: return -1; // this should not happen
147 | }
148 | }
149 |
150 |
151 | public BaseOsuHitEventFinder(Process osu) : base(osu)
152 | {
153 | }
154 |
155 | public override bool TryInit()
156 | {
157 | bool success = false;
158 |
159 | SigScan.Reload();
160 | {
161 | for (int i = 0; i < pattern.Length; i++)
162 | {
163 | Addresses[0] = SigScan.FindPattern(StringToByte(pattern[i]), mask[i], offset[i]);
164 | success = Addresses[0] != IntPtr.Zero;
165 |
166 | if (!success)
167 | {
168 | continue;
169 | }
170 |
171 | success = TryReadIntPtrFromMemory(Addresses[0], out Addresses[0]);
172 | success &= Addresses[0] != IntPtr.Zero;
173 | if (!success)
174 | {
175 | continue;
176 | }
177 |
178 | LogHelper.LogToFile($"Hit Event ({name}) Base Address: 0x{(int)Addresses[0]:X8} by pattern #{i}");
179 | break;
180 | }
181 | }
182 | SigScan.ResetRegion();
183 |
184 | return success;
185 | }
186 |
187 | private HitEvent GetHitEvent(int index)
188 | {
189 | bool success = true, changed = false;
190 | for (int depth = 0; depth < 4; depth++)
191 | {
192 | int offset = GetOffset(depth, index);
193 | if (offset != PreOffsets[depth] || changed)
194 | {
195 | PreOffsets[depth] = offset;
196 | changed = true;
197 | success &= TryReadIntPtrFromMemory(Addresses[depth], out Addresses[depth + 1]);
198 | if (Addresses[depth + 1] == IntPtr.Zero || !success)
199 | {
200 | return null;
201 | }
202 | Addresses[depth + 1] = Addresses[depth + 1] + offset;
203 |
204 | //LogHelper.LogToFile($"Hit Event Base Address({depth + 1}): 0x{(int)Addresses[depth + 1]:X8}");
205 | }
206 | }
207 |
208 | float x, y;
209 | int z;
210 | int timeStamp;
211 | success &= TryReadSingleFromMemory(Addresses[4] + 4, out x);
212 | success &= TryReadSingleFromMemory(Addresses[4] + 8, out y);
213 | success &= TryReadIntFromMemory(Addresses[4] + 12, out z);
214 | success &= TryReadIntFromMemory(Addresses[4] + 16, out timeStamp);
215 |
216 | if (success)
217 | {
218 | return new HitEvent(x, y, z, timeStamp);
219 | }
220 | return null;
221 | }
222 |
223 | public void Clear()
224 | {
225 | CurrentEvents.Clear();
226 | }
227 |
228 | // Return if the events are changed.
229 | public bool GetEvents(out List hitEvents)
230 | {
231 |
232 | hitEvents = CurrentEvents;
233 | PreOffsets = new int[4] { -1, -1, -1, -1 };
234 |
235 | int increment = 0;
236 | Stopwatch sw = new Stopwatch();
237 | sw.Start();
238 |
239 | while (true)
240 | {
241 | HitEvent hitEvent = GetHitEvent(CurrentEvents.Count);
242 | if (hitEvent == null)
243 | {
244 | break;
245 | }
246 | CurrentEvents.Add(hitEvent);
247 | increment = increment + 1;
248 | //LogHelper.LogToFile($"{increment}");
249 | }
250 |
251 | sw.Stop();
252 | long time = sw.ElapsedMilliseconds;
253 |
254 | return increment != 0;
255 | }
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/Memory/OsuInternalStatus.cs:
--------------------------------------------------------------------------------
1 | namespace OsuRTDataProvider.Memory
2 | {
3 | internal enum OsuInternalStatus
4 | {
5 | Menu,
6 | Edit,
7 | Play,
8 | Exit,
9 | SelectEdit,
10 | SelectPlay,
11 | SelectDrawings,
12 | Rank,
13 | Update,
14 | Busy,
15 | Unknown,
16 | Lobby,
17 | MatchSetup,
18 | SelectMulti,
19 | RankingVs,
20 | OnlineSelection,
21 | OptionsOffsetWizard,
22 | RankingTagCoop,
23 | RankingTeam,
24 | BeatmapImport,
25 | PackageUpdater,
26 | Benchmark,
27 | Tourney,
28 | Charts
29 | };
30 | }
--------------------------------------------------------------------------------
/Memory/OsuModeFinder.cs:
--------------------------------------------------------------------------------
1 | using OsuRTDataProvider.Listen;
2 | using System;
3 | using System.Diagnostics;
4 |
5 | namespace OsuRTDataProvider.Memory
6 | {
7 | internal class OsuPlayModeFinder : OsuFinderBase
8 | {
9 | //85 ff 74 57? a1 ?? ?? ?? ?? 89 45 e4
10 |
11 | //private static readonly string s_mode_pattern = "\x73\x01\x00\x00\x83\x3D\x00\x00\x00\x00\x01\x0F\x84\x66\";
12 | //private static readonly string s_mode_mask = "xxxxxx????xxxx";
13 | //Another playmode pattern.
14 | //It can be used when the main pattern fails.
15 | private static readonly string s_mode_pattern = "\xEC\x57\x56\x53\x3B\x0D\x00\x00\x00\x00\x74\x60\x89\x0D";
16 |
17 | private static readonly string s_mode_mask = "xxxxxx????xxxx";
18 |
19 | private IntPtr m_mode_address;
20 |
21 | public OsuPlayModeFinder(Process process) : base(process)
22 | {
23 | }
24 |
25 | public override bool TryInit()
26 | {
27 | bool success = false;
28 |
29 | SigScan.Reload();
30 | {
31 | m_mode_address = SigScan.FindPattern(StringToByte(s_mode_pattern), s_mode_mask,6);
32 | LogHelper.LogToFile($"Mode Address (0):0x{(int)m_mode_address:X8}");
33 |
34 | success = TryReadIntPtrFromMemory(m_mode_address, out m_mode_address);
35 | LogHelper.LogToFile($"Mode Address (1):0x{(int)m_mode_address:X8}");
36 | }
37 | SigScan.ResetRegion();
38 |
39 | if (m_mode_address == IntPtr.Zero)
40 | success = false;
41 |
42 | return success;
43 | }
44 |
45 | public OsuPlayMode GetMode()
46 | {
47 | TryReadIntFromMemory(m_mode_address, out int val);
48 | return (OsuPlayMode)val;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/Memory/OsuPlayFinder.cs:
--------------------------------------------------------------------------------
1 | using OsuRTDataProvider.Listen;
2 | using OsuRTDataProvider.Mods;
3 | using System;
4 | using System.Diagnostics;
5 |
6 | namespace OsuRTDataProvider.Memory
7 | {
8 | internal class OsuPlayFinder : OsuFinderBase
9 | {
10 | #region Address Arguments
11 |
12 | //0xA1,0,0,0,0,0x8D,0x56,0x0C,0xE8,0x00,0x00,0x00,0x00,0x8B,0x47,0x04
13 | private static readonly string s_acc_pattern = "\xA1\x0\x0\x0\x0\x8D\x56\x0C\xE8\x00\x00\x00\x00\x8B\x47\x04";
14 | private static readonly string s_acc_mask = "x????xxxx????xxx";
15 |
16 | //0x73,0x7a,0x8b,0x0d,0x0,0x0,0x0,0x0,0x85,0xc9,0x74,0x1f
17 | private static readonly string s_acc_pattern_fallback = "\x73\x7a\x8b\x0d\x0\x0\x0\x0\x85\xc9\x74\x1f\x8d\x55\xf0";
18 | private static readonly string s_acc_mask_fallback = "xxxx????xxxxxxx";
19 |
20 | //0x5e,0x5f,0x5d,0xc3,0xa1,0x0,0x0,0x0,0x0,0x89,0x0,0x04
21 | private static readonly string s_time_pattern = "\x5e\x5f\x5d\xc3\xa1\x0\x0\x0\x0\x89\x0\x04";
22 | private static readonly string s_time_mask = "xxxxx????x?x";
23 |
24 | private static readonly string s_global_mods_pattern = "\x8B\xF1\xA1\x00\x00\x00\x00\x25\x00\x00\x40\x00\x85\xC0";
25 | private static readonly string s_global_mods_mask = "xxx????xxxxxxx";
26 |
27 | #endregion Address Arguments
28 |
29 | private IntPtr m_acc_address;//acc,combo,hp,mods,300hit,100hit,50hit,miss Base Address
30 | private IntPtr m_time_address;
31 | private IntPtr m_mods_address;
32 |
33 | public OsuPlayFinder(Process osu) : base(osu)
34 | {
35 | }
36 |
37 | public override bool TryInit()
38 | {
39 | bool success = false;
40 | bool m_accuracy_address_success = false;
41 | bool m_time_address_success = false;
42 | bool m_mods_address_success = false;
43 |
44 | SigScan.Reload();
45 | {
46 | if (Setting.EnableModsChangedAtListening)
47 | {
48 | //Find mods address
49 | m_mods_address = SigScan.FindPattern(StringToByte(s_global_mods_pattern), s_global_mods_mask, 3);
50 | LogHelper.LogToFile($"Mods Base Address (0):0x{(int)m_mods_address:X8}");
51 |
52 | m_mods_address_success = TryReadIntPtrFromMemory(m_mods_address, out m_mods_address);
53 | LogHelper.LogToFile($"Mods Base Address (1):0x{(int)m_mods_address:X8}");
54 | }
55 |
56 | //Find acc Address
57 | m_acc_address = SigScan.FindPattern(StringToByte(s_acc_pattern), s_acc_mask, 1);
58 | LogHelper.LogToFile($"Playing Accuracy Base Address (0):0x{(int)m_acc_address:X8}");
59 |
60 | m_accuracy_address_success = TryReadIntPtrFromMemory(m_acc_address, out m_acc_address);
61 | LogHelper.LogToFile($"Playing Accuracy Base Address (1):0x{(int)m_acc_address:X8}");
62 |
63 | if (!m_accuracy_address_success)//use s_acc_pattern_fallback
64 | {
65 | LogHelper.LogToFile("Use Fallback Accuracy Pattern");
66 | m_acc_address = SigScan.FindPattern(StringToByte(s_acc_pattern_fallback), s_acc_mask_fallback, 4);
67 | LogHelper.LogToFile($"Playing Accuracy Base Address (0):0x{(int)m_acc_address:X8}");
68 |
69 | m_accuracy_address_success = TryReadIntPtrFromMemory(m_acc_address, out m_acc_address);
70 | LogHelper.LogToFile($"Playing Accuracy Base Address (1):0x{(int)m_acc_address:X8}");
71 | }
72 |
73 | //Find Time Address
74 | m_time_address = SigScan.FindPattern(StringToByte(s_time_pattern), s_time_mask, 5);
75 | LogHelper.LogToFile($"Time Base Address (0):0x{(int)m_time_address:X8}");
76 |
77 | m_time_address_success = TryReadIntPtrFromMemory(m_time_address, out m_time_address);
78 | LogHelper.LogToFile($"Time Base Address (1):0x{(int)m_time_address:X8}");
79 | }
80 | SigScan.ResetRegion();
81 |
82 | success = m_time_address_success && m_accuracy_address_success;
83 | if(Setting.EnableModsChangedAtListening)
84 | success = success && m_mods_address_success;
85 |
86 | if (m_acc_address == IntPtr.Zero || m_time_address == IntPtr.Zero)
87 | success = false;
88 |
89 | return success;
90 | }
91 |
92 | public double GetCurrentAccuracy()
93 | {
94 | TryReadIntPtrFromMemory(RulesetBaseAddress + 0x48, out var tmp_ptr);
95 |
96 | TryReadDoubleFromMemory(tmp_ptr + 0x14, out double value);
97 | return value;
98 | }
99 |
100 | public double GetCurrentHP()
101 | {
102 | TryReadIntPtrFromMemory(RulesetBaseAddress + 0x40, out var tmp_ptr);
103 |
104 | TryReadDoubleFromMemory(tmp_ptr + 0x1c, out double value);
105 | return value;
106 | }
107 |
108 | #region Score Address
109 |
110 | public int GetCurrentScore()
111 | {
112 | TryReadIntPtrFromMemory(m_acc_address, out var tmpPtr);
113 |
114 | TryReadIntPtrFromMemory(tmpPtr + 0x44, out tmpPtr);
115 | TryReadIntFromMemory(tmpPtr + 0xf8, out var value);
116 | return value;
117 | }
118 |
119 | public int GetCurrentCombo()
120 | {
121 | TryReadShortFromMemory(ScoreBaseAddress + 0x94, out var value);
122 | return value;
123 | }
124 |
125 | public int GetMissCount()
126 | {
127 | TryReadShortFromMemory(ScoreBaseAddress + 0x92, out var value);
128 | return value;
129 | }
130 |
131 | public int Get300Count()
132 | {
133 | TryReadShortFromMemory(ScoreBaseAddress + 0x8a, out ushort value);
134 | return value;
135 | }
136 |
137 | public int Get100Count()
138 | {
139 | TryReadShortFromMemory(ScoreBaseAddress + 0x88, out var value);
140 | return value;
141 | }
142 |
143 | public int Get50Count()
144 | {
145 | TryReadShortFromMemory(ScoreBaseAddress + 0x8c, out var value);
146 | return value;
147 | }
148 |
149 | ///
150 | /// Osu Geki
151 | /// Mania 300g
152 | ///
153 | ///
154 | public int GetGekiCount()
155 | {
156 | TryReadShortFromMemory(ScoreBaseAddress + 0x8e, out var value);
157 | return value;
158 | }
159 |
160 | ///
161 | /// Osu Katu
162 | /// Mania 200
163 | ///
164 | public int GetKatuCount()
165 | {
166 | TryReadShortFromMemory(ScoreBaseAddress + 0x90, out var value);
167 | return value;
168 | }
169 |
170 | public ErrorStatisticsResult GetUnstableRate()
171 | {
172 | TryReadListFromMemory(ScoreBaseAddress + 0x38, out var list);
173 | if (list == null)
174 | return ErrorStatisticsResult.Empty;
175 | var result = Utils.GetErrorStatisticsArray(list);
176 | return new ErrorStatisticsResult
177 | {
178 | ErrorMin = result[0],
179 | ErrorMax = result[1],
180 | UnstableRate = result[4] * 10,
181 | };
182 | }
183 |
184 | public string GetCurrentPlayerName()
185 | {
186 | TryReadStringFromMemory(ScoreBaseAddress + 0x28, out var str);
187 | return str;
188 | }
189 |
190 | public ModsInfo GetCurrentModsAtListening()
191 | {
192 | if (TryReadIntFromMemory(m_mods_address, out var mods))
193 | {
194 | //if (TryReadIntFromMemory(mods_ptr + 0x8, out int salt) &&
195 | // TryReadIntFromMemory(mods_ptr + 0xc, out int mods))
196 | //{
197 | return new ModsInfo()
198 | {
199 | Mod = (ModsInfo.Mods)(mods)
200 | };
201 | //}
202 | }
203 | return ModsInfo.Empty;
204 | }
205 |
206 | public ModsInfo GetCurrentMods()
207 | {
208 | IntPtr mods_ptr;
209 |
210 | var tmp_ptr = ScoreBaseAddress;
211 | TryReadIntPtrFromMemory(tmp_ptr + 0x1c, out mods_ptr);
212 |
213 | if (TryReadIntFromMemory(mods_ptr + 0x8, out int salt) &&
214 | TryReadIntFromMemory(mods_ptr + 0xc, out int mods))
215 | {
216 | return new ModsInfo()
217 | {
218 | Mod = (ModsInfo.Mods)(mods ^ salt)
219 | };
220 | }
221 | return ModsInfo.Empty;
222 | }
223 | #endregion
224 |
225 | #region Time Address
226 | public int GetPlayingTime()
227 | {
228 | TryReadIntFromMemory(m_time_address, out int value);
229 | return value;
230 | }
231 | #endregion
232 |
233 |
234 |
235 | private IntPtr RulesetBaseAddress
236 | {
237 | get
238 | {
239 | TryReadIntPtrFromMemory(m_acc_address, out var tmp_ptr);
240 | return tmp_ptr;
241 | }
242 | }
243 |
244 | private IntPtr ScoreBaseAddress
245 | {
246 | get
247 | {
248 | TryReadIntPtrFromMemory(RulesetBaseAddress + 0x38, out var tmp_ptr);
249 | return tmp_ptr;
250 | }
251 | }
252 | }
253 | }
--------------------------------------------------------------------------------
/Memory/OsuStatusFinder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 |
4 | namespace OsuRTDataProvider.Memory
5 | {
6 | internal class OsuStatusFinder : OsuFinderBase
7 | {
8 | private static readonly string s_game_modes_pattern = "\x75\x07\x8B\x45\x90\xC6\x40\x2A\x00\x83\x3D\x0\x0\x0\x0\x0F";
9 |
10 | private static readonly string s_game_modes_mask = "xxxxxxxxxxx????x";
11 |
12 | private IntPtr m_game_modes_address;
13 | private bool success = false;
14 |
15 | public OsuStatusFinder(Process osu) : base(osu)
16 | {
17 | }
18 |
19 | public override bool TryInit()
20 | {
21 | SigScan.Reload();
22 | {
23 | //Find Game Modes
24 | m_game_modes_address = SigScan.FindPattern(StringToByte(s_game_modes_pattern), s_game_modes_mask, 11);
25 | LogHelper.LogToFile($"Game Status Address (0):0x{(int)m_game_modes_address:X8}");
26 |
27 | success = TryReadIntPtrFromMemory(m_game_modes_address, out m_game_modes_address);
28 | LogHelper.LogToFile($"Game Status Address (1):0x{(int)m_game_modes_address:X8}");
29 | }
30 | SigScan.ResetRegion();
31 |
32 | if (m_game_modes_address == IntPtr.Zero) success = false;
33 |
34 |
35 |
36 | return success;
37 | }
38 |
39 | public OsuInternalStatus GetCurrentOsuModes()
40 | {
41 | TryReadIntFromMemory(m_game_modes_address, out int value);
42 |
43 | return (OsuInternalStatus)value;
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/Memory/OsuVersionCompareInfoAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace OsuRTDataProvider.Memory
8 | {
9 | public class OsuVersionCompareInfoAttribute : Attribute
10 | {
11 | public OsuVersionCompareInfoAttribute(double osuVersion, BeatmapOffsetInfo.CompareCondition compareCondition)
12 | {
13 | OsuVersion = osuVersion;
14 | CompareCondition = compareCondition;
15 | }
16 |
17 | public double OsuVersion { get; }
18 | public BeatmapOffsetInfo.CompareCondition CompareCondition { get; }
19 |
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Memory/SigScan.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Dynamic;
5 | using System.Runtime.InteropServices;
6 |
7 | //Clone Form https://github.com/Deathmax/dxgw2/blob/master/Trainer_Rewrite/SigScan.cs
8 | //Modified by KedamaOvO
9 |
10 | //
11 | // sigScan C# Implementation - Written by atom0s [aka Wiccaan]
12 | // Class Version: 2.0.0
13 | //
14 | // [ CHANGE LOG ] -------------------------------------------------------------------------
15 | //
16 | // 2.0.0
17 | // - Updated to no longer require unsafe or fixed code.
18 | // - Removed unneeded methods and code.
19 | //
20 | // 1.0.0
21 | // - First version written and release.
22 | //
23 | // [ CREDITS ] ----------------------------------------------------------------------------
24 | //
25 | // sigScan is based on the FindPattern code written by
26 | // dom1n1k and Patrick at GameDeception.net
27 | //
28 | // Full credit to them for the purpose of this code. I, atom0s, simply
29 | // take credit for converting it to C#.
30 | //
31 | // [ USAGE ] ------------------------------------------------------------------------------
32 | //
33 | // Examples:
34 | //
35 | // SigScan _sigScan = new SigScan();
36 | // _sigScan.Process = someProc;
37 | // _sigScan.Address = new IntPtr(0x123456);
38 | // _sigScan.Size = 0x1000;
39 | // IntPtr pAddr = _sigScan.FindPattern(new byte[]{ 0xFF, 0xFF, 0xFF, 0xFF, 0x51, 0x55, 0xFC, 0x11 }, "xxxx?xx?", 12);
40 | //
41 | // SigScan _sigScan = new SigScan(someProc, new IntPtr(0x123456), 0x1000);
42 | // IntPtr pAddr = _sigScan.FindPattern(new byte[]{ 0xFF, 0xFF, 0xFF, 0xFF, 0x51, 0x55, 0xFC, 0x11 }, "xxxx?xx?", 12);
43 | //
44 | // ----------------------------------------------------------------------------------------
45 | namespace OsuRTDataProvider.Memory
46 | {
47 | internal class SigScan
48 | {
49 | private class MemoryRegion
50 | {
51 | public IntPtr AllocationBase { get; set; }
52 | public IntPtr BaseAddress { get; set; }
53 | public ulong RegionSize { get; set; }
54 | public byte[] DumpedRegion { get; set; }
55 | }
56 |
57 | ///
58 | /// m_vProcess
59 | ///
60 | /// The process we want to read the memory of.
61 | ///
62 | private Process m_vProcess;
63 |
64 | ///
65 | /// m_vAddress
66 | ///
67 | /// The starting address we want to begin reading at.
68 | ///
69 |
70 | ///
71 | /// m_vSize
72 | ///
73 | /// The number of bytes we wish to read from the process.
74 | ///
75 |
76 | #region "sigScan Class Construction"
77 |
78 | ///
79 | /// SigScan
80 | ///
81 | /// Overloaded class constructor that sets the class
82 | /// properties during construction.
83 | ///
84 | /// The process to dump the memory from.
85 | /// The started address to begin the dump.
86 | /// The size of the dump.
87 | public SigScan(Process proc)
88 | {
89 | this.m_vProcess = proc;
90 | //InitMemoryRegionInfo();
91 | }
92 |
93 | #endregion "sigScan Class Construction"
94 |
95 | public void Reload()
96 | {
97 | ResetRegion();
98 | InitMemoryRegionInfo();
99 | }
100 |
101 | private List m_memoryRegionList = new List();
102 | private const int PROCESS_QUERY_INFORMATION = 0x0400;
103 | private const int MEM_COMMIT = 0x00001000;
104 | private const int PAGE_READWRITE = 0x04;
105 | private const int PROCESS_WM_READ = 0x0010;
106 |
107 | private unsafe void InitMemoryRegionInfo()
108 | {
109 | SYSTEM_INFO sys_info;
110 | //Get the maximum and minimum addresses of the process.
111 | GetSystemInfo(out sys_info);
112 | IntPtr proc_min_address = sys_info.minimumApplicationAddress;
113 | IntPtr proc_max_address = sys_info.maximumApplicationAddress;
114 |
115 | byte* current_address = (byte*)proc_min_address.ToPointer();
116 | byte* lproc_max_address = (byte*)proc_max_address.ToPointer();
117 |
118 | IntPtr handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_WM_READ, false, m_vProcess.Id);
119 |
120 | if (handle == IntPtr.Zero)
121 | {
122 | Logger.Error($"Error Code:0x{Marshal.GetLastWin32Error():X8}");
123 | return;
124 | }
125 |
126 | MemoryBasicInformation mem_basic_info = new MemoryBasicInformation();
127 |
128 | while (current_address < lproc_max_address)
129 | {
130 | //Query the current memory page information.
131 | bool ok = QueryMemoryBasicInformation(handle, (IntPtr)current_address, ref mem_basic_info);
132 |
133 | if (!ok)
134 | {
135 | Logger.Error($"Error Code:0x{Marshal.GetLastWin32Error():X8}");
136 | break;
137 | }
138 |
139 | //Dump JIT code
140 | if ((mem_basic_info.Protect & AllocationProtect.PAGE_EXECUTE_READWRITE)>0 && mem_basic_info.State == MEM_COMMIT)
141 | {
142 | var region = new MemoryRegion()
143 | {
144 | BaseAddress = mem_basic_info.BaseAddress,
145 | AllocationBase = mem_basic_info.AllocationBase,
146 | RegionSize = mem_basic_info.RegionSize
147 | };
148 | m_memoryRegionList.Add(region);
149 | }
150 |
151 | //if (Setting.DebugMode)
152 | //{
153 | // LogHelper.EncryptLog($"BaseAddress: 0x{mem_basic_info.BaseAddress:X8} RegionSize: 0x{mem_basic_info.RegionSize:X8} AllocationBase: 0x{mem_basic_info.AllocationBase:X8} Protect: {mem_basic_info.Protect} Commit: {mem_basic_info.State==MEM_COMMIT}(0x{mem_basic_info.State:X8})");
154 | //}
155 |
156 | current_address += mem_basic_info.RegionSize;
157 | }
158 |
159 | CloseHandle(handle);
160 |
161 | if(m_memoryRegionList.Count==0)
162 | {
163 | Logger.Error($"Error:List is Empty");
164 | }
165 | }
166 |
167 | #region "sigScan Class Private Methods"
168 |
169 | ///
170 | /// DumpMemory
171 | ///
172 | /// Internal memory dump function that uses the set class
173 | /// properties to dump a memory region.
174 | ///
175 | /// Boolean based on RPM results and valid properties.
176 | private bool DumpMemory()
177 | {
178 | try
179 | {
180 | // Checks to ensure we have valid data.
181 | if (this.m_vProcess == null)
182 | return false;
183 | if (this.m_vProcess.HasExited == true)
184 | return false;
185 |
186 | // Create the region space to dump into.
187 | foreach (var region in m_memoryRegionList)
188 | {
189 | if (region.DumpedRegion != null) continue;
190 |
191 | region.DumpedRegion = new byte[region.RegionSize];
192 |
193 | bool bReturn = false;
194 | int nBytesRead = 0;
195 |
196 | // Dump the memory.
197 | bReturn = ReadProcessMemory(
198 | this.m_vProcess.Handle, region.BaseAddress, region.DumpedRegion, (uint)region.RegionSize, out nBytesRead
199 | );
200 |
201 | // Validation checks.
202 | if (bReturn == false || nBytesRead != (int)region.RegionSize)
203 | return false;
204 | }
205 | return true;
206 | }
207 | catch (Exception ex)
208 | {
209 | Logger.Error($":{ex.Message}");
210 | return false;
211 | }
212 | }
213 |
214 | ///
215 | /// MaskCheck
216 | ///
217 | /// Compares the current pattern byte to the current memory dump
218 | /// byte to check for a match. Uses wildcards to skip bytes that
219 | /// are deemed unneeded in the compares.
220 | ///
221 | /// Offset in the dump to start at.
222 | /// Pattern to scan for.
223 | /// Mask to compare against.
224 | /// Boolean depending on if the pattern was found.
225 | private bool MaskCheck(MemoryRegion region, int nOffset, byte[] btPattern, string strMask)
226 | {
227 | // Loop the pattern and compare to the mask and dump.
228 | for (int x = 0; x < btPattern.Length && (ulong)(nOffset + x) < region.RegionSize; x++)
229 | {
230 | // If the mask char is a wildcard, just continue.
231 | if (strMask[x] == '?')
232 | continue;
233 |
234 | // If the mask char is not a wildcard, ensure a match is made in the pattern.
235 | if ((strMask[x] == 'x') && (btPattern[x] != region.DumpedRegion[nOffset + x]))
236 | return false;
237 | }
238 |
239 | // The loop was successful so we found the pattern.
240 | return true;
241 | }
242 |
243 | #endregion "sigScan Class Private Methods"
244 |
245 | #region "sigScan Class Public Methods"
246 |
247 | ///
248 | /// FindPattern
249 | ///
250 | /// Attempts to locate the given pattern inside the dumped memory region
251 | /// compared against the given mask. If the pattern is found, the offset
252 | /// is added to the located address and returned to the user.
253 | ///
254 | /// Byte pattern to look for in the dumped region.
255 | /// The mask string to compare against.
256 | /// The offset added to the result address.
257 | /// IntPtr - zero if not found, address if found.
258 | public IntPtr FindPattern(byte[] btPattern, string strMask, int nOffset)
259 | {
260 | try
261 | {
262 | // Dump the memory region if we have not dumped it yet.
263 |
264 | if (!this.DumpMemory())
265 | return IntPtr.Zero;
266 |
267 | // Ensure the mask and pattern lengths match.
268 | if (strMask.Length != btPattern.Length)
269 | return IntPtr.Zero;
270 |
271 | // Loop the region and look for the pattern.
272 | foreach (var region in m_memoryRegionList)
273 | {
274 | for (int x = 0; x < region.DumpedRegion.Length; x++)
275 | {
276 | if (this.MaskCheck(region, x, btPattern, strMask))
277 | {
278 | // The pattern was found, return it.
279 | return new IntPtr((int)region.BaseAddress + (x + nOffset));
280 | }
281 | }
282 | }
283 | // Pattern was not found.
284 | return IntPtr.Zero;
285 | }
286 | catch (Exception ex)
287 | {
288 | Logger.Error($":{ex.Message}");
289 | return IntPtr.Zero;
290 | }
291 | }
292 |
293 | ///
294 | /// ResetRegion
295 | ///
296 | /// Resets the memory dump array to nothing to allow
297 | /// the class to redump the memory.
298 | ///
299 | public void ResetRegion()
300 | {
301 | m_memoryRegionList.Clear();
302 | }
303 |
304 | #endregion "sigScan Class Public Methods"
305 |
306 | #region "sigScan Class Properties"
307 |
308 | private bool isX64()
309 | {
310 | return IntPtr.Size == 8;
311 | }
312 |
313 | private bool QueryMemoryBasicInformation(IntPtr handle, IntPtr current_address, ref MemoryBasicInformation memoryBasicInformation)
314 | {
315 | if (isX64())
316 | {
317 | MEMORY_BASIC_INFORMATION_X64 mem_basic_info = new MEMORY_BASIC_INFORMATION_X64();
318 | int mem_info_size = Marshal.SizeOf();
319 | int size = VirtualQueryEx_X64(handle, current_address, out mem_basic_info, (uint)mem_info_size);
320 |
321 | if (size != mem_info_size)
322 | {
323 | Logger.Error($"(X64)Error Code:0x{Marshal.GetLastWin32Error():X8}");
324 | return false;
325 | }
326 |
327 | memoryBasicInformation.RegionSize = mem_basic_info.RegionSize;
328 | memoryBasicInformation.BaseAddress = mem_basic_info.BaseAddress;
329 | memoryBasicInformation.AllocationProtect = mem_basic_info.AllocationProtect;
330 | memoryBasicInformation.AllocationBase = mem_basic_info.AllocationBase;
331 | memoryBasicInformation.Type = mem_basic_info.Type;
332 | memoryBasicInformation.State = mem_basic_info.State;
333 | memoryBasicInformation.Protect = mem_basic_info.Protect;
334 | return true;
335 | }
336 | else
337 | {
338 | MEMORY_BASIC_INFORMATION_X86 mem_basic_info = new MEMORY_BASIC_INFORMATION_X86();
339 | int mem_info_size = Marshal.SizeOf();
340 | int size = VirtualQueryEx_X86(handle, current_address, out mem_basic_info, (uint)mem_info_size);
341 |
342 | if (size != mem_info_size)
343 | {
344 | Logger.Error($"(X86)Error Code:0x{Marshal.GetLastWin32Error():X8}");
345 | return false;
346 | }
347 |
348 | memoryBasicInformation.RegionSize = mem_basic_info.RegionSize;
349 | memoryBasicInformation.BaseAddress = mem_basic_info.BaseAddress;
350 | memoryBasicInformation.AllocationProtect = mem_basic_info.AllocationProtect;
351 | memoryBasicInformation.AllocationBase = mem_basic_info.AllocationBase;
352 | memoryBasicInformation.Type = mem_basic_info.Type;
353 | memoryBasicInformation.State = mem_basic_info.State;
354 | memoryBasicInformation.Protect = mem_basic_info.Protect;
355 | return true;
356 | }
357 | }
358 |
359 | public Process Process
360 | {
361 | get { return this.m_vProcess; }
362 | set { this.m_vProcess = value; }
363 | }
364 |
365 | #endregion "sigScan Class Properties"
366 |
367 | #region PInvoke
368 |
369 | private struct MemoryBasicInformation
370 | {
371 | public IntPtr BaseAddress;
372 | public IntPtr AllocationBase;
373 | public uint AllocationProtect;
374 | public ulong RegionSize;
375 | public uint State;
376 | public AllocationProtect Protect;
377 | public uint Type;
378 | }
379 |
380 |
381 | [StructLayout(LayoutKind.Sequential)]
382 | private struct MEMORY_BASIC_INFORMATION_X86
383 | {
384 | public IntPtr BaseAddress;
385 | public IntPtr AllocationBase;
386 | public uint AllocationProtect;
387 | public uint RegionSize;
388 | public uint State;
389 | public AllocationProtect Protect;
390 | public uint Type;
391 | }
392 |
393 | [StructLayout(LayoutKind.Sequential, Pack = 16)]
394 | private struct MEMORY_BASIC_INFORMATION_X64
395 | {
396 | public IntPtr BaseAddress;
397 | public IntPtr AllocationBase;
398 | public uint AllocationProtect;
399 | public uint __alignment1;
400 | public ulong RegionSize;
401 | public uint State;
402 | public AllocationProtect Protect;
403 | public uint Type;
404 | public uint __alignment2;
405 | }
406 | public enum AllocationProtect : uint
407 | {
408 | PAGE_EXECUTE = 0x00000010,
409 | PAGE_EXECUTE_READ = 0x00000020,
410 | PAGE_EXECUTE_READWRITE = 0x00000040,
411 | PAGE_EXECUTE_WRITECOPY = 0x00000080,
412 | PAGE_NOACCESS = 0x00000001,
413 | PAGE_READONLY = 0x00000002,
414 | PAGE_READWRITE = 0x00000004,
415 | PAGE_WRITECOPY = 0x00000008,
416 | PAGE_GUARD = 0x00000100,
417 | PAGE_NOCACHE = 0x00000200,
418 | PAGE_WRITECOMBINE = 0x00000400
419 | }
420 |
421 | [StructLayout(LayoutKind.Sequential)]
422 | public struct SYSTEM_INFO
423 | {
424 | public ushort processorArchitecture;
425 | private ushort reserved;
426 | public uint pageSize;
427 | public IntPtr minimumApplicationAddress;
428 | public IntPtr maximumApplicationAddress;
429 | public IntPtr activeProcessorMask;
430 | public uint numberOfProcessors;
431 | public uint processorType;
432 | public uint allocationGranularity;
433 | public ushort processorLevel;
434 | public ushort processorRevision;
435 | }
436 |
437 | ///
438 | /// ReadProcessMemory
439 | ///
440 | /// API import definition for ReadProcessMemory.
441 | ///
442 | /// Handle to the process we want to read from.
443 | /// The base address to start reading from.
444 | /// The return buffer to write the read data to.
445 | /// The size of data we wish to read.
446 | /// The number of bytes successfully read.
447 | ///
448 | [DllImport("kernel32.dll", SetLastError = true)]
449 | public static extern bool ReadProcessMemory(
450 | IntPtr hProcess,
451 | IntPtr lpBaseAddress,
452 | [Out] byte[] lpBuffer,
453 | uint dwSize,
454 | out int lpNumberOfBytesRead
455 | );
456 |
457 | [DllImport("kernel32.dll", SetLastError = true, EntryPoint = "VirtualQueryEx")]
458 | private static extern int VirtualQueryEx_X86(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION_X86 lpBuffer, uint dwLength);
459 |
460 | [DllImport("kernel32.dll", SetLastError = true, EntryPoint = "VirtualQueryEx")]
461 | private static extern int VirtualQueryEx_X64(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION_X64 lpBuffer, uint dwLength);
462 |
463 | [DllImport("kernel32.dll", SetLastError = true)]
464 | private static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);
465 |
466 | [DllImport("kernel32.dll", SetLastError = true)]
467 | public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
468 |
469 | [DllImport("kernel32.dll", SetLastError = true)]
470 | public static extern bool CloseHandle(IntPtr hObject);
471 |
472 | #endregion PInvoke
473 | }
474 | }
--------------------------------------------------------------------------------
/Mods/ModsInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace OsuRTDataProvider.Mods
6 | {
7 | public struct ModsInfo
8 | {
9 | [Flags]
10 | public enum Mods : uint
11 | {
12 | NoMod = 0u,
13 | None = 0u,
14 | NoFail = 1u << 0,
15 | Easy = 1u << 1,
16 | TouchDevice = 1u << 2,
17 | Hidden = 1u << 3,
18 | HardRock = 1u << 4,
19 | SuddenDeath = 1u << 5,
20 | DoubleTime = 1u << 6,
21 | Relax = 1u << 7,
22 | HalfTime = 1u << 8,
23 | Nightcore = 1u << 9,
24 | Flashlight = 1u << 10,
25 | Autoplay = 1u << 11,
26 | SpunOut = 1u << 12,
27 | AutoPilot = 1u << 13,
28 | Perfect = 1u << 14,
29 | Key4 = 1u << 15,
30 | Key5 = 1u << 16,
31 | Key6 = 1u << 17,
32 | Key7 = 1u << 18,
33 | Key8 = 1u << 19,
34 | FadeIn = 1u << 20,
35 | Random = 1u << 21,
36 | Cinema = 1u << 22,
37 | Target = 1u << 23,
38 | Key9 = 1u << 24,
39 | KeyCoop = 1u << 25,
40 | Key1 = 1u << 26,
41 | Key3 = 1u << 27,
42 | Key2 = 1u << 28,
43 | ScoreV2 = 1u << 29,
44 | Mirror = 1u << 30,
45 | Unknown = 0xFFFFFFFFu
46 | }
47 |
48 | private static Dictionary s_name_to_sname = new Dictionary
49 | {
50 | ["NoMod"] = "",
51 | ["None"] = "",
52 | ["NoFail"] = "NF",
53 | ["Easy"] = "EZ",
54 | ["TouchDevice"] = "TD",
55 | ["Hidden"] = "HD",
56 | ["HardRock"] = "HR",
57 | ["SuddenDeath"] = "SD",
58 | ["DoubleTime"] = "DT",
59 | ["Relax"] = "RL",
60 | ["HalfTime"] = "HT",
61 | ["Nightcore"] = "NC",
62 | ["Flashlight"] = "FL",
63 | ["Autoplay"] = "Auto",
64 | ["SpunOut"] = "SO",
65 | ["AutoPilot"] = "AP",
66 | ["Perfect"] = "PF",
67 | ["Key1"] = "1K",
68 | ["Key2"] = "2K",
69 | ["Key3"] = "3K",
70 | ["Key4"] = "4K",
71 | ["Key5"] = "5K",
72 | ["Key6"] = "6K",
73 | ["Key7"] = "7K",
74 | ["Key8"] = "8K",
75 | ["Key9"] = "9K",
76 | ["KeyCoop"] = "Co-op",
77 | ["FadeIn"] = "FI",
78 | ["Random"] = "RD",
79 | ["Cinema"] = "CN",
80 | ["Target"] = "TP",
81 | ["ScoreV2"] = "V2",
82 | ["Mirror"] = "MR",
83 | ["Unknown"] = "Unknown"
84 | };
85 |
86 | static public ModsInfo Empty
87 | {
88 | get
89 | {
90 | ModsInfo m;
91 | m.m_mod = Mods.Unknown;
92 | m.m_time_rate = 1.0;
93 | return m;
94 | }
95 | }
96 |
97 | private Mods m_mod;
98 |
99 | ///
100 | /// Get Mods
101 | ///
102 | public Mods Mod
103 | {
104 | set
105 | {
106 | if ((value & Mods.Nightcore) == Mods.Nightcore)
107 | value &= ~Mods.DoubleTime;
108 | else if ((value & Mods.Cinema) == Mods.Cinema)
109 | value &= ~Mods.Autoplay;
110 | else if ((value & Mods.Perfect) == Mods.Perfect)
111 | value &= ~Mods.SuddenDeath;
112 |
113 | if ((value & Mods.Nightcore) > 0 || (value & Mods.DoubleTime) > 0)
114 | m_time_rate = 1.5;
115 | else if ((value & Mods.HalfTime) > 0)
116 | m_time_rate = 0.75;
117 | else
118 | m_time_rate = 1.0;
119 |
120 | m_mod = value;
121 | }
122 | get => m_mod;
123 | }
124 |
125 | private double m_time_rate;
126 | public double TimeRate => m_time_rate;
127 |
128 | private static readonly List s_invaild_mods_mask = new List {
129 | Mods.Easy|Mods.HardRock,
130 | Mods.HalfTime|Mods.DoubleTime,
131 | Mods.SuddenDeath|Mods.AutoPilot,
132 | Mods.AutoPilot|Mods.Relax,
133 | Mods.NoFail|Mods.SuddenDeath,
134 | Mods.NoFail|Mods.Perfect,
135 | Mods.NoFail|Mods.Relax,
136 | Mods.NoFail|Mods.AutoPilot,
137 | Mods.Relax|Mods.SuddenDeath,
138 | Mods.SpunOut|Mods.AutoPilot,
139 | Mods.Key1 | Mods.Key3,
140 | Mods.Key1 | Mods.Key4,
141 | Mods.Key1 | Mods.Key5,
142 | Mods.Key1 | Mods.Key6,
143 | Mods.Key1 | Mods.Key7,
144 | Mods.Key1 | Mods.Key8,
145 | Mods.Key1 | Mods.Key9,
146 |
147 | Mods.Key2 | Mods.Key3,
148 | Mods.Key2 | Mods.Key4,
149 | Mods.Key2 | Mods.Key5,
150 | Mods.Key2 | Mods.Key6,
151 | Mods.Key2 | Mods.Key7,
152 | Mods.Key2 | Mods.Key8,
153 | Mods.Key2 | Mods.Key9,
154 |
155 | Mods.Key3 | Mods.Key4,
156 | Mods.Key3 | Mods.Key5,
157 | Mods.Key3 | Mods.Key6,
158 | Mods.Key3 | Mods.Key7,
159 | Mods.Key3 | Mods.Key8,
160 | Mods.Key3 | Mods.Key9,
161 |
162 | Mods.Key4 | Mods.Key5,
163 | Mods.Key4 | Mods.Key6,
164 | Mods.Key4 | Mods.Key7,
165 | Mods.Key4 | Mods.Key8,
166 | Mods.Key4 | Mods.Key9,
167 |
168 | Mods.Key5 | Mods.Key6,
169 | Mods.Key5 | Mods.Key7,
170 | Mods.Key5 | Mods.Key8,
171 | Mods.Key5 | Mods.Key9,
172 |
173 | Mods.Key6 | Mods.Key7,
174 | Mods.Key6 | Mods.Key8,
175 | Mods.Key6 | Mods.Key9,
176 |
177 | Mods.Key7 | Mods.Key8,
178 | Mods.Key7 | Mods.Key9,
179 |
180 | Mods.Key8 | Mods.Key9,
181 | };
182 |
183 | public static bool VaildMods(ModsInfo mods)
184 | {
185 | foreach(var mask in s_invaild_mods_mask)
186 | {
187 | if ((mask & mods.Mod) == mask)
188 | return false;
189 | }
190 | return true;
191 | }
192 |
193 | ///
194 | /// Get Mods Name
195 | ///
196 | public string Name
197 | {
198 | get
199 | {
200 | var mods_str = Mod.ToString().Replace(" ", string.Empty);
201 | mods_str = mods_str.Replace("None", "NoMod");
202 | if (mods_str.Contains("NoMod,"))
203 | {
204 | mods_str = mods_str.Replace("NoMod,", "");
205 | }
206 | return mods_str;
207 | }
208 | }
209 |
210 | ///
211 | /// Get Short Mods Name
212 | ///
213 | public string ShortName
214 | {
215 | get
216 | {
217 | string mods_str = Name;
218 | string[] mods_arr = mods_str.Replace(" ", string.Empty).Split(',');
219 | StringBuilder b = new StringBuilder(128);
220 |
221 | foreach (var str in mods_arr)
222 | {
223 | if (s_name_to_sname.ContainsKey(str))
224 | b.Append(s_name_to_sname[str]);
225 | else return "Error";
226 | b.Append(',');
227 | }
228 | return b.Remove(b.Length - 1, 1).ToString().Trim(',');
229 | }
230 | }
231 |
232 | public bool HasMod(Mods mods)
233 | {
234 | return (m_mod & mods) > 0;
235 | }
236 |
237 | public override string ToString()
238 | {
239 | return ShortName;
240 | }
241 |
242 | public override bool Equals(object obj)
243 | {
244 | if (!(obj is ModsInfo))
245 | {
246 | return false;
247 | }
248 |
249 | var info = (ModsInfo)obj;
250 | return m_mod == info.m_mod;
251 | }
252 |
253 | public override int GetHashCode()
254 | {
255 | var hashCode = -801518429;
256 | hashCode = hashCode * -1521134295 + base.GetHashCode();
257 | hashCode = hashCode * -1521134295 + m_mod.GetHashCode();
258 | return hashCode;
259 | }
260 |
261 | public static bool operator !=(ModsInfo a, ModsInfo b)
262 | {
263 | return a.Mod != b.Mod;
264 | }
265 |
266 | public static bool operator ==(ModsInfo a, ModsInfo b)
267 | {
268 | return a.Mod == b.Mod;
269 | }
270 |
271 | public static bool operator !=(ModsInfo a, Mods b)
272 | {
273 | return a.Mod != b;
274 | }
275 |
276 | public static bool operator ==(ModsInfo a, Mods b)
277 | {
278 | return a.Mod == b;
279 | }
280 | }
281 | }
--------------------------------------------------------------------------------
/OsuRTDataProvider.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {D518DC59-CC9A-4886-839F-7691D5EFAE56}
8 | Library
9 | Properties
10 | OsuRTDataProvider
11 | OsuRTDataProvider
12 | v4.5.2
13 | 512
14 |
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\Plugins\
21 | TRACE;DEBUG
22 | prompt
23 | 4
24 | AnyCPU
25 | false
26 | true
27 | false
28 |
29 |
30 | none
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 | AnyCPU
37 | true
38 |
39 |
40 | true
41 | bin\x86\Debug\
42 | TRACE;DEBUG;X86
43 | full
44 | x86
45 | prompt
46 | MinimumRecommendedRules.ruleset
47 |
48 |
49 | bin\x86\Release\
50 | TRACE
51 | true
52 | pdbonly
53 | x86
54 | prompt
55 | MinimumRecommendedRules.ruleset
56 |
57 |
58 | true
59 | bin\x64\Debug\
60 | TRACE;DEBUG;X64
61 | full
62 | x64
63 | prompt
64 | MinimumRecommendedRules.ruleset
65 |
66 |
67 | bin\x64\Release\
68 | TRACE
69 | x64
70 | prompt
71 | MinimumRecommendedRules.ruleset
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 |
110 |
111 |
112 |
113 |
114 |
115 | {FBD514C2-2830-479E-B050-D1C383028456}
116 | Sync
117 | False
118 |
119 |
120 |
121 |
122 | copy /y $(TargetPath) $(SolutionDir)\Sync\bin\$(ConfigurationName)\Plugins
123 |
124 |
--------------------------------------------------------------------------------
/OsuRTDataProviderPlugin.cs:
--------------------------------------------------------------------------------
1 | using OsuRTDataProvider.BeatmapInfo;
2 | using OsuRTDataProvider.Helper;
3 | using OsuRTDataProvider.Listen;
4 | using OsuRTDataProvider.Mods;
5 | using Sync;
6 | using Sync.Plugins;
7 | using Sync.Tools;
8 | using System;
9 | using System.Collections.Generic;
10 | using System.IO;
11 | using System.Linq;
12 | using System.Threading.Tasks;
13 | using static OsuRTDataProvider.Listen.OsuListenerManager;
14 |
15 | namespace OsuRTDataProvider
16 | {
17 | [SyncPluginID("7216787b-507b-4eef-96fb-e993722acf2e", VERSION)]
18 | public class OsuRTDataProviderPlugin : Plugin
19 | {
20 | public const string PLUGIN_NAME = "OsuRTDataProvider";
21 | public const string PLUGIN_AUTHOR = "KedamaOvO";
22 | public const string VERSION = "1.7.0";
23 |
24 | private PluginConfigurationManager m_config_manager;
25 |
26 | private OsuListenerManager[] m_listener_managers = new OsuListenerManager[16];
27 | private int m_listener_managers_count = 0;
28 |
29 | public bool ModsChangedAtListening => Setting.EnableModsChangedAtListening;
30 |
31 | ///
32 | /// If EnableTourneyMode = false in config.ini, return 0.
33 | /// If EnableTourneyMode = true in config.ini, return TeamSize * 2.
34 | ///
35 | public int TourneyListenerManagersCount { get => Setting.EnableTourneyMode ? m_listener_managers_count : 0; }
36 |
37 | ///
38 | /// return a ListenerManager.
39 | ///
40 | public OsuListenerManager ListenerManager { get => m_listener_managers[0]; }
41 |
42 | ///
43 | /// If EnableTourneyMode = false in config.ini, return null.
44 | /// If EnableTourneyMode = true in config.ini, return all ListenerManagers.
45 | ///
46 | public OsuListenerManager[] TourneyListenerManagers { get => Setting.EnableTourneyMode ? m_listener_managers : null; }
47 |
48 | public OsuRTDataProviderPlugin() : base(PLUGIN_NAME, PLUGIN_AUTHOR)
49 | {
50 | I18n.Instance.ApplyLanguage(new DefaultLanguage());
51 | m_config_manager = new PluginConfigurationManager(this);
52 | m_config_manager.AddItem(new SettingIni());
53 |
54 | if (Setting.DebugMode)
55 | {
56 | base.EventBus.BindEvent((e) =>
57 | HardwareInformationHelper.PrintHardwareInformation());
58 | }
59 |
60 | base.EventBus.BindEvent(InitCommand);
61 | base.EventBus.BindEvent((e) => {
62 | Logger.Info(string.Format(DefaultLanguage.LANG_TOURNEY_HINT, Setting.EnableTourneyMode));
63 | Task.Run(()=>UpdateChecker.CheckUpdate());
64 | });
65 |
66 | try
67 | {
68 | File.Delete("OsuRTDataProviderRelease.dll");
69 | }
70 | catch (Exception) { }
71 | }
72 |
73 | public override void OnEnable()
74 | {
75 | Sync.Tools.IO.CurrentIO.WriteColor(PLUGIN_NAME + " By " + PLUGIN_AUTHOR, ConsoleColor.DarkCyan);
76 |
77 | if (Setting.EnableTourneyMode)
78 | {
79 | m_listener_managers_count = Setting.TeamSize * 2;
80 | for (int i = 0; i < m_listener_managers_count; i++)
81 | InitTourneyManager(i);
82 | }
83 | else
84 | {
85 | InitManager();
86 | }
87 |
88 | DebugOutput(Setting.DebugMode, true);
89 | }
90 |
91 | private void InitTourneyManager(int id)
92 | {
93 | m_listener_managers[id] = new OsuListenerManager(true, id);
94 | m_listener_managers[id].Start();
95 | }
96 |
97 | private void InitManager()
98 | {
99 | m_listener_managers[0] = new OsuListenerManager();
100 | m_listener_managers[0].Start();
101 | }
102 |
103 | private void InitCommand(PluginEvents.InitCommandEvent @e)
104 | {
105 | @e.Commands.Dispatch.bind("ortdp", (args) =>
106 | {
107 | if(args.Count >= 1)
108 | {
109 | if(args[0] == "releases")
110 | {
111 | System.Diagnostics.Process.Start("https://github.com/OsuSync/OsuRTDataProvider/releases");
112 | }
113 | }
114 |
115 | if (args.Count >= 2)
116 | {
117 | if (args[0] == "debug")
118 | {
119 | if (bool.TryParse(args[1], out bool f))
120 | {
121 | DebugOutput(f);
122 | Logger.Info($"Debug mode = {Setting.DebugMode}");
123 | }
124 | }
125 | return true;
126 | }
127 | return false;
128 | }, "OsuRTDataProvider control panel");
129 | }
130 |
131 | private void DebugOutput(bool enable, bool first = false)
132 | {
133 | if (!first && Setting.DebugMode == enable) return;
134 |
135 | if (Setting.EnableTourneyMode)
136 | {
137 | for (int i = 0; i < TourneyListenerManagersCount; i++)
138 | {
139 | int id = i;
140 | void OnTourneyStatusChanged(OsuStatus l, OsuStatus c)=>
141 | Logger.Info($"[{id}]Current Game Status:{c}");
142 | void OnTourneyModsChanged(ModsInfo m)=>
143 | Logger.Info($"[{id}]Mods:{m}(0x{(uint)m.Mod:X8})");
144 | void OnTourneyModeChanged(OsuPlayMode last, OsuPlayMode mode)=>
145 | Logger.Info($"[{id}]Mode:{mode}");
146 | void OnTourneyBeatmapChanged(Beatmap map) =>
147 | Logger.Info($"[{id}]Beatmap: {map.Artist} - {map.Title}[{map.Difficulty}]({map.BeatmapSetID},{map.BeatmapID},{map.FilenameFull})");
148 | void OnTourneyPlayerChanged(string playername) =>
149 | Logger.Info($"[{id}]Current Player: {playername}");
150 | /*
151 | void OnTourneyHitEventsChanged(PlayType playType, List hitEvents)
152 | {
153 | string log = $"[{id}]Play Type: {playType}, end time: {(hitEvents.Count == 0 ? -1 : hitEvents[hitEvents.Count - 1].TimeStamp)}, count: {hitEvents.Count}";
154 | log += $" LastKeysDown:{hitEvents.LastOrDefault()?.KeysDown}";
155 | Logger.Info(log);
156 | };
157 | */
158 |
159 | if (enable)
160 | {
161 | m_listener_managers[i].OnStatusChanged += OnTourneyStatusChanged;
162 | m_listener_managers[i].OnModsChanged += OnTourneyModsChanged;
163 | m_listener_managers[i].OnPlayModeChanged += OnTourneyModeChanged;
164 | m_listener_managers[i].OnBeatmapChanged += OnTourneyBeatmapChanged;
165 | m_listener_managers[i].OnPlayerChanged += OnTourneyPlayerChanged;
166 | //m_listener_managers[i].OnHitEventsChanged += OnTourneyHitEventsChanged;
167 | }
168 | else
169 | {
170 | m_listener_managers[i].OnStatusChanged -= OnTourneyStatusChanged;
171 | m_listener_managers[i].OnModsChanged -= OnTourneyModsChanged;
172 | m_listener_managers[i].OnPlayModeChanged -= OnTourneyModeChanged;
173 | //m_listener_managers[i].OnHitEventsChanged -= OnTourneyHitEventsChanged;
174 | }
175 | }
176 | }
177 | else
178 | {
179 | void OnStatusChanged(OsuStatus l, OsuStatus c) =>
180 | Logger.Info($"Current Game Status:{c}");
181 | void OnModsChanged(ModsInfo m) =>
182 | Logger.Info($"Mods:{m}(0x{(uint)m.Mod:X8})");
183 | void OnModeChanged(OsuPlayMode last, OsuPlayMode mode) =>
184 | Logger.Info($"Mode:{mode}");
185 | void OnBeatmapChanged(Beatmap map) =>
186 | Logger.Info($"Beatmap: {map.Artist} - {map.Title}[{map.Difficulty}]({map.BeatmapSetID},{map.BeatmapID},{map.FilenameFull})");
187 | void OnPlayerChanged(string playername) =>
188 | Logger.Info($"Current Player: {playername}");
189 | /*
190 | void OnHitEventsChanged(PlayType playType, List hitEvents)
191 | {
192 | string log = $"Play Type: {playType}, end time: {(hitEvents.Count == 0 ? -1 : hitEvents[hitEvents.Count - 1].TimeStamp)}, count: {hitEvents.Count}";
193 | log += $" LastKeysDown:{hitEvents.LastOrDefault()?.KeysDown}";
194 | Logger.Info(log);
195 | };
196 | */
197 |
198 | if (enable)
199 | {
200 | m_listener_managers[0].OnStatusChanged += OnStatusChanged;
201 | m_listener_managers[0].OnModsChanged += OnModsChanged;
202 | m_listener_managers[0].OnPlayModeChanged += OnModeChanged;
203 | m_listener_managers[0].OnBeatmapChanged += OnBeatmapChanged;
204 | m_listener_managers[0].OnPlayerChanged += OnPlayerChanged;
205 | //m_listener_managers[0].OnHitEventsChanged += OnHitEventsChanged;
206 | }
207 | else
208 | {
209 | m_listener_managers[0].OnStatusChanged -= OnStatusChanged;
210 | m_listener_managers[0].OnModsChanged -= OnModsChanged;
211 | m_listener_managers[0].OnPlayModeChanged -= OnModeChanged;
212 | m_listener_managers[0].OnBeatmapChanged -= OnBeatmapChanged;
213 | m_listener_managers[0].OnPlayerChanged -= OnPlayerChanged;
214 | //m_listener_managers[0].OnHitEventsChanged -= OnHitEventsChanged;
215 | }
216 | }
217 |
218 | Setting.DebugMode = enable;
219 | }
220 |
221 | public override void OnDisable()
222 | {
223 | int size = Setting.EnableTourneyMode ? TourneyListenerManagersCount : 1;
224 | for (int i = 0; i < size; i++)
225 | {
226 | m_listener_managers[i].Stop();
227 | }
228 | }
229 |
230 | public override void OnExit()
231 | {
232 | OnDisable();
233 | }
234 | }
235 | }
--------------------------------------------------------------------------------
/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("OsuRTDataProvider")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("OsuRTDataProvider")]
13 | [assembly: AssemblyCopyright("Copyright © 2017")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // 将 ComVisible 设置为 false 会使此程序集中的类型
18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("d518dc59-cc9a-4886-839f-7691d5efae56")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | // 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
33 | //通过使用 "*",如下所示:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion(OsuRTDataProvider.OsuRTDataProviderPlugin.VERSION)]
36 | [assembly: AssemblyFileVersion(OsuRTDataProvider.OsuRTDataProviderPlugin.VERSION)]
37 |
--------------------------------------------------------------------------------
/README-CN.md:
--------------------------------------------------------------------------------
1 | [English](https://github.com/KedamaOvO/OsuRTDataProvider-Release/blob/master/README.md)
2 | # OsuRTDataProvider是什么?
3 | OsuRTDataProvider是一个[OsuSync](https://github.com/Deliay/osuSync)插件,可以实时读取osu!数据.
4 | 它支持OSU!和OSU!Tourney。
5 |
6 | OsuRTDataProvider能实时的从[OSU!](https://osu.ppy.sh)中获取以下内容(只支持正式版):
7 | * BeatmapID
8 | * 游戏状态
9 | * Accuracy
10 | * 生命值
11 | * 当前连击
12 | * 300数量
13 | * 100数量
14 | * 50数量
15 | * Katu数量
16 | * Geki数量
17 | * Miss数量
18 | * Mods
19 | * 播放时间
20 | * 分数
21 | * 游戏模式
22 |
23 | 不同的OSU!客户端版本所需要的ORTDP插件版本也可能不同.
24 |
25 | # 怎么使用(对于普通用户)
26 | 1. 下载 [OsuSync](https://github.com/Deliay/osuSync)。
27 | 2. 下载 [OsuRTDataProvider](https://github.com/KedamaOvO/OsuRTDataProvider-Release/releases)。
28 | 3. 复制OsuRTDataProvider 到 {OsuSync Path}/Plugins 目录下。
29 | 4. 运行 OsuSync。
30 | 5. 安装其他依赖于此插件的插件,比如[实时pp显示插件](https://github.com/OsuSync/RealTimePPDisplayer).
31 | 6. Enjoy!
32 |
33 |
34 | # Config.ini
35 | [OsuRTDataProvider.SettingIni]
36 |
37 | |Setting Name|Default Value|Description|
38 | | ----- | ----- | ----- |
39 | | ListenInterval | 100 | 监听数据的间隔(单位毫秒)。PS:如果太小可能会卡 |
40 | | EnableTourneyMode | False | 启用Tourney模式?(实验性) |
41 | | TeamSize | 1 | Tourney client的队伍大小|
42 | | ForceOsuSongsDirectory | | 强制在指定路径中搜索Beatmap文件夹|
43 | | GameMode | Auto |如果ModeFinder初始化失败. 请手动设置游戏模式(osu,mania,ctb,taiko,auto)|
44 | | DisableProcessNotFoundInformation | False | 隐藏"没有发现osu.exe进程"的消息提示|
45 | | EnableModsChangedAtListening | False | 尝试在非Play状态监听Mods变化|
46 |
47 | # API
48 | #### OsuRTDataProviderPlugin ***class***
49 | ##### Property
50 | ```csharp
51 | public OsuListenerManager ListenerManager;
52 |
53 | //如果config.ini中的EnableTourneyMode = false,该属性为null.
54 | public OsuListenerManager[] TourneyListenerManagers;
55 | public int TourneyListenerManagersCount;
56 | ```
57 |
58 | # 如何编译
59 | 1. clone此repo
60 | 2. clone[Sync](https://github.com/OsuSync/Sync)
61 | 3. 引用sync项目进ortdp项目
62 | 4. 编译.
63 |
64 | #### OsuListenerManager ***class***
65 | ##### Event
66 | ```csharp
67 | public delegate void OnBeatmapChangedEvt(Beatmap map);
68 | public delegate void OnHealthPointChangedEvt(double hp);
69 | public delegate void OnAccuracyChangedEvt(double acc);
70 | public delegate void OnComboChangedEvt(int combo);
71 | public delegate void OnModsChangedEvt(ModsInfo mods);
72 | public delegate void OnPlayingTimeChangedEvt(int ms);
73 | public delegate void OnHitCountChangedEvt(int hit);
74 | public delegate void OnStatusChangedEvt(OsuStatus last_status, OsuStatus status);
75 | public delegate void OnErrorStatisticsChangedEvt(ErrorStatisticsResult result);
76 | public delegate void OnPlayerChangedEvt(string player);
77 | public delegate void OnHitEventsChangedEvt(PlayType playType, List hitEvents);
78 |
79 | ///
80 | /// Playing和Linsten时可用.
81 | /// 如果Beatmap太老, map.ID = -1.
82 | ///
83 | public event OnBeatmapChangedEvt OnBeatmapChanged;
84 |
85 | ///
86 | /// Playing时可用.
87 | ///
88 | public event OnHealthPointChangedEvt OnHealthPointChanged;
89 |
90 | ///
91 | /// Playing时可用.
92 | ///
93 | public event OnAccuracyChangedEvt OnAccuracyChanged;
94 |
95 | ///
96 | /// Playing时可用.
97 | ///
98 | public event OnComboChangedEvt OnComboChanged;
99 |
100 | ///
101 | /// Playing时可用.
102 | /// Playing->Listen时 , mods = ModsInfo.Empty
103 | ///
104 | public event OnModsChangedEvt OnModsChanged;
105 |
106 | ///
107 | /// Playing和Linsten时可用.
108 | ///
109 | public event OnPlayingTimeChangedEvt OnPlayingTimeChanged;
110 |
111 | ///
112 | /// Playing时可用.
113 | ///
114 | public event OnHitCountChangedEvt On300HitChanged;
115 |
116 | ///
117 | /// Playing时可用.
118 | ///
119 | public event OnHitCountChangedEvt On100HitChanged;
120 |
121 | ///
122 | /// Playing时可用.
123 | ///
124 | public event OnHitCountChangedEvt On50HitChanged;
125 |
126 | ///
127 | /// Playing时可用.
128 | ///
129 | public event OnHitCountChangedEvt OnMissHitChanged;
130 |
131 | ///
132 | /// 任何时候可用.
133 | ///
134 | public event OnStatusChangedEvt OnStatusChanged;
135 |
136 | ///
137 | /// Playing可用
138 | /// 错误分析(UnstableRate and Error Hit).
139 | ///
140 | public event OnErrorStatisticsChangedEvt OnErrorStatisticsChanged;
141 |
142 | ///
143 | /// Playing可用
144 | /// 玩家名改变(发生在观看replay时)
145 | ///
146 | public event OnPlayerChangedEvt OnPlayerChanged;
147 |
148 | ///
149 | /// Playing可用
150 | /// 打击事件改变 (https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osr_(file_format))
151 | ///
152 | public event OnHitEventsChangedEvt OnHitEventsChanged;
153 | ```
154 |
155 | ##### OsuStatus ***enum***
156 | ```csharp
157 | public enum OsuStatus
158 | {
159 | NoFoundProcess,
160 | Unkonwn,
161 | Listening,
162 | Playing,
163 | Editing,
164 | Rank
165 | }
166 | ```
167 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [中文](https://github.com/KedamaOvO/OsuRTDataProvider-Release/blob/master/README-CN.md)
2 | # What is OsuRTDataProvider?
3 | OsuRTDataProvider is an [OsuSync](https://github.com/Deliay/osuSync) plugin.
4 | it's support OSU! and OSU!Tourney.
5 |
6 | OsuRTDataProvider can be obtained from [OSU!](https://osu.ppy.sh)(Stable Only):
7 | * Beatmap
8 | * Game Status
9 | * Accuracy
10 | * Health Point
11 | * Combo
12 | * 300 Count
13 | * 100 Count
14 | * 50 Count
15 | * Miss Count
16 | * Katu Count
17 | * Geki Count
18 | * Mods
19 | * Play Time
20 | * Score
21 | * Game Mode
22 |
23 | OSU! Clinet Version Requirements: **b20190816 After**
24 |
25 | # How to use?
26 | 1. Download [OsuSync](https://github.com/Deliay/osuSync)
27 | 2. Download [OsuRTDataProvider](https://github.com/KedamaOvO/OsuRTDataProvider-Release/releases).
28 | 3. Copy OsuRTDataProvider to {OsuSync Path}/Plugins.
29 | 4. Run OsuSync.
30 |
31 | # Config.ini
32 | [OsuRTDataProvider.SettingIni]
33 |
34 | |Setting Name|Default Value|Description|
35 | | ----- | ----- | ----- |
36 | | ListenInterval | 100 | Listen data interval(ms). PS: If it is too small may lag |
37 | | EnableTourneyMode | False | Is tourney client?(Experimental)|
38 | | TeamSize | 1 | Tourney client team size|
39 | | ForceOsuSongsDirectory | | Force search for Beatmap from this path|
40 | | GameMode | Auto |If ModeFinder initialization fails. Please manually select the current Mode.|
41 | | DisableProcessNotFoundInformation | False | Hide "Not found osu!.exe process"|
42 | | EnableModsChangedAtListening | False | Try to monitor Mods changes in non-Play status|
43 |
44 | # API
45 | #### OsuRTDataProviderPlugin ***class***
46 | ##### Property
47 | ```csharp
48 | public OsuListenerManager ListenerManager;
49 |
50 | //If EnableTourneyMode = false in config.ini, return null.
51 | public OsuListenerManager[] TourneyListenerManagers;
52 | public int TourneyListenerManagersCount;
53 | ```
54 | #### OsuListenerManager ***class***
55 | ##### Event
56 | ```csharp
57 | public delegate void OnBeatmapChangedEvt(Beatmap map);
58 | public delegate void OnHealthPointChangedEvt(double hp);
59 | public delegate void OnAccuracyChangedEvt(double acc);
60 | public delegate void OnComboChangedEvt(int combo);
61 | public delegate void OnModsChangedEvt(ModsInfo mods);
62 | public delegate void OnPlayingTimeChangedEvt(int ms);
63 | public delegate void OnHitCountChangedEvt(int hit);
64 | public delegate void OnStatusChangedEvt(OsuStatus last_status, OsuStatus status);
65 | public delegate void OnErrorStatisticsChangedEvt(ErrorStatisticsResult result);
66 | public delegate void OnPlayerChangedEvt(string player);
67 | public delegate void OnHitEventsChangedEvt(PlayType playType, List hitEvents);
68 |
69 | ///
70 | /// Available at Playing and Linsten.
71 | /// If too old beatmap, map.ID = -1.
72 | ///
73 | public event OnBeatmapChangedEvt OnBeatmapChanged;
74 |
75 | ///
76 | /// Available at Playing.
77 | ///
78 | public event OnHealthPointChangedEvt OnHealthPointChanged;
79 |
80 | ///
81 | /// Available at Playing.
82 | ///
83 | public event OnAccuracyChangedEvt OnAccuracyChanged;
84 |
85 | ///
86 | /// Available at Playing.
87 | ///
88 | public event OnComboChangedEvt OnComboChanged;
89 |
90 | ///
91 | /// Available at Playing.
92 | /// if OsuStatus turns Listen , mods = ModsInfo.Empty
93 | ///
94 | public event OnModsChangedEvt OnModsChanged;
95 |
96 | ///
97 | /// Available at Playing and Listen.
98 | ///
99 | public event OnPlayingTimeChangedEvt OnPlayingTimeChanged;
100 |
101 | ///
102 | /// Available at Playing.
103 | ///
104 | public event OnHitCountChangedEvt On300HitChanged;
105 |
106 | ///
107 | /// Available at Playing.
108 | ///
109 | public event OnHitCountChangedEvt On100HitChanged;
110 |
111 | ///
112 | /// Available at Playing.
113 | ///
114 | public event OnHitCountChangedEvt On50HitChanged;
115 |
116 | ///
117 | /// Available at Playing.
118 | ///
119 | public event OnHitCountChangedEvt OnMissHitChanged;
120 |
121 | ///
122 | /// Available at Any.
123 | ///
124 | public event OnStatusChangedEvt OnStatusChanged;
125 |
126 | ///
127 | /// Get ErrorStatistics(UnstableRate and Error Hit).
128 | ///
129 | public event OnErrorStatisticsChangedEvt OnErrorStatisticsChanged;
130 |
131 | ///
132 | /// Get player name in playing.
133 | ///
134 | public event OnPlayerChangedEvt OnPlayerChanged;
135 |
136 | ///
137 | /// Get play type and hit events in playing. (https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osr_(file_format))
138 | ///
139 | public event OnHitEventsChangedEvt OnHitEventsChanged;
140 | ```
141 |
142 | ##### OsuStatus ***enum***
143 | ```csharp
144 | public enum OsuStatus
145 | {
146 | NoFoundProcess,
147 | Unkonwn,
148 | Listening,
149 | Playing,
150 | Editing,
151 | Rank
152 | }
153 | ```
154 | # How to use?(Dev)
155 | ```csharp
156 | var ortdp = getHoster().EnumPluings().FirstOrDefault(p => p.Name == "OsuRTDataProvider") as OsuRTDataProviderPlugin;
157 | ortdp.ListenerManager.OnBeatmapChanged+=(b)=>{/*...*/};
158 | ```
159 |
--------------------------------------------------------------------------------
/Setting.cs:
--------------------------------------------------------------------------------
1 | using Sync.Tools.ConfigurationAttribute;
2 | using Sync.Tools;
3 | using System;
4 | using System.Linq;
5 | using System.Text.RegularExpressions;
6 |
7 | namespace OsuRTDataProvider
8 | {
9 | public class SettingIni : IConfigurable
10 | {
11 | [Integer(MinValue = 1,MaxValue = 10000)]
12 | public ConfigurationElement ListenInterval
13 | {
14 | set => Setting.ListenInterval = int.Parse(value);
15 | get => Setting.ListenInterval.ToString();
16 | }
17 |
18 | [Bool(RequireRestart = true)]
19 | public ConfigurationElement EnableTourneyMode
20 | {
21 | get => Setting.EnableTourneyMode.ToString();
22 | set => Setting.EnableTourneyMode = bool.Parse(value);
23 | }
24 |
25 | [Integer(MinValue = 1,MaxValue = 8,RequireRestart = true)]
26 | public ConfigurationElement TeamSize {
27 | get => Setting.TeamSize.ToString();
28 | set
29 | {
30 | Setting.TeamSize = int.Parse(value);
31 | if (Setting.TeamSize < 1 || Setting.TeamSize > 8)
32 | {
33 | Setting.TeamSize = 0;
34 | Logger.Info("TeamSize must be between 1 and 8.");
35 | }
36 | }
37 | }
38 |
39 | [Bool(RequireRestart = true)]
40 | public ConfigurationElement DebugMode
41 | {
42 | get => Setting.DebugMode.ToString();
43 | set => Setting.DebugMode = bool.Parse(value);
44 | }
45 |
46 | [Path(IsDirectory = true, RequireRestart = true)]
47 | public ConfigurationElement ForceOsuSongsDirectory
48 | {
49 | get => Setting.ForceOsuSongsDirectory;
50 | set => Setting.ForceOsuSongsDirectory = value;
51 | }
52 | //Auto,Osu,Taiko,Mania,CTB
53 | [List(ValueList = new string[] { "Auto", "Osu", "Taiko", "CatchTheBeat", "Mania" })]
54 | public ConfigurationElement GameMode
55 | {
56 | get => Setting.GameMode;
57 | set => Setting.GameMode = value;
58 | }
59 |
60 | [Bool]
61 | public ConfigurationElement DisableProcessNotFoundInformation
62 | {
63 | set => Setting.DisableProcessNotFoundInformation = bool.Parse(value);
64 | get => Setting.DisableProcessNotFoundInformation.ToString();
65 | }
66 |
67 | [Bool]
68 | public ConfigurationElement EnableModsChangedAtListening
69 | {
70 | set => Setting.EnableModsChangedAtListening = bool.Parse(value);
71 | get => Setting.EnableModsChangedAtListening.ToString();
72 | }
73 |
74 | public void onConfigurationLoad()
75 | {
76 | }
77 |
78 | public void onConfigurationReload()
79 | {
80 | }
81 |
82 | public void onConfigurationSave()
83 | {
84 | }
85 | }
86 |
87 | internal static class Setting
88 | {
89 | public static bool DebugMode = false;
90 | public static int ListenInterval = 100;//ms
91 | public static bool EnableTourneyMode = false;
92 | public static int TeamSize = 1;
93 | public static string ForceOsuSongsDirectory = "";
94 | public static string GameMode = "Auto";
95 | public static bool DisableProcessNotFoundInformation = false;
96 | public static bool EnableModsChangedAtListening = false;
97 |
98 | #region NoSave
99 | public static string SongsPath = string.Empty;
100 | public static string OsuVersion = string.Empty;
101 | public static string Username = string.Empty;
102 | #endregion
103 |
104 | public static double CurrentOsuVersionValue => Utils.ConvertVersionStringToValue(OsuVersion);
105 | }
106 | }
--------------------------------------------------------------------------------
/UpdateChecker.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Runtime.InteropServices;
7 | using System.Text;
8 | using System.Text.RegularExpressions;
9 | using System.Threading.Tasks;
10 |
11 | namespace OsuRTDataProvider
12 | {
13 | static class UpdateChecker
14 | {
15 | private static readonly Regex NAME_REGEX = new Regex(@"""name"":\s*""v(\d+\.\d+\.\d+)""");
16 |
17 | private const string LATEST_RELEASE_URL = "https://api.github.com/repos/OsuSync/OsuRTDataProvider/releases/latest";
18 |
19 | public static void CheckUpdate()
20 | {
21 | try
22 | {
23 | string data = GetHttpData(LATEST_RELEASE_URL);
24 | var groups = NAME_REGEX.Match(data).Groups;
25 | string ortdp_version = groups[1].Value;
26 | bool has_update = CheckRtppUpdate(ortdp_version);
27 |
28 | if(has_update)
29 | {
30 | Logger.Warn(DefaultLanguage.CHECK_GOTO_RELEASE_PAGE_HINT);
31 | }
32 | }
33 | catch (Exception e)
34 | {
35 | Logger.Error(e.ToString());
36 | }
37 | }
38 |
39 | private static bool CheckRtppUpdate(string tag)
40 | {
41 | Version ver = Version.Parse(tag);
42 | Version selfVer = Version.Parse(OsuRTDataProviderPlugin.VERSION);
43 | if (ver > selfVer)
44 | {
45 | Logger.Warn(string.Format(DefaultLanguage.LANG_CHECK_ORTDP_UPDATE, ver));
46 | return true;
47 | }
48 | return false;
49 | }
50 |
51 | private static string GetHttpData(string url)
52 | {
53 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls | SecurityProtocolType.Ssl3;
54 | HttpWebRequest wReq = (HttpWebRequest)WebRequest.Create(url);
55 | wReq.UserAgent = "OsuSync";
56 | WebResponse wResp = wReq.GetResponse();
57 | Stream respStream = wResp.GetResponseStream();
58 |
59 | using (StreamReader reader = new StreamReader(respStream, Encoding.UTF8))
60 | {
61 | return reader.ReadToEnd();
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Utils.cs:
--------------------------------------------------------------------------------
1 | using Sync.Tools;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Globalization;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Text.RegularExpressions;
8 | using System.Threading.Tasks;
9 |
10 | namespace OsuRTDataProvider
11 | {
12 | public static class Utils
13 | {
14 | public static double ConvertVersionStringToValue(string osu_version_string)
15 | {
16 | if (double.TryParse(Regex.Match(osu_version_string, @"\d+(\.\d*)?").Value.ToString(),NumberStyles.Float, CultureInfo.InvariantCulture, out var ver))
17 | {
18 |
19 | return ver;
20 | }
21 |
22 | throw new Exception("Can't parse version: "+osu_version_string);
23 | }
24 |
25 | //https://gist.github.com/peppy/3a11cb58c856b6af7c1916422f668899
26 | public static List GetErrorStatisticsArray(List list)
27 | {
28 | if (list == null || list.Count == 0)
29 | return null;
30 | List result = new List(4);
31 | double total = 0, _total = 0, totalAll = 0;
32 | int count = 0, _count = 0;
33 | int max = 0, min = int.MaxValue;
34 | for (int i = 0; i < list.Count; i++)
35 | {
36 | if (list[i] > max)
37 | max = list[i];
38 | if (list[i] < min)
39 | min = list[i];
40 | totalAll += list[i];
41 | if (list[i] >= 0)
42 | {
43 | total += list[i];
44 | count++;
45 | }
46 | else
47 | {
48 | _total += list[i];
49 | _count++;
50 | }
51 | }
52 | double avarage = totalAll / list.Count;
53 | double variance = 0;
54 | for (int i = 0; i < list.Count; i++)
55 | {
56 | variance += Math.Pow(list[i] - avarage, 2);
57 | }
58 | variance = variance / list.Count;
59 | result.Add(_count == 0 ? 0 : _total / _count); //0
60 | result.Add(count == 0 ? 0 : total / count); //1
61 | result.Add(avarage); //2
62 | result.Add(variance); //3
63 | result.Add(Math.Sqrt(variance)); //4
64 | result.Add(max); //5
65 | result.Add(min); //6
66 | return result;
67 | }
68 | }
69 |
70 | public static class Logger
71 | {
72 | static Logger logger=new Logger();
73 |
74 | public static void Info(string message) => logger.LogInfomation(message);
75 |
76 | public static void Debug(string message)
77 | {
78 | if (Setting.DebugMode)
79 | logger.LogInfomation(message);
80 | }
81 |
82 | public static void Error(string message) => logger.LogError(message);
83 | public static void Warn(string message) => logger.LogWarning(message);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------