├── .github
├── FUNDING.yml
└── workflows
│ ├── build.yml
│ └── go.yml
├── .gitignore
├── .gitmodules
├── .vscode
└── launch.json
├── LICENSE
├── README.md
├── config
└── config.go
├── db
└── db.go
├── go.mod
├── go.sum
├── injctr
└── injctr.go
├── main.go
├── mem
├── debug.go
├── linux.go
├── mem.go
├── read.go
├── scan.go
└── windows.go
├── memory
├── functions.go
├── init.go
├── mods.go
├── read.go
├── tournament_linux.go
├── tournament_windows.go
└── values.go
├── out.ico
├── pp
├── editor.go
├── functions.go
├── iffc.go
├── mania.go
├── max.go
├── oppai.c
├── oppai_impl.c
└── pp.go
├── updater
└── updater.go
└── web
├── structure.go
└── web.go
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: https://www.buymeacoffee.com/BlackShark
2 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build and Archive
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - name: Set up Go
12 | uses: actions/setup-go@v2
13 | with:
14 | go-version: 1.17
15 |
16 | - name: Check out code
17 | uses: actions/checkout@v2
18 |
19 | - name: Install build dependencies
20 | run: sudo apt-get update && sudo apt-get install -y build-essential && sudo apt-get install gcc-multilib
21 |
22 | - name: Install MinGW packages
23 | run: sudo apt-get update && sudo apt-get install -y gcc-mingw-w64-x86-64 gcc-mingw-w64-i686
24 |
25 | - name: Clone static repository
26 | run: git clone https://github.com/l3lackShark/static
27 |
28 | - name: Build for Windows amd64
29 | run: CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -o gosumemory.exe
30 |
31 | - name: Archive Windows amd64
32 | uses: actions/upload-artifact@v2
33 | with:
34 | name: gosumemory_windows_amd64
35 | path: |
36 | gosumemory.exe
37 | static/
38 |
39 | - name: Rename Windows amd64 executable
40 | run: mv gosumemory.exe gosumemory_windows_amd64.exe
41 |
42 | - name: Build for Windows 386
43 | run: CC=i686-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=386 go build -o gosumemory.exe
44 |
45 | - name: Archive Windows 386
46 | uses: actions/upload-artifact@v2
47 | with:
48 | name: gosumemory_windows_386
49 | path: |
50 | gosumemory.exe
51 | static/
52 |
53 | - name: Rename Windows 386 executable
54 | run: mv gosumemory.exe gosumemory_windows_386.exe
55 |
56 | - name: Build for Linux amd64
57 | run: CC=gcc go build -o gosumemory
58 |
59 | - name: Archive Linux amd64
60 | uses: actions/upload-artifact@v2
61 | with:
62 | name: gosumemory_linux_amd64
63 | path: |
64 | gosumemory
65 | static/
66 |
67 | - name: Rename Linux amd64 executable
68 | run: mv gosumemory gosumemory_linux_amd64
69 |
70 | - name: Build for Linux 386
71 | run: CC=gcc CGO_ENABLED=1 GOARCH=386 go build -o gosumemory
72 |
73 | - name: Archive Linux 386
74 | uses: actions/upload-artifact@v2
75 | with:
76 | name: gosumemory_linux_386
77 | path: |
78 | gosumemory
79 | static/
80 |
81 | - name: Rename Linux 386 executable
82 | run: mv gosumemory gosumemory_linux_386
83 |
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: Go
2 | on:
3 | workflow_dispatch:
4 | jobs:
5 |
6 | build:
7 | name: Build
8 | runs-on: self-hosted
9 | steps:
10 |
11 | - name: Set up Go 1.17
12 | uses: actions/setup-go@v1
13 | with:
14 | go-version: 1.17
15 | id: go
16 |
17 | - name: Check out code into the Go module directory
18 | uses: actions/checkout@v2
19 |
20 | - name: Get dependencies
21 | run: |
22 | go get -v -t -d ./...
23 | if [ -f Gopkg.toml ]; then
24 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
25 | dep ensure
26 | fi
27 | - name: Build
28 | run: rsrc -arch=amd64 -ico ./out.ico -o gosumemory.syso && CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -v -o win64/gosumemory.exe && rm gosumemory.syso && rsrc -arch=386 -ico ./out.ico -o gosumemory.syso && CC=i686-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=386 go build -v -o win32/gosumemory.exe && rm gosumemory.syso && CC=gcc go build -v -o lin64/gosumemory && CC=gcc CGO_ENABLED=1 GOARCH=386 go build -v -o lin32/gosumemory
29 | - name: Clone deps
30 | run: git clone https://github.com/l3lackShark/static && find win32 win64 lin32 lin64 -maxdepth 0 -exec cp -rf static {} \;
31 | - name: Upload win32
32 | uses: actions/upload-artifact@v1.0.0
33 | with:
34 | # Artifact name
35 | name: gosumemory_windows_386
36 | # Directory containing files to upload
37 | path: ./win32
38 |
39 | - name: Upload win64
40 | uses: actions/upload-artifact@v1.0.0
41 | with:
42 | # Artifact name
43 | name: gosumemory_windows_amd64
44 | # Directory containing files to upload
45 | path: ./win64
46 |
47 | - name: Upload lin32
48 | uses: actions/upload-artifact@v1.0.0
49 | with:
50 | # Artifact name
51 | name: gosumemory_linux_386
52 | # Directory containing files to upload
53 | path: ./lin32
54 |
55 | - name: Upload lin64
56 | uses: actions/upload-artifact@v1.0.0
57 | with:
58 | # Artifact name
59 | name: gosumemory_linux_amd64
60 | # Directory containing files to upload
61 | path: ./lin64
62 |
63 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | main
2 | gosumemory
3 | gosumemory.exe
4 | gameoverlay
5 | static
6 | osu!.db
7 | config.ini
8 | __debug_bin
9 | gosumemory.exe.manifest
10 | icon.ico
11 | gosumemory.syso
12 | res.ico
13 | /.idea/gosumemory.iml
14 | /.idea/modules.xml
15 | /.idea/vcs.xml
16 | # Default ignored files
17 | /shelf/
18 | /workspace.xml
19 | # Datasource local storage ignored files
20 | /../../../../../../../../../:\Users\BlackShark\go\src\github.com\l3lackShark\gosumemory\.idea/dataSources/
21 | /dataSources.local.xml
22 | # Editor-based HTTP Client requests
23 | /httpRequests/
24 | gameoverlay.zip
25 | *.exe
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l3lackShark/gosumemory/8b1915f594e218c3fbaa23d0dcddfadd4523c762/.gitmodules
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 |
8 | {
9 | "name": "Launch",
10 | "type": "go",
11 | "request": "launch",
12 | "mode": "auto",
13 | "showGlobalVariables": true,
14 | "program": "${workspaceFolder}/main.go",
15 | "env": {},
16 | "args": []
17 | }
18 | ]
19 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU 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 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Yet another memory reader for osu! Supports both Linux and Windows. (**requires sudo on Linux** though since only root can read /proc)
4 |
5 | Build custom pp counters with ease!\
6 | You can contact us here: https://discord.gg/8enr4qD
7 |
8 | # Real-World examples:
9 |
10 | [](https://youtu.be/neHcOLycieE)
11 | [](https://www.twitch.tv/flyingtuna/clip/TransparentObliviousHawkAMPEnergyCherry)
12 | [](https://mega.nz/file/QV1gTKoI#j1QRjDkrjnFvIhyb9JuGi3g_0XZCFzXEXz9PKWcxgmI)
13 | [](https://mega.nz/file/oAlmlQoY#8ABeJPGboMLgCiaY5vR21HX2Km--_jiwqRHOmUJvVmg)
14 | [](https://mega.nz/file/UZsEUKDK#Ji3JAUr8_04Q7u0RG1BAJFGzZ2-CRhRZkEQqdXVrv60)
15 | [](https://mega.nz/file/5dsk1QJD#noUKykU5qJYv53I2DPZ7PY2CIQOftS1ufqzOh4rqOb8)
16 | [](https://mega.nz/file/dY8k1YyZ#1Phdta1CzxXDotjtllUKsZunnCdliYlQ1VrZ_BNaNIs)
17 |
18 |
19 | # Usage
20 |
21 | 1. [Download the latest Release](https://github.com/l3lackShark/gosumemory/releases/latest)
22 | * Unzip files anywhere
23 | > In the root folder of the program, you can find the **static** directory. It contains all of the available counters. Those are getting streamed via HTTP-File server
24 |
25 | 2. Run gosumemory & osu!
26 | * Visit this link in your browser: http://localhost:24050 and choose the counter that you like.
27 | * Add a browser source in OBS (Width and Height could be found in the **Included counters** spoiler)
28 | 4. If using built-in counters covers all of your needs, then you are done here.
29 | > **Please note that auto-updates only cover the executable itself, however, if a new counter gets released, we will mention it in the Release Notes.**\
30 | > If you want to make your own, just create a new directory in the *static* folder.
31 |
32 | # Does this work in-game?
33 |
34 | Yes! see [this](https://github.com/l3lackShark/gosumemory/wiki/GameOverlay)
35 |
36 |
37 | # Included counters:
38 |
39 | Click ME
40 |
41 | ### InGame1
42 |
43 | > Size: 240x133\
44 |
\
45 | By: [Dartandr][1]
46 |
47 | ### InGame2
48 |
49 | > Size: 370x120\
50 |
\
51 | By: [Dartandr][1]
52 |
53 | ### InGame3
54 |
55 | > Size: 150x70\
56 |
\
57 | By: [Dartandr][1]
58 |
59 |
60 | ### reComfortaa
61 |
62 | > Size: 1152x245\
63 | > *Song Selection*\
64 |
\
65 | >*Gameplay*\
66 |
\
67 | By: [Xynogen][6]
68 |
69 | ### MonokaiPane
70 |
71 | > Size: 512x150\
72 | > *Song Selection*\
73 |
\
74 | >*Gameplay 1*\
75 |
\
76 | >*Gameplay 2*\
77 |
\
78 | By: Xynogen
79 |
80 | ### WaveTournament
81 |
82 | > Size: 1920x1080\
83 | > *Chat + Warmup (Team score disabled) view*\
84 |
\
85 | > *Chat + Warmup (Team score disabled) view*\
86 |
\
87 | > *Gameplay*\
88 |
\
89 | By: [VictimCrasher][4]
90 |
91 | ### Classic
92 |
93 | > Size: 550x300\
94 |
\
95 | By: [Dartandr][1]
96 |
97 | ### OldClassic
98 |
99 | > Size: 550x300\
100 |
\
101 | By: [Dartandr][1]
102 |
103 | ### Simplistic
104 |
105 | > Size: 750x150\
106 | > *Song Selection*\
107 |
\
108 | >*Gameplay 1*\
109 |
\
110 | By: [jassper0][5]
111 |
112 | ### DarkAndWhite
113 |
114 | > Size: 840x140\
115 |
\
116 | By: [cyperdark][2]
117 |
118 | ### Kerli1 & Kerli2
119 |
120 | > Size (1)(2): 794x124 | 353x190\
121 |
\
122 | By: [Dartandr][1]
123 |
124 | ### Luscent
125 |
126 | > Size: 1920x1080\
127 | Open-Source Implementation of [Siae's Luscent][3] overlay. No elements were stolen. This is a remake. Please [consider buying](https://gumroad.com/l/Luscent) her version!\
128 |
\
129 | Remake by: [Dartandr][1]
130 |
131 | ### VictimCrasherCompact
132 |
133 | > Size: 560x150\
134 | > *Song Selection*\
135 |
\
136 | >
137 | > *Gameplay*\
138 |
\
139 | By: [VictimCrasher][4]
140 |
141 | ### VictimCrasherOverlay
142 |
143 | > Size: 1920x1080\
144 |
\
145 | By: [VictimCrasher][4]
146 |
147 | ### UnstableRate
148 |
149 | > Size: 300x100\
150 | Just a plain number that shows current UnstableRate, could be useful if you want to put it above your UR Bar.\
151 | By: [Dartandr][1]
152 |
153 | ### MaximalLime
154 |
155 | > Size: 800x306\
156 |
\
157 | By: [cyperdark][2]
158 |
159 | ### MinimalLime
160 |
161 | > Size: 640x130\
162 |
\
163 | By: [cyperdark][2]
164 |
165 | ### TrafficLight
166 |
167 | > Size: 458x380\
168 |
\
169 | By: [cyperdark][2]
170 |
171 | [1]: https://github.com/Dartandr
172 |
173 | [2]: https://github.com/cyperdark
174 |
175 | [3]: https://twitter.com/mk_cou/status/1464208290158501894
176 |
177 | [4]: https://github.com/VictimCrasher
178 |
179 | [5]: https://github.com/jassper0
180 |
181 | [6]: https://github.com/Xyn0gen
182 |
183 |
184 |
185 |
186 | # How does it work?
187 |
188 | gosumemory streams WebSocket data to **ws://localhost:24050/ws** that you can use in any programming language to develop a frontend. We recommend JavaScript though, as it's much easier to make something pretty with the Web framework. All of the included counters are good starting points. There is also http://localhost:24050/json that you can open in a web browser to see the available data. We strongly recommend against sending GET requests to that address in production, please **use WebSocket instead**.
189 |
190 | **[Example JSON and a little wiki of it's values](https://github.com/l3lackShark/gosumemory/wiki/JSON-values)**
191 |
192 | # What if I don't know any programming languages but still want to output data to OBS?
193 | https://www.youtube.com/watch?v=8ApXBEO5bes
194 |
195 | # How do I submit a pp counter?
196 |
197 | Head over to [static](https://github.com/l3lackShark/static) and create a pull request there. If it's good quality, then it will get approved and included in the next release.
198 |
199 | # Tournament Client
200 |
201 | When operating in tourney mode, real-time pp counters for each client, leaderboard and grades don't work. Each gameplay object is sorted by tournament client "id", "menu" object is a tournament manager (state 22).
202 | **Tournament mode works only with Cutting Edge tourney client and is not supported on Linux.**
203 |
204 | # Linux
205 |
206 | You have two options. Either run native, but with sudo privileges, or through WINE.
207 | Please note that Linux builds are not well tested and could contain crashes. Report them if you encounter any.
208 |
209 | # Consider supporting
210 |
211 |
212 | # This project depends on:
213 |
214 | * [cast](https://github.com/spf13/cast)
215 | * [gorilla-websocket](https://github.com/gorilla/websocket)
216 | * [open](https://github.com/skratchdot/open-golang)
217 | * [semver](https://github.com/blang/semver)
218 | * [selfupdate](https://github.com/rhysd/go-github-selfupdate)
219 | * [pretty-print](https://github.com/k0kubun/pp)
220 | * [mp3](https://github.com/tcolgate/mp3)
221 | * [go-windows](https://github.com/elastic/go-windows)
222 |
223 | # Special Thanks to:
224 |
225 | * [Piotrekol](https://github.com/Piotrekol/) and his [ProcessMemoryDataFinder](https://github.com/Piotrekol/ProcessMemoryDataFinder) for most of the memory signatures
226 | * [Francesco149](https://github.com/Francesco149) and his [oppai-ng](https://github.com/Francesco149/oppai-ng) for the pp calculator that we use
227 | * [tdeo](https://github.com/tadeokondrak) for the [Memory Signature Scanner](https://github.com/l3lackShark/gosumemory/tree/master/mem) package
228 | * [omkelderman](https://github.com/omkelderman) for helping out with the [db](https://github.com/l3lackShark/gosumemory/tree/master/db) package
229 | * [jamuwu](https://github.com/jamuwu/osu-strain) and his [osu-strain](https://github.com/jamuwu/osu-strain) for difficulty strain logic
230 | * [cyperdark](https://github.com/cyperdark) and [Dartandr](https://github.com/Dartandr) for frontend designs
231 | * [KotRik](https://github.com/KotRikD) for making an [OBS Script](https://github.com/l3lackShark/gosumemory-helpers/blob/master/gosumemory-reader.py) and porting legacy counters
232 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "log"
7 | "os"
8 | "path/filepath"
9 |
10 | "github.com/l3lackShark/config"
11 | )
12 |
13 | //Config file
14 | var Config map[string]string
15 |
16 | //Init the config file
17 | func Init() {
18 | ex, err := os.Executable()
19 | if err != nil {
20 | panic(err)
21 | }
22 | exPath := filepath.Dir(ex)
23 |
24 | cfg, err := config.SetFile(filepath.Join(exPath, "config.ini"))
25 | if err == config.ErrDoesNotExist {
26 | d := []byte(`[Main]
27 | update = 100
28 | path = auto
29 | cgodisable = false
30 | memdebug = false
31 | memcycletest = false
32 | wine = false
33 |
34 | [Web]
35 | serverip = 127.0.0.1:24050
36 | cors = false
37 |
38 | [GameOverlay] ; https://github.com/l3lackShark/gosumemory/wiki/GameOverlay
39 | enabled = false
40 | gameWidth = 1920
41 | gameHeight = 1080
42 | overlayURL = http://localhost:24050/InGame2
43 | overlayWidth = 380
44 | overlayHeight = 110
45 | overlayOffsetX = 0
46 | overlayOffsetY = 0
47 | overlayScale = 10
48 |
49 | `)
50 | if err := ioutil.WriteFile(filepath.Join(exPath, "config.ini"), d, 0644); err != nil {
51 | panic(err)
52 | }
53 | cfg, err = config.SetFile(filepath.Join(exPath, "config.ini"))
54 | if err != nil {
55 | panic(err)
56 | }
57 | } else if err != nil {
58 | log.Fatalln(err)
59 | }
60 | Config, err = cfg.Parse()
61 | if err != nil {
62 | panic(err)
63 | }
64 | if Config["overlayURL"] == "" { //Quck hack to append GameOverlay stuff to existing config, whole system needs revamp
65 | file, err := os.OpenFile(filepath.Join(exPath, "config.ini"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
66 | if err != nil {
67 | panic(err)
68 | }
69 | _, err = file.WriteString(fmt.Sprintf("\n[GameOverlay]; https://github.com/l3lackShark/gosumemory/wiki/GameOverlay\nenabled = false\ngameWidth = 1920\ngameHeight = 1080\noverlayURL = http://localhost:24050/InGame2\noverlayWidth = 380\noverlayHeight = 110\noverlayOffsetX = 0\noverlayOffsetY = 0\noverlayScale = 10"))
70 | if err != nil {
71 | panic(err)
72 | }
73 |
74 | Init()
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "bufio"
5 | "encoding/binary"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "log"
10 | "os"
11 | "path/filepath"
12 | "runtime/debug"
13 | "strings"
14 | "time"
15 |
16 | "github.com/l3lackShark/gosumemory/memory"
17 |
18 | "github.com/k0kubun/pp"
19 | )
20 |
21 | type osudb struct {
22 | buildVer int32
23 | songsFolderSize int32
24 | isAccountUnlocked bool
25 | Nickname string
26 | BmInfo []beatmapInfo
27 | }
28 |
29 | type beatmapInfo struct {
30 | Artist string
31 | artistU string
32 | Title string
33 | titleU string
34 | Creator string
35 | Difficulty string
36 | audioName string
37 | md5 string
38 | Filename string
39 | rankedStatus int8
40 | NumHitCircles int16
41 | NumSliders int16
42 | NumSpinners int16
43 | dateTime int64
44 | approachRate float32
45 | circleSize float32
46 | hpDrain float32
47 | overallDifficulty float32
48 | sliderVelocity float64 //double
49 | starRatingOsu []starRating
50 | starRatingTaiko []starRating
51 | starRatingCtb []starRating
52 | StarRatingMania []starRating
53 | drainTime int32
54 | totalTime int32
55 | previewTime int32
56 | timingPoints []timingPoint
57 | beatmapID int32
58 | beatmapSetID int32
59 | threadID int32
60 | gradeOsu int8
61 | gradeTaiko int8
62 | gradeCtb int8
63 | gradeMania int8
64 | localOffset int16
65 | stackLeniency float32
66 | gameMode int8
67 | songSource string
68 | songTags string
69 | onlineOffset int16
70 | fontTitle string //?
71 | isUnplayed bool
72 | lastPlayed int64
73 | isOsz2 bool
74 | folderFromSongs string
75 | lastCheckedAgainstOsuRepo int64
76 | isBmSoundIgnored bool
77 | isBmSkinIgnored bool
78 | isBmStoryBoardDisabled bool
79 | isBmVideoDisabled bool
80 | isVisualOverride bool
81 | lastClosedEditor int32
82 | maniaScrollSpeed uint8
83 | }
84 |
85 | type starRating struct {
86 | BitMods int32
87 | StarRating float64 //double
88 | }
89 |
90 | type timingPoint struct {
91 | msPerBeat float64 //double
92 | songOffset float64 //double
93 | inheritedTimingPoint bool
94 | }
95 |
96 | //OsuDB is a structure representation of osu!.db file
97 | var OsuDB osudb
98 |
99 | var internalDB osudb
100 |
101 | //InitDB initializes osu database and gets data within it
102 | func InitDB() error {
103 | fmt.Println("[DB] Awaiting memory data...")
104 | for memory.DynamicAddresses.IsReady != true {
105 | time.Sleep(500 * time.Millisecond)
106 | }
107 | fmt.Println("[DB] Parsing osu!db...")
108 | dbpath := strings.TrimSuffix(memory.SongsFolderPath, "\\Songs")
109 | file, err := os.Open(filepath.Join(dbpath, "osu!.db"))
110 | if err != nil {
111 | pp.Println("Could not find osu!.db, mania related functionality will be unavailable")
112 | return nil
113 | }
114 | osuDB := bufio.NewReader(file)
115 | defer file.Close()
116 | binary.Read(osuDB, binary.LittleEndian, &internalDB.buildVer)
117 | binary.Read(osuDB, binary.LittleEndian, &internalDB.songsFolderSize)
118 | binary.Read(osuDB, binary.LittleEndian, &internalDB.isAccountUnlocked)
119 | var dateTime int64
120 | binary.Read(osuDB, binary.LittleEndian, &dateTime)
121 | internalDB.Nickname, err = readDBString(osuDB)
122 | if err != nil {
123 | log.Println("Database parse error: ", err)
124 | }
125 | internalDB.BmInfo, err = readDBArray(osuDB)
126 | if err != nil {
127 | panic(err)
128 | }
129 | OsuDB.BmInfo = make([]beatmapInfo, len(internalDB.BmInfo))
130 | OsuDB.isAccountUnlocked = internalDB.isAccountUnlocked
131 | OsuDB.buildVer = internalDB.buildVer
132 | OsuDB.Nickname = internalDB.Nickname
133 | OsuDB.songsFolderSize = internalDB.songsFolderSize
134 | for i := 0; i < len(internalDB.BmInfo); i++ {
135 | OsuDB.BmInfo[i].StarRatingMania = make([]starRating, len(internalDB.BmInfo[i].StarRatingMania))
136 | OsuDB.BmInfo[i].Filename = internalDB.BmInfo[i].Filename
137 | OsuDB.BmInfo[i].Artist = internalDB.BmInfo[i].Artist
138 | OsuDB.BmInfo[i].Title = internalDB.BmInfo[i].Title
139 | OsuDB.BmInfo[i].NumHitCircles = internalDB.BmInfo[i].NumHitCircles
140 | OsuDB.BmInfo[i].NumSliders = internalDB.BmInfo[i].NumSliders
141 | OsuDB.BmInfo[i].NumSpinners = internalDB.BmInfo[i].NumSpinners
142 | OsuDB.BmInfo[i].Creator = internalDB.BmInfo[i].Creator
143 | OsuDB.BmInfo[i].Difficulty = internalDB.BmInfo[i].Difficulty
144 | for j := 0; j < len(internalDB.BmInfo[i].StarRatingMania); j++ {
145 | if internalDB.BmInfo[i].StarRatingMania[j].BitMods == 0 || internalDB.BmInfo[i].StarRatingMania[j].BitMods == 64 || internalDB.BmInfo[i].StarRatingMania[j].BitMods == 256 {
146 | OsuDB.BmInfo[i].StarRatingMania[j].BitMods = internalDB.BmInfo[i].StarRatingMania[j].BitMods
147 | OsuDB.BmInfo[i].StarRatingMania[j].StarRating = internalDB.BmInfo[i].StarRatingMania[j].StarRating
148 | }
149 | }
150 | }
151 | internalDB = osudb{}
152 | debug.FreeOSMemory()
153 | fmt.Println("[DB] Done parsing osu!db")
154 |
155 | return nil
156 | }
157 |
158 | func readVarUint(r io.Reader, n uint) (uint64, error) {
159 | if n > 64 {
160 | panic(errors.New("leb128: n must <= 64"))
161 | }
162 | p := make([]byte, 1)
163 | var res uint64
164 | var shift uint
165 | for {
166 | _, err := io.ReadFull(r, p)
167 | if err != nil {
168 | return 0, err
169 | }
170 | b := uint64(p[0])
171 | switch {
172 | // note: can not use b < 1<= 1<<7 && n > 7:
177 | res += (1 << shift) * (b - 1<<7)
178 | shift += 7
179 | n -= 7
180 | default:
181 | return 0, errors.New("leb128: invalid uint")
182 | }
183 | }
184 | }
185 |
186 | func readDBString(osuDB io.Reader) (string, error) {
187 | var checkByte byte
188 | err := binary.Read(osuDB, binary.LittleEndian, &checkByte)
189 | if err != nil {
190 | return "", err
191 | }
192 | switch checkByte {
193 | case 0x00:
194 | return "", nil
195 | case 0x0b:
196 | strlen, err := readVarUint(osuDB, 32)
197 | if err != nil {
198 | return "", err
199 | }
200 | stringBytes := make([]byte, int(strlen))
201 | _, err = io.ReadFull(osuDB, stringBytes)
202 | if err != nil {
203 | return "", err
204 | }
205 | return string(stringBytes[:]), nil
206 |
207 | default:
208 | return "", errors.New("string parse error")
209 | }
210 | }
211 | func readDBArray(osuDB io.Reader) ([]beatmapInfo, error) {
212 | var arrLength int32
213 | err := binary.Read(osuDB, binary.LittleEndian, &arrLength)
214 | if err != nil {
215 | return nil, err
216 | }
217 | if arrLength == -1 {
218 | return nil, nil
219 | }
220 | beatmapsArray := make([]beatmapInfo, int(arrLength))
221 | for i := 0; i < int(arrLength); i++ {
222 | beatmapsArray[i], err = readBeatmapInfo(osuDB)
223 | if err != nil {
224 | return nil, err
225 | }
226 | }
227 | return beatmapsArray, nil
228 | }
229 | func readBeatmapInfo(osuDB io.Reader) (beatmapInfo, error) {
230 |
231 | data := beatmapInfo{}
232 | var err error
233 | data.Artist, err = readDBString(osuDB)
234 | data.artistU, err = readDBString(osuDB)
235 | data.Title, err = readDBString(osuDB)
236 | data.titleU, err = readDBString(osuDB)
237 | data.Creator, err = readDBString(osuDB)
238 | data.Difficulty, err = readDBString(osuDB)
239 | data.audioName, err = readDBString(osuDB)
240 | data.md5, err = readDBString(osuDB)
241 | data.Filename, err = readDBString(osuDB)
242 | err = binary.Read(osuDB, binary.LittleEndian, &data.rankedStatus)
243 | err = binary.Read(osuDB, binary.LittleEndian, &data.NumHitCircles)
244 | err = binary.Read(osuDB, binary.LittleEndian, &data.NumSliders)
245 | err = binary.Read(osuDB, binary.LittleEndian, &data.NumSpinners)
246 | err = binary.Read(osuDB, binary.LittleEndian, &data.dateTime)
247 | err = binary.Read(osuDB, binary.LittleEndian, &data.approachRate)
248 | err = binary.Read(osuDB, binary.LittleEndian, &data.circleSize)
249 | err = binary.Read(osuDB, binary.LittleEndian, &data.hpDrain)
250 | err = binary.Read(osuDB, binary.LittleEndian, &data.overallDifficulty)
251 | err = binary.Read(osuDB, binary.LittleEndian, &data.sliderVelocity)
252 | var lengthList int32 // should move this into a separate functuion and use reflections to set values
253 | err = binary.Read(osuDB, binary.LittleEndian, &lengthList)
254 | if lengthList >= 1 {
255 | var zeroXeight uint8
256 | var zeroXzerod uint8
257 | data.starRatingOsu = make([]starRating, int(lengthList))
258 | for i := 0; i < int(lengthList); i++ {
259 | err = binary.Read(osuDB, binary.LittleEndian, &zeroXeight)
260 | err = binary.Read(osuDB, binary.LittleEndian, &data.starRatingOsu[i].BitMods)
261 | err = binary.Read(osuDB, binary.LittleEndian, &zeroXzerod)
262 | if zeroXzerod != 0x0d || zeroXeight != 0x08 {
263 | pp.Println("Star rating parse err.")
264 | data := beatmapInfo{}
265 | return data, errors.New("Star rating parse err")
266 | }
267 | err = binary.Read(osuDB, binary.LittleEndian, &data.starRatingOsu[i].StarRating)
268 | if err != nil {
269 | data := beatmapInfo{}
270 | return data, err
271 | }
272 | }
273 | }
274 |
275 | var lengthListTaiko int32 // should move this into a separate functuion and use reflections to set values
276 | err = binary.Read(osuDB, binary.LittleEndian, &lengthListTaiko)
277 | if lengthListTaiko >= 1 {
278 | var zeroXeight uint8
279 | var zeroXzerod uint8
280 | data.starRatingTaiko = make([]starRating, int(lengthListTaiko))
281 | for i := 0; i < int(lengthListTaiko); i++ {
282 | err = binary.Read(osuDB, binary.LittleEndian, &zeroXeight)
283 | err = binary.Read(osuDB, binary.LittleEndian, &data.starRatingTaiko[i].BitMods)
284 | err = binary.Read(osuDB, binary.LittleEndian, &zeroXzerod)
285 | if zeroXzerod != 0x0d || zeroXeight != 0x08 {
286 | pp.Println("Star rating parse err. (taiko)")
287 | data := beatmapInfo{}
288 | return data, errors.New("Star rating parse err (taiko)")
289 | }
290 | err = binary.Read(osuDB, binary.LittleEndian, &data.starRatingTaiko[i].StarRating)
291 | if err != nil {
292 | data := beatmapInfo{}
293 | return data, err
294 | }
295 | }
296 | }
297 |
298 | var lengthListCtb int32 // should move this into a separate functuion and use reflections to set values
299 | err = binary.Read(osuDB, binary.LittleEndian, &lengthListCtb)
300 | if lengthListCtb >= 1 {
301 | var zeroXeight uint8
302 | var zeroXzerod uint8
303 | data.starRatingCtb = make([]starRating, int(lengthListCtb))
304 | for i := 0; i < int(lengthListCtb); i++ {
305 | err = binary.Read(osuDB, binary.LittleEndian, &zeroXeight)
306 | err = binary.Read(osuDB, binary.LittleEndian, &data.starRatingCtb[i].BitMods)
307 | err = binary.Read(osuDB, binary.LittleEndian, &zeroXzerod)
308 | if zeroXzerod != 0x0d || zeroXeight != 0x08 {
309 | pp.Println("Star rating parse err. (ctb)")
310 | data := beatmapInfo{}
311 | return data, errors.New("Star rating parse err (ctb)")
312 | }
313 | err = binary.Read(osuDB, binary.LittleEndian, &data.starRatingCtb[i].StarRating)
314 | if err != nil {
315 | data := beatmapInfo{}
316 | return data, err
317 | }
318 | }
319 | }
320 | var lengthListMania int32 // should move this into a separate functuion and use reflections to set values
321 | err = binary.Read(osuDB, binary.LittleEndian, &lengthListMania)
322 | if lengthListMania >= 1 {
323 | var zeroXeight uint8
324 | var zeroXzerod uint8
325 | data.StarRatingMania = make([]starRating, int(lengthListMania))
326 | for i := 0; i < int(lengthListMania); i++ {
327 | err = binary.Read(osuDB, binary.LittleEndian, &zeroXeight)
328 | err = binary.Read(osuDB, binary.LittleEndian, &data.StarRatingMania[i].BitMods)
329 | err = binary.Read(osuDB, binary.LittleEndian, &zeroXzerod)
330 | if zeroXzerod != 0x0d || zeroXeight != 0x08 {
331 | pp.Println("Star rating parse err. (Mania)")
332 | data := beatmapInfo{}
333 | return data, errors.New("Star rating parse err (Mania)")
334 | }
335 | err = binary.Read(osuDB, binary.LittleEndian, &data.StarRatingMania[i].StarRating)
336 | if err != nil {
337 | data := beatmapInfo{}
338 | return data, err
339 | }
340 | }
341 | }
342 | err = binary.Read(osuDB, binary.LittleEndian, &data.drainTime)
343 | err = binary.Read(osuDB, binary.LittleEndian, &data.totalTime)
344 | err = binary.Read(osuDB, binary.LittleEndian, &data.previewTime)
345 |
346 | var lengthTimingPoints int32
347 | err = binary.Read(osuDB, binary.LittleEndian, &lengthTimingPoints)
348 | if lengthTimingPoints >= 1 {
349 | data.timingPoints = make([]timingPoint, int(lengthTimingPoints))
350 | for i := 0; i < int(lengthTimingPoints); i++ {
351 | err = binary.Read(osuDB, binary.LittleEndian, &data.timingPoints[i].msPerBeat)
352 | err = binary.Read(osuDB, binary.LittleEndian, &data.timingPoints[i].songOffset)
353 | err = binary.Read(osuDB, binary.LittleEndian, &data.timingPoints[i].inheritedTimingPoint)
354 | if err != nil {
355 | data := beatmapInfo{}
356 | return data, err
357 | }
358 | }
359 | }
360 | err = binary.Read(osuDB, binary.LittleEndian, &data.beatmapID)
361 | err = binary.Read(osuDB, binary.LittleEndian, &data.beatmapSetID)
362 | err = binary.Read(osuDB, binary.LittleEndian, &data.threadID)
363 | err = binary.Read(osuDB, binary.LittleEndian, &data.gradeOsu)
364 | err = binary.Read(osuDB, binary.LittleEndian, &data.gradeTaiko)
365 | err = binary.Read(osuDB, binary.LittleEndian, &data.gradeCtb)
366 | err = binary.Read(osuDB, binary.LittleEndian, &data.gradeMania)
367 | err = binary.Read(osuDB, binary.LittleEndian, &data.localOffset)
368 | err = binary.Read(osuDB, binary.LittleEndian, &data.stackLeniency)
369 | err = binary.Read(osuDB, binary.LittleEndian, &data.gameMode)
370 | data.songSource, err = readDBString(osuDB)
371 | data.songTags, err = readDBString(osuDB)
372 | err = binary.Read(osuDB, binary.LittleEndian, &data.onlineOffset)
373 | data.fontTitle, err = readDBString(osuDB)
374 | err = binary.Read(osuDB, binary.LittleEndian, &data.isUnplayed)
375 | err = binary.Read(osuDB, binary.LittleEndian, &data.lastPlayed)
376 | err = binary.Read(osuDB, binary.LittleEndian, &data.isOsz2)
377 | data.folderFromSongs, err = readDBString(osuDB)
378 | err = binary.Read(osuDB, binary.LittleEndian, &data.lastCheckedAgainstOsuRepo)
379 | err = binary.Read(osuDB, binary.LittleEndian, &data.isBmSoundIgnored)
380 | err = binary.Read(osuDB, binary.LittleEndian, &data.isBmSkinIgnored)
381 | err = binary.Read(osuDB, binary.LittleEndian, &data.isBmStoryBoardDisabled)
382 | err = binary.Read(osuDB, binary.LittleEndian, &data.isBmVideoDisabled)
383 | err = binary.Read(osuDB, binary.LittleEndian, &data.isVisualOverride)
384 | err = binary.Read(osuDB, binary.LittleEndian, &data.lastClosedEditor)
385 | err = binary.Read(osuDB, binary.LittleEndian, &data.maniaScrollSpeed)
386 |
387 | if err != nil {
388 | data := beatmapInfo{}
389 | return data, err
390 | }
391 | return data, nil
392 | }
393 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/l3lackShark/gosumemory
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/Wieku/gosu-pp v0.0.0-20211202005932-7ae98709eece // indirect
7 | github.com/blang/semver v3.5.1+incompatible
8 | github.com/elastic/go-windows v1.0.1
9 | github.com/gorilla/websocket v1.4.2
10 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
11 | github.com/k0kubun/pp v3.0.1+incompatible
12 | github.com/l3lackShark/config v0.0.0-20201023014929-236482b96fde
13 | github.com/mattn/go-colorable v0.1.11 // indirect
14 | github.com/rhysd/go-github-selfupdate v1.2.3
15 | github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
16 | github.com/spf13/cast v1.4.1
17 | github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300
18 | golang.org/x/sys v0.0.0-20211013075003-97ac67df715c
19 | golang.org/x/text v0.3.7 // indirect
20 | )
21 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Wieku/gosu-pp v0.0.0-20211202005932-7ae98709eece h1:bwCUYu9idzXTf5qYGHJtgbDLCaL7wJ9aIxyTjWR4EOg=
2 | github.com/Wieku/gosu-pp v0.0.0-20211202005932-7ae98709eece/go.mod h1:1Bj2dQOyHt5BCgltsCoiWdRCl7vDb1OgIEiB2V1pmz0=
3 | github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
4 | github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
9 | github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
10 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
12 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
13 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
14 | github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=
15 | github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
16 | github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
17 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
18 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
19 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
20 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
21 | github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
22 | github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
23 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
24 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
25 | github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40=
26 | github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
27 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
28 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
29 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
30 | github.com/l3lackShark/config v0.0.0-20201023014929-236482b96fde h1:Z2oPYkaxn/g/xJ6cy1nZHSpgRuKGdkUC5xDT3JJOy2k=
31 | github.com/l3lackShark/config v0.0.0-20201023014929-236482b96fde/go.mod h1:ga9kHvMOqz3nFftshR6URgGs1C2fXFX2QBruGbgT1wg=
32 | github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
33 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
34 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
35 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
36 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
37 | github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
38 | github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
39 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
40 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
41 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
42 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
43 | github.com/rhysd/go-github-selfupdate v1.2.3 h1:iaa+J202f+Nc+A8zi75uccC8Wg3omaM7HDeimXA22Ag=
44 | github.com/rhysd/go-github-selfupdate v1.2.3/go.mod h1:mp/N8zj6jFfBQy/XMYoWsmfzxazpPAODuqarmPDe2Rg=
45 | github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
46 | github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
47 | github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
48 | github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
49 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
50 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
51 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
52 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
53 | github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
54 | github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
55 | github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300 h1:XQdibLKagjdevRB6vAjVY4qbSr8rQ610YzTkWcxzxSI=
56 | github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300/go.mod h1:FNa/dfN95vAYCNFrIKRrlRo+MBLbwmR9Asa5f2ljmBI=
57 | github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
58 | github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
59 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
60 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
61 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
62 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
63 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
64 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
65 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
66 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
67 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
68 | golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 h1:JIqe8uIcRBHXDQVvZtHwp80ai3Lw3IJAeJEs55Dc1W0=
69 | golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
70 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
71 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
72 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
73 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
74 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
75 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
76 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
77 | golang.org/x/sys v0.0.0-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8=
78 | golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
79 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
80 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
81 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
82 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
83 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
84 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
85 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
86 | google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
87 | google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
88 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
89 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
90 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
91 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
92 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
93 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
94 |
--------------------------------------------------------------------------------
/injctr/injctr.go:
--------------------------------------------------------------------------------
1 | package injctr
2 |
3 | import (
4 | "archive/zip"
5 | "errors"
6 | "fmt"
7 | "io"
8 | "net/http"
9 | "os"
10 | "os/exec"
11 | "path/filepath"
12 | "runtime"
13 | "strconv"
14 | "strings"
15 | )
16 |
17 | //Injct dll into osu's process
18 | func Injct(pid int) error {
19 | if runtime.GOOS != "windows" {
20 | return errors.New("Gameoverlay only works under windows")
21 | }
22 | ex, err := os.Executable()
23 | if err != nil {
24 | panic(err)
25 | }
26 | exPath := filepath.Dir(ex)
27 | _, err = os.Stat("gameoverlay")
28 | if err != nil {
29 | fmt.Println("[GAMEOVERLAY] Downloading gameoverlay... (can take a while, filesize is around 60MB)")
30 | err = downloadFile("https://omk.pics/12/wVugx", "gameoverlay.zip")
31 | if err != nil {
32 | return err
33 | }
34 | unzip("gameoverlay.zip", "gameoverlay")
35 | err = os.Remove("gameoverlay.zip")
36 | if err != nil {
37 | return err
38 | }
39 | }
40 |
41 | _, err = os.Stat("gameoverlay\\gosumemoryoverlay.dll")
42 | if err != nil {
43 | return err
44 | }
45 | _, err = exec.Command("gameoverlay\\a.exe", strconv.Itoa(pid), filepath.Join(exPath, "gameoverlay", "gosumemoryoverlay.dll")).Output()
46 | if err != nil {
47 | return err
48 | }
49 | fmt.Println("[GAMEOVERLAY] Initialized successfully, see https://github.com/l3lackShark/gosumemory/wiki/GameOverlay for tutorial")
50 | return nil
51 | }
52 |
53 | func downloadFile(URL, fileName string) error {
54 | //Get the response bytes from the url
55 | response, err := http.Get(URL)
56 | if err != nil {
57 | return err
58 | }
59 | defer response.Body.Close()
60 |
61 | if response.StatusCode != 200 {
62 | return errors.New("Received non 200 response code")
63 | }
64 | //Create a empty file
65 | file, err := os.Create(fileName)
66 | if err != nil {
67 | return err
68 | }
69 | defer file.Close()
70 |
71 | //Write the bytes to the fiel
72 | _, err = io.Copy(file, response.Body)
73 | if err != nil {
74 | return err
75 | }
76 |
77 | return nil
78 | }
79 |
80 | func unzip(src, dest string) error {
81 | r, err := zip.OpenReader(src)
82 | if err != nil {
83 | return err
84 | }
85 | defer func() {
86 | if err := r.Close(); err != nil {
87 | panic(err)
88 | }
89 | }()
90 |
91 | // Closure to address file descriptors issue with all the deferred .Close() methods
92 | extractAndWriteFile := func(f *zip.File) error {
93 | rc, err := f.Open()
94 | if err != nil {
95 | return err
96 | }
97 | defer func() {
98 | if err := rc.Close(); err != nil {
99 | panic(err)
100 | }
101 | }()
102 |
103 | path := filepath.Join(dest, f.Name)
104 |
105 | // Check for ZipSlip (Directory traversal)
106 | if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
107 | return fmt.Errorf("illegal file path: %s", path)
108 | }
109 |
110 | if f.FileInfo().IsDir() {
111 | os.MkdirAll(path, f.Mode())
112 | } else {
113 | os.MkdirAll(filepath.Dir(path), f.Mode())
114 | f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
115 | if err != nil {
116 | return err
117 | }
118 | defer func() {
119 | if err := f.Close(); err != nil {
120 | panic(err)
121 | }
122 | }()
123 |
124 | _, err = io.Copy(f, rc)
125 | if err != nil {
126 | return err
127 | }
128 | }
129 | return nil
130 | }
131 |
132 | for _, f := range r.File {
133 | err := extractAndWriteFile(f)
134 | if err != nil {
135 | return err
136 | }
137 | }
138 |
139 | return nil
140 | }
141 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "log"
6 | "os"
7 | "runtime"
8 |
9 | "github.com/spf13/cast"
10 |
11 | "github.com/l3lackShark/gosumemory/config"
12 |
13 | "github.com/l3lackShark/gosumemory/mem"
14 | "github.com/l3lackShark/gosumemory/memory"
15 | "github.com/l3lackShark/gosumemory/pp"
16 | "github.com/l3lackShark/gosumemory/updater"
17 | "github.com/l3lackShark/gosumemory/web"
18 | )
19 |
20 | func main() {
21 | config.Init()
22 | updateTimeFlag := flag.Int("update", cast.ToInt(config.Config["update"]), "How fast should we update the values? (in milliseconds)")
23 | shouldWeUpdate := flag.Bool("autoupdate", true, "Should we auto update the application?")
24 | isRunningInWINE := flag.Bool("wine", cast.ToBool(config.Config["wine"]), "Running under WINE?")
25 | songsFolderFlag := flag.String("path", config.Config["path"], `Path to osu! Songs directory ex: /mnt/ps3drive/osu\!/Songs`)
26 | memDebugFlag := flag.Bool("memdebug", cast.ToBool(config.Config["memdebug"]), `Enable verbose memory debugging?`)
27 | memCycleTestFlag := flag.Bool("memcycletest", cast.ToBool(config.Config["memcycletest"]), `Enable memory cycle time measure?`)
28 | disablecgo := flag.Bool("cgodisable", cast.ToBool(config.Config["cgodisable"]), `Disable everything non memory-reader related? (pp counters)`)
29 | flag.Parse()
30 | cgo := *disablecgo
31 | mem.Debug = *memDebugFlag
32 | memory.MemCycle = *memCycleTestFlag
33 | memory.UpdateTime = *updateTimeFlag
34 | memory.SongsFolderPath = *songsFolderFlag
35 | memory.UnderWine = *isRunningInWINE
36 | if runtime.GOOS != "windows" && memory.SongsFolderPath == "auto" {
37 | log.Fatalln("Please specify path to osu!Songs (see --help)")
38 | }
39 | if memory.SongsFolderPath != "auto" {
40 | if _, err := os.Stat(memory.SongsFolderPath); os.IsNotExist(err) {
41 | log.Fatalln(`Specified Songs directory does not exist on the system! (try setting to "auto" if you are on Windows or make sure that the path is correct)`)
42 | }
43 | }
44 | if *shouldWeUpdate == true {
45 | updater.DoSelfUpdate()
46 | }
47 |
48 | go memory.Init()
49 | // err := db.InitDB()
50 | // if err != nil {
51 | // log.Println(err)
52 | // time.Sleep(5 * time.Second)
53 | // os.Exit(1)
54 | // }
55 | go web.SetupStructure()
56 | go web.SetupRoutes()
57 | if !cgo {
58 | go pp.GetData()
59 | go pp.GetFCData()
60 | go pp.GetMaxData()
61 | go pp.GetEditorData()
62 | }
63 | web.HTTPServer()
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/mem/debug.go:
--------------------------------------------------------------------------------
1 | package mem
2 |
3 | import (
4 | "fmt"
5 | "runtime"
6 | "strings"
7 | "sync/atomic"
8 | )
9 |
10 | var Debug = false
11 | var indent int32 = 0
12 |
13 | func beginDebug() {
14 | if !Debug {
15 | return
16 | }
17 |
18 | var callers [8]uintptr
19 | n := runtime.Callers(2, callers[:])
20 |
21 | frames := runtime.CallersFrames(callers[:n])
22 | var stack []string
23 | for {
24 | frame, more := frames.Next()
25 | if strings.HasPrefix(frame.Function, "runtime") {
26 | break
27 | }
28 | stack = append(stack,
29 | fmt.Sprintf("%s:%d", frame.Function, frame.Line))
30 | if !more {
31 | break
32 | }
33 | }
34 | for i, j := 0, len(stack)-1; i < j; i, j = i+1, j-1 {
35 | stack[i], stack[j] = stack[j], stack[i]
36 | }
37 | for _, s := range stack {
38 | log("%s\n", s)
39 | atomic.AddInt32(&indent, 1)
40 | }
41 | }
42 |
43 | func pushDebug() int32 {
44 | if !Debug {
45 | return 0
46 | }
47 |
48 | return atomic.AddInt32(&indent, 1) - 1
49 | }
50 |
51 | func popDebug(i int32) {
52 | if !Debug {
53 | return
54 | }
55 |
56 | atomic.StoreInt32(&indent, i)
57 | }
58 |
59 | func endDebug() {
60 | if !Debug {
61 | return
62 | }
63 |
64 | atomic.StoreInt32(&indent, 0)
65 | }
66 |
67 | func log(format string, args ...interface{}) {
68 | if !Debug {
69 | return
70 | }
71 |
72 | for i := int32(0); i < atomic.LoadInt32(&indent)*4; i++ {
73 | fmt.Printf(" ")
74 | }
75 | fmt.Printf(format, args...)
76 | }
77 |
78 | func logRead(b []byte, n int, off int64, err error) {
79 | if !Debug {
80 | return
81 | }
82 |
83 | if err == nil {
84 | var arr string
85 | if n < 16 {
86 | arr = fmt.Sprintf("%v", b[:n])
87 | } else {
88 | arr = "[...]"
89 | }
90 | log("Read(0x%x, %d): %s\n", uint64(off), n, arr)
91 | } else {
92 | log("Read(0x%x, %d): %v\n", uint64(off), n, err)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/mem/linux.go:
--------------------------------------------------------------------------------
1 | // +build linux
2 |
3 | package mem
4 |
5 | import (
6 | "bufio"
7 | "bytes"
8 | "fmt"
9 | "io"
10 | "io/ioutil"
11 | "os"
12 | "path/filepath"
13 | "regexp"
14 | "strconv"
15 |
16 | "golang.org/x/sys/unix"
17 | )
18 |
19 | func FindProcess(re *regexp.Regexp, blacklistedTitles ...string) ([]Process, error) { //blacklistedTitles not implemented yet
20 | dirs, err := ioutil.ReadDir("/proc")
21 | if err != nil {
22 | return nil, err
23 | }
24 | var pids []int
25 | for _, dir := range dirs {
26 | if pid, err := strconv.Atoi(dir.Name()); err == nil {
27 | pids = append(pids, pid)
28 | }
29 | }
30 | var procs []Process
31 | for _, pid := range pids {
32 | path := fmt.Sprintf("/proc/%d/cmdline", pid)
33 | f, err := os.Open(path)
34 | if err != nil {
35 | continue
36 | }
37 | defer f.Close()
38 |
39 | content, err := ioutil.ReadAll(f)
40 | if err != nil {
41 | continue
42 | }
43 |
44 | slices := bytes.SplitN(content, []byte{'\x00'}, 2)
45 | if !re.Match(slices[0]) {
46 | continue
47 | }
48 |
49 | procs = append(procs, process{pid})
50 | }
51 | if len(procs) < 1 {
52 | return nil, ErrNoProcess
53 | }
54 | return procs, nil
55 | }
56 |
57 | type process struct {
58 | pid int
59 | }
60 |
61 | func (p process) ExecutablePath() (string, error) {
62 | path := fmt.Sprintf("/proc/%d/exe", p.pid)
63 | path, err := filepath.EvalSymlinks(path)
64 | if err != nil {
65 | return "", err
66 | }
67 | return filepath.Abs(path)
68 | }
69 |
70 | func (p process) Close() error {
71 | return nil
72 | }
73 |
74 | func (p process) Pid() int {
75 | return p.pid
76 | }
77 |
78 | func (p process) ReadAt(b []byte, off int64) (n int, err error) {
79 | localIov := [1]unix.Iovec{
80 | {Base: &b[0]},
81 | }
82 | localIov[0].SetLen(len(b))
83 | remoteIov := [1]unix.RemoteIovec{
84 | {Base: uintptr(off), Len: len(b)},
85 | }
86 | n, err = unix.ProcessVMReadv(p.pid, localIov[:], remoteIov[:], 0)
87 | logRead(b, n, off, err)
88 | return n, err
89 | }
90 |
91 | func (p process) Maps() ([]Map, error) {
92 | path := fmt.Sprintf("/proc/%d/maps", p.pid)
93 | f, err := os.Open(path)
94 | if err != nil {
95 | return nil, err
96 | }
97 | defer f.Close()
98 |
99 | var maps []Map
100 | s := bufio.NewScanner(f)
101 | for s.Scan() {
102 | var reg region
103 | _, err := fmt.Sscanf(s.Text(), "%x-%x",
104 | ®.start, ®.end)
105 | if err != nil && err != io.EOF {
106 | return nil, err
107 | }
108 | maps = append(maps, reg)
109 | }
110 | return maps, nil
111 | }
112 |
113 | type region struct {
114 | start int64
115 | end int64
116 | }
117 |
118 | func (r region) Start() int64 {
119 | return r.start
120 | }
121 |
122 | func (r region) Size() int64 {
123 | return r.end - r.start
124 | }
125 |
--------------------------------------------------------------------------------
/mem/mem.go:
--------------------------------------------------------------------------------
1 | package mem
2 |
3 | import (
4 | "errors"
5 | "io"
6 | )
7 |
8 | var (
9 | ErrNoProcess = errors.New("no process matching the criteria was found")
10 | ErrPatternNotFound = errors.New("no memory matched the pattern")
11 | )
12 |
13 | type (
14 | Process interface {
15 | io.Closer
16 | io.ReaderAt
17 | Pid() int
18 | Maps() ([]Map, error)
19 | ExecutablePath() (string, error)
20 | }
21 |
22 | Map interface {
23 | Start() int64
24 | Size() int64
25 | }
26 | )
27 |
--------------------------------------------------------------------------------
/mem/read.go:
--------------------------------------------------------------------------------
1 | package mem
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | "io"
7 | "math"
8 | "unicode/utf16"
9 | )
10 |
11 | const (
12 | MaxStringLength = 4096
13 | MaxArrayLength = 65536
14 | )
15 |
16 | var (
17 | ErrStringTooLong = errors.New("read failed, string too long")
18 | ErrArrayTooLong = errors.New("read failed, array too long")
19 |
20 | ErrInvalidStringLength = errors.New("read failed, string length < 0")
21 | ErrInvalidArrayLength = errors.New("read failed, array length < 0")
22 | )
23 |
24 | func readFullAt(r io.ReaderAt, buf []byte, off int64) (n int, err error) {
25 | for n < len(buf) && err == nil {
26 | var nn int
27 | nn, err = r.ReadAt(buf[n:], off+int64(n))
28 | n += nn
29 | }
30 |
31 | if n >= len(buf) {
32 | err = nil
33 | } else if n > 0 && err == io.EOF {
34 | err = io.ErrUnexpectedEOF
35 | }
36 |
37 | return
38 | }
39 |
40 | func bytesToInt(buf []byte) uint64 {
41 | var num uint64
42 |
43 | for i := range buf {
44 | num |= uint64(buf[i]) << (8 * i)
45 | }
46 |
47 | return num
48 | }
49 |
50 | func removeLast(slice []int64) ([]int64, *int64) {
51 | if len(slice) == 0 {
52 | return nil, nil
53 | }
54 |
55 | return slice[:len(slice)-1], &slice[len(slice)-1]
56 | }
57 |
58 | func followOffsets(r io.ReaderAt, addr int64, offsets ...int64) (int64, error) {
59 | start, last := removeLast(offsets)
60 |
61 | for _, offset := range start {
62 | newaddr, err := ReadPtr(r, addr+offset, 0)
63 | if err != nil {
64 | return 0, err
65 | }
66 | addr = newaddr
67 | }
68 |
69 | if last != nil {
70 | addr += *last
71 | }
72 |
73 | return addr, nil
74 | }
75 |
76 | func readUintRaw(r io.ReaderAt, addr int64, size int) (uint64, error) {
77 | var buf [8]byte
78 |
79 | if _, err := readFullAt(r, buf[:size], addr); err != nil {
80 | return 0, err
81 | }
82 |
83 | return bytesToInt(buf[:size]), nil
84 | }
85 |
86 | func readUint(r io.ReaderAt, addr int64, size int, offsets ...int64) (uint64, error) {
87 | addr, err := followOffsets(r, addr, offsets...)
88 | if err != nil {
89 | return 0, err
90 | }
91 |
92 | return readUintRaw(r, addr, size)
93 | }
94 |
95 | func readUintArray(r io.ReaderAt, addr int64, size int,
96 | offsets ...int64) ([]uint64, error) {
97 | base, err := followOffsets(r, addr, offsets...)
98 | if err != nil {
99 | return nil, err
100 | }
101 |
102 | length, err := ReadInt32(r, base, 12)
103 | if err != nil {
104 | return nil, err
105 | }
106 |
107 | if length < 0 {
108 | return nil, ErrInvalidArrayLength
109 | }
110 |
111 | if length > MaxArrayLength {
112 | return nil, ErrArrayTooLong
113 | }
114 |
115 | data, err := ReadPtr(r, base, 4)
116 | if err != nil {
117 | return nil, err
118 | }
119 |
120 | buf := make([]byte, int(length)*size)
121 | _, err = readFullAt(r, buf, data+8)
122 | if err != nil {
123 | return nil, err
124 | }
125 |
126 | buf64 := make([]uint64, length)
127 | for i := 0; i < int(length); i++ {
128 | buf64[i] = bytesToInt(buf[i*size : i*size+size])
129 | }
130 |
131 | return buf64, nil
132 | }
133 |
134 | func ReadString(r io.ReaderAt, addr int64, offsets ...int64) (string, error) {
135 | base, err := followOffsets(r, addr, offsets...)
136 | if err != nil {
137 | return "", err
138 | }
139 |
140 | length, err := ReadUint32(r, base, 4)
141 | if err != nil {
142 | return "", err
143 | }
144 |
145 | if length < 0 {
146 | return "", ErrInvalidStringLength
147 | }
148 |
149 | if length > MaxStringLength {
150 | return "", ErrStringTooLong
151 | }
152 |
153 | buf := make([]byte, length*2)
154 |
155 | _, err = readFullAt(r, buf, base+8)
156 | if err != nil {
157 | return "", err
158 | }
159 |
160 | buf16 := make([]uint16, length)
161 | for i := uint32(0); i < length; i += 1 {
162 | buf16[i] = binary.LittleEndian.Uint16(buf[i*2 : i*2+2])
163 | }
164 |
165 | return string(utf16.Decode(buf16)), nil
166 | }
167 |
168 | func ReadInt8(r io.ReaderAt, addr int64, offsets ...int64) (int8, error) {
169 | num, err := readUint(r, addr, 1, offsets...)
170 | return int8(int64(num)), err
171 | }
172 |
173 | func ReadInt8Array(r io.ReaderAt, addr int64, offsets ...int64) ([]int8, error) {
174 | array, err := readUintArray(r, addr, 1, offsets...)
175 | array64 := make([]int8, len(array))
176 | for i, v := range array {
177 | array64[i] = int8(int64(v))
178 | }
179 | return array64, err
180 | }
181 |
182 | func ReadUint8(r io.ReaderAt, addr int64, offsets ...int64) (uint8, error) {
183 | num, err := readUint(r, addr, 1, offsets...)
184 | return uint8(num), err
185 | }
186 |
187 | func ReadUint8Array(r io.ReaderAt, addr int64, offsets ...int64) ([]uint8, error) {
188 | array, err := readUintArray(r, addr, 1, offsets...)
189 | array8 := make([]uint8, len(array))
190 | for i, v := range array {
191 | array8[i] = uint8(v)
192 | }
193 | return array8, err
194 | }
195 |
196 | func ReadInt16(r io.ReaderAt, addr int64, offsets ...int64) (int16, error) {
197 | num, err := readUint(r, addr, 2, offsets...)
198 | return int16(int64(num)), err
199 | }
200 |
201 | func ReadInt16Array(r io.ReaderAt, addr int64, offsets ...int64) ([]int16, error) {
202 | array, err := readUintArray(r, addr, 2, offsets...)
203 | array16 := make([]int16, len(array))
204 | for i, v := range array {
205 | array16[i] = int16(int64(v))
206 | }
207 | return array16, err
208 | }
209 |
210 | func ReadUint16(r io.ReaderAt, addr int64, offsets ...int64) (uint16, error) {
211 | num, err := readUint(r, addr, 2, offsets...)
212 | return uint16(num), err
213 | }
214 |
215 | func ReadUint16Array(r io.ReaderAt, addr int64, offsets ...int64) ([]uint16, error) {
216 | array, err := readUintArray(r, addr, 2, offsets...)
217 | array16 := make([]uint16, len(array))
218 | for i, v := range array {
219 | array16[i] = uint16(v)
220 | }
221 | return array16, err
222 | }
223 |
224 | func ReadInt32(r io.ReaderAt, addr int64, offsets ...int64) (int32, error) {
225 | num, err := readUint(r, addr, 4, offsets...)
226 | return int32(int64(num)), err
227 | }
228 |
229 | func ReadInt32Array(r io.ReaderAt, addr int64, offsets ...int64) ([]int32, error) {
230 | array, err := readUintArray(r, addr, 4, offsets...)
231 | array32 := make([]int32, len(array))
232 | for i, v := range array {
233 | array32[i] = int32(int64(v))
234 | }
235 | return array32, err
236 | }
237 |
238 | func ReadUint32(r io.ReaderAt, addr int64, offsets ...int64) (uint32, error) {
239 | num, err := readUint(r, addr, 4, offsets...)
240 | return uint32(num), err
241 | }
242 |
243 | func ReadUint32Array(r io.ReaderAt, addr int64, offsets ...int64) ([]uint32, error) {
244 | array, err := readUintArray(r, addr, 4, offsets...)
245 | array32 := make([]uint32, len(array))
246 | for i, v := range array {
247 | array32[i] = uint32(v)
248 | }
249 | return array32, err
250 | }
251 |
252 | func ReadInt64(r io.ReaderAt, addr int64, offsets ...int64) (int64, error) {
253 | num, err := readUint(r, addr, 8, offsets...)
254 | return int64(num), err
255 | }
256 |
257 | func ReadInt64Array(r io.ReaderAt, addr int64, offsets ...int64) ([]int64, error) {
258 | array, err := readUintArray(r, addr, 8, offsets...)
259 | array64 := make([]int64, len(array))
260 | for i, v := range array {
261 | array64[i] = int64(v)
262 | }
263 | return array64, err
264 | }
265 |
266 | func ReadUint64(r io.ReaderAt, addr int64, offsets ...int64) (uint64, error) {
267 | num, err := readUint(r, addr, 8, offsets...)
268 | return uint64(num), err
269 | }
270 |
271 | func ReadUint64Array(r io.ReaderAt, addr int64, offsets ...int64) ([]uint64, error) {
272 | array, err := readUintArray(r, addr, 8, offsets...)
273 | array64 := make([]uint64, len(array))
274 | for i, v := range array {
275 | array64[i] = v
276 | }
277 | return array64, err
278 | }
279 |
280 | func ReadFloat32(r io.ReaderAt, addr int64, offsets ...int64) (float32, error) {
281 | num, err := readUint(r, addr, 4, offsets...)
282 | return math.Float32frombits(uint32(num)), err
283 | }
284 |
285 | func ReadFloat32Array(r io.ReaderAt, addr int64, offsets ...int64) ([]float32, error) {
286 | array, err := readUintArray(r, addr, 4, offsets...)
287 | array32 := make([]float32, len(array))
288 | for i, v := range array {
289 | array32[i] = math.Float32frombits(uint32(v))
290 | }
291 | return array32, err
292 | }
293 |
294 | func ReadFloat64(r io.ReaderAt, addr int64, offsets ...int64) (float64, error) {
295 | num, err := readUint(r, addr, 8, offsets...)
296 | return math.Float64frombits(num), err
297 | }
298 |
299 | func ReadFloat64Array(r io.ReaderAt, addr int64, offsets ...int64) ([]float64, error) {
300 | array, err := readUintArray(r, addr, 8, offsets...)
301 | array64 := make([]float64, len(array))
302 | for i, v := range array {
303 | array64[i] = math.Float64frombits(v)
304 | }
305 | return array64, err
306 | }
307 |
308 | func ReadPtr(r io.ReaderAt, addr int64, offsets ...int64) (int64, error) {
309 | num, err := ReadUint32(r, addr, offsets...)
310 | return int64(num), err
311 | }
312 |
--------------------------------------------------------------------------------
/mem/scan.go:
--------------------------------------------------------------------------------
1 | package mem
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "fmt"
7 | "io"
8 | "reflect"
9 | "strconv"
10 | "strings"
11 | "text/scanner"
12 | )
13 |
14 | type pattern struct {
15 | Bytes []uint32
16 | Mask []uint32
17 | }
18 |
19 | func parsePattern(s string) (pattern, error) {
20 | var bytes, mask []byte
21 | for _, bytestr := range strings.Split(s, " ") {
22 | if bytestr == "??" {
23 | bytes = append(bytes, 0x00)
24 | mask = append(mask, 0x00)
25 | continue
26 | }
27 | b, err := strconv.ParseUint(bytestr, 16, 8)
28 | if err != nil {
29 | return pattern{}, err
30 | }
31 | bytes = append(bytes, byte(b))
32 | mask = append(mask, 0xFF)
33 | }
34 |
35 | var p pattern
36 | for i := 0; i < len(bytes); i += 4 {
37 | var byt uint32
38 | for i, b := range bytes[i : i+4] {
39 | byt |= uint32(b) << (i * 8)
40 | }
41 | p.Bytes = append(p.Bytes, byt)
42 | var mas uint32
43 | for i, m := range mask[i : i+4] {
44 | mas |= uint32(m) << (i * 8)
45 | }
46 | p.Mask = append(p.Mask, mas)
47 | }
48 |
49 | return p, nil
50 | }
51 |
52 | func maskbyte(needle, mask uint32) (search byte, offset int) {
53 | for offset := 0; offset < 4; offset++ {
54 | maskByte := byte(mask >> (offset * 8) & 0xFF)
55 | needleByte := byte(needle >> (offset * 8) & 0xFF)
56 | if maskByte != 0x00 && needleByte != 0x00 {
57 | return needleByte, offset
58 | }
59 | }
60 | panic("empty mask (bad pattern)")
61 | }
62 |
63 | func search(buf []byte, pat pattern) (int, bool) {
64 | progress := 0
65 | maskbyte, byteoffset := maskbyte(pat.Bytes[0], pat.Mask[0])
66 | for {
67 | i := bytes.IndexByte(buf, maskbyte)
68 | if i == -1 {
69 | return 0, false
70 | }
71 | begin := i - byteoffset
72 | end := begin + len(pat.Bytes)*4
73 | if begin < 0 || end > len(buf) {
74 | progress += i + 1
75 | buf = buf[i+1:]
76 | continue
77 | }
78 | success := true
79 | for j := range pat.Bytes {
80 | needle, mask := pat.Bytes[j], pat.Mask[j]
81 | slice := buf[begin+(j*4) : begin+(j*4)+4]
82 | haystack := binary.LittleEndian.Uint32(slice)
83 | if needle^haystack&mask != 0 {
84 | success = false
85 | break
86 | }
87 | }
88 | if success {
89 | return begin + progress, true
90 | }
91 | progress += i + 1
92 | buf = buf[i+1:]
93 | }
94 | }
95 |
96 | func find(p Process, pat pattern, reg Map) (int64, error) {
97 | const bufsize = 65536
98 | var buf [bufsize]byte
99 | for i := int64(0); i < reg.Size(); {
100 | ntoread := reg.Size() - i
101 | if ntoread >= bufsize {
102 | ntoread = bufsize - 1
103 | }
104 | n, err := p.ReadAt(buf[:ntoread], int64(reg.Start()+i))
105 | if err != nil {
106 | return 0, err
107 | }
108 | if at, ok := search(buf[:n], pat); ok {
109 | return int64(reg.Start() + i + int64(at)), nil
110 | }
111 | diff := n - len(pat.Bytes)*8
112 | if diff <= 1 {
113 | diff = 1
114 | }
115 | i += int64(diff)
116 | }
117 | return 0, ErrPatternNotFound
118 | }
119 |
120 | func Scan(p Process, pattern string) (int64, error) {
121 | maps, err := p.Maps()
122 | if err != nil {
123 | return 0, err
124 | }
125 |
126 | pat, err := parsePattern(pattern)
127 | if err != nil {
128 | return 0, err
129 | }
130 |
131 | for _, reg := range maps {
132 | if i, err := find(p, pat, reg); err == nil {
133 | return i, nil
134 | }
135 | }
136 |
137 | return 0, fmt.Errorf("no memory matched the pattern: %s", pattern)
138 | }
139 |
140 | func ResolvePatterns(p Process, offsets interface{}) error {
141 | pval := reflect.ValueOf(offsets)
142 | val := reflect.Indirect(pval)
143 | valt := val.Type()
144 | if pval.Kind() != reflect.Ptr || val.Kind() != reflect.Struct {
145 | panic("offsets must be a pointer to a struct")
146 | }
147 | var anyErr error
148 | for i := 0; i < val.NumField(); i++ {
149 | field := valt.Field(i)
150 | sig, ok := field.Tag.Lookup("sig")
151 | if !ok {
152 | continue
153 | }
154 | offset, err := Scan(p, sig)
155 | if err != nil {
156 | anyErr = err
157 | continue
158 | }
159 | val.Field(i).Set(reflect.ValueOf(offset))
160 | }
161 |
162 | return anyErr
163 | }
164 |
165 | type ReadError []error
166 |
167 | func (r ReadError) Error() string {
168 | var strs []string
169 | for _, err := range r {
170 | strs = append(strs, err.Error())
171 | }
172 | return strings.Join(strs, ", ")
173 | }
174 |
175 | func Read(r io.ReaderAt, addresses interface{}, p interface{}) error {
176 | beginDebug()
177 | defer endDebug()
178 |
179 | addrpval := reflect.ValueOf(addresses)
180 | addrval := addrpval.Elem()
181 |
182 | if addrval.Kind() != reflect.Struct {
183 | panic("addresses must be a pointer to a struct")
184 | }
185 |
186 | pval := reflect.ValueOf(p)
187 | val := pval.Elem()
188 | valt := val.Type()
189 |
190 | if val.Kind() != reflect.Struct {
191 | panic("p must be a pointer to a struct")
192 | }
193 |
194 | var errs ReadError
195 | for i := 0; i < val.NumField(); i++ {
196 | field := val.Field(i)
197 | fieldt := valt.Field(i)
198 | tag, ok := fieldt.Tag.Lookup("mem")
199 | if !ok {
200 | continue
201 | }
202 | evalFunc := func(addr int64) (int64, error) {
203 | return ReadPtr(r, addr, 0)
204 | }
205 | var varFunc func(name string) (int64, error)
206 | varFunc = func(name string) (int64, error) {
207 | field := addrval.FieldByName(name)
208 | if field.IsValid() {
209 | addr := field.Interface().(int64)
210 | log("%s: 0x%x\n", name, addr)
211 | return addr, nil
212 | }
213 | method := addrval.Addr().MethodByName(name)
214 | if method.IsValid() {
215 | ret := method.Call([]reflect.Value{})
216 | exprStr := ret[0].Interface().(string)
217 | expr, err := parseMem(exprStr, varFunc)
218 | if err != nil {
219 | log("Failed to parse variable %s: %v\n",
220 | name, err)
221 | return 0, err
222 | }
223 | log("%s(): %#v\n", name, exprStr)
224 | val, err := expr.eval(evalFunc)
225 | if err == nil {
226 | log("%s() = 0x%x\n",
227 | name, val)
228 | } else {
229 | log("Failed to resolve variable %s: %v\n",
230 | name, err)
231 | }
232 | return val, err
233 | }
234 | return 0, fmt.Errorf("undefined variable %s", name)
235 | }
236 | log("%v: %#v\n", fieldt.Name, tag)
237 | dbg := pushDebug()
238 | expr, err := parseMem(tag, varFunc)
239 | if err != nil {
240 | return fmt.Errorf(
241 | "failed to parse mem tag for %s.%s: %w",
242 | valt.Name(), fieldt.Name, err)
243 | }
244 | addr, err := expr.eval(evalFunc)
245 | if err != nil {
246 | return fmt.Errorf("failed to read %s.%s: %w",
247 | valt.Name(), fieldt.Name, err)
248 | }
249 | if err := readPrimitive(r, field.Addr().Interface(),
250 | addr, 0); err != nil {
251 | err = fmt.Errorf("failed to read %s.%s: %w",
252 | valt.Name(), fieldt.Name, err)
253 | errs = append(errs, err)
254 | }
255 | popDebug(dbg)
256 | }
257 |
258 | if len(errs) != 0 {
259 | return errs
260 | }
261 |
262 | return nil
263 | }
264 |
265 | func readPrimitive(r io.ReaderAt, p interface{},
266 | addr int64, offsets ...int64) error {
267 | var err error
268 | switch p := p.(type) {
269 | case *int8:
270 | *p, err = ReadInt8(r, addr, offsets...)
271 | case *int16:
272 | *p, err = ReadInt16(r, addr, offsets...)
273 | case *int32:
274 | *p, err = ReadInt32(r, addr, offsets...)
275 | case *int64:
276 | *p, err = ReadInt64(r, addr, offsets...)
277 | case *uint8:
278 | *p, err = ReadUint8(r, addr, offsets...)
279 | case *uint16:
280 | *p, err = ReadUint16(r, addr, offsets...)
281 | case *uint32:
282 | *p, err = ReadUint32(r, addr, offsets...)
283 | case *uint64:
284 | *p, err = ReadUint64(r, addr, offsets...)
285 | case *float32:
286 | *p, err = ReadFloat32(r, addr, offsets...)
287 | case *float64:
288 | *p, err = ReadFloat64(r, addr, offsets...)
289 | case *[]int8:
290 | *p, err = ReadInt8Array(r, addr, offsets...)
291 | case *[]int16:
292 | *p, err = ReadInt16Array(r, addr, offsets...)
293 | case *[]int32:
294 | *p, err = ReadInt32Array(r, addr, offsets...)
295 | case *[]int64:
296 | *p, err = ReadInt64Array(r, addr, offsets...)
297 | case *[]uint8:
298 | *p, err = ReadUint8Array(r, addr, offsets...)
299 | case *[]uint16:
300 | *p, err = ReadUint16Array(r, addr, offsets...)
301 | case *[]uint32:
302 | *p, err = ReadUint32Array(r, addr, offsets...)
303 | case *[]uint64:
304 | *p, err = ReadUint64Array(r, addr, offsets...)
305 | case *[]float32:
306 | *p, err = ReadFloat32Array(r, addr, offsets...)
307 | case *[]float64:
308 | *p, err = ReadFloat64Array(r, addr, offsets...)
309 | case *string:
310 | *p, err = ReadString(r, addr, offsets...)
311 | default:
312 | err = fmt.Errorf("unknown type %T", p)
313 | }
314 | return err
315 |
316 | }
317 |
318 | type mem struct {
319 | Child *mem
320 | Offset int64
321 | }
322 |
323 | func (m *mem) String() string {
324 | var b strings.Builder
325 | if m.Child != nil {
326 | fmt.Fprintf(&b, "[%s]", m.Child)
327 | }
328 | if m.Child != nil && m.Offset != 0 {
329 | b.WriteString(" + ")
330 | }
331 | if m.Offset != 0 {
332 | fmt.Fprintf(&b, "0x%x", m.Offset)
333 | }
334 | return b.String()
335 | }
336 |
337 | func (m *mem) eval(f func(p int64) (int64, error)) (int64, error) {
338 | if m.Child != nil {
339 | childAddr, err := m.Child.eval(f)
340 | if err != nil {
341 | return 0, err
342 | }
343 |
344 | dereferenced, err := f(childAddr)
345 | if err != nil {
346 | return 0, err
347 | }
348 |
349 | if m.Offset == 0 {
350 | log("[0x%x] = 0x%x\n", childAddr, dereferenced)
351 | } else {
352 | log("[0x%x] + 0x%x = 0x%x\n", childAddr, m.Offset,
353 | dereferenced+m.Offset)
354 | }
355 |
356 | return dereferenced + m.Offset, nil
357 | } else {
358 | return m.Offset, nil
359 | }
360 | }
361 |
362 | func parseMem(tag string,
363 | varFunc func(name string) (int64, error)) (*mem, error) {
364 | var s scanner.Scanner
365 | s.Init(strings.NewReader(tag))
366 | s.Mode = scanner.ScanIdents | scanner.ScanInts
367 | return parseMemExpr(&s, varFunc, false)
368 | }
369 |
370 | func parseMemExpr(s *scanner.Scanner,
371 | varFunc func(name string) (int64, error), inBrackets bool) (*mem, error) {
372 | expr := &mem{}
373 | switch tok := s.Scan(); tok {
374 | case '[':
375 | inner, err := parseMemExpr(s, varFunc, true)
376 | if err != nil {
377 | return nil, err
378 | }
379 | expr.Child = inner
380 | case scanner.Ident:
381 | name := s.TokenText()
382 | var err error
383 | expr.Offset, err = varFunc(name)
384 | if err != nil {
385 | return nil, err
386 | }
387 | case scanner.Int:
388 | var err error
389 | expr.Offset, err = strconv.ParseInt(s.TokenText(), 0, 64)
390 | if err != nil {
391 | return nil, err
392 | }
393 | default:
394 | return nil, fmt.Errorf("unexpected token %d (%s)",
395 | tok, s.TokenText())
396 | }
397 |
398 | switch tok := s.Scan(); tok {
399 | case '+', '-':
400 | rest, err := parseMemExpr(s, varFunc, inBrackets)
401 | if err != nil {
402 | return nil, err
403 | }
404 | switch tok {
405 | case '+':
406 | expr.Offset += rest.Offset
407 | case '-':
408 | expr.Offset -= rest.Offset
409 | }
410 | return expr, nil
411 | case scanner.EOF, ']':
412 | if tok == ']' && !inBrackets {
413 | return nil, fmt.Errorf("unexpected token %d (%s)",
414 | tok, s.TokenText())
415 | }
416 | return expr, nil
417 | default:
418 | return nil, fmt.Errorf("unexpected token %d (%s)",
419 | tok, s.TokenText())
420 | }
421 | }
422 |
--------------------------------------------------------------------------------
/mem/windows.go:
--------------------------------------------------------------------------------
1 | // +build windows
2 |
3 | package mem
4 |
5 | import (
6 | "fmt"
7 | "regexp"
8 | "strings"
9 | "syscall"
10 | "unsafe"
11 |
12 | windows "github.com/elastic/go-windows"
13 | xsyscall "golang.org/x/sys/windows"
14 | )
15 |
16 | var (
17 | modkernel32 = xsyscall.NewLazySystemDLL("kernel32.dll")
18 | user32 = xsyscall.NewLazySystemDLL("user32.dll")
19 | procEnumWindows = user32.NewProc("EnumWindows")
20 | procGetWindowTextW = user32.NewProc("GetWindowTextW")
21 | getWindowThreadProcessID = user32.NewProc("GetWindowThreadProcessId")
22 | procVirtualQueryEx = modkernel32.NewProc("VirtualQueryEx")
23 | queryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW")
24 | )
25 |
26 | func enumWindows(enumFunc uintptr, lparam uintptr) (err error) {
27 | r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, uintptr(enumFunc), uintptr(lparam), 0)
28 | if r1 == 0 {
29 | if e1 != 0 {
30 | err = error(e1)
31 | } else {
32 | err = syscall.EINVAL
33 | }
34 | }
35 | return
36 | }
37 |
38 | func getWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (len int32, err error) {
39 | r0, _, e1 := syscall.Syscall(procGetWindowTextW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount))
40 | len = int32(r0)
41 | if len == 0 {
42 | if e1 != 0 {
43 | err = error(e1)
44 | } else {
45 | err = syscall.EINVAL
46 | }
47 | }
48 | return
49 | }
50 |
51 | func GetWindowThreadProcessID(hwnd syscall.Handle) int32 {
52 | var processID int32
53 | getWindowThreadProcessID.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&processID)))
54 | return processID
55 | }
56 |
57 | func FindWindow(title string) (syscall.Handle, error) {
58 | var hwnd syscall.Handle
59 | cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
60 | b := make([]uint16, 200)
61 | _, err := getWindowText(h, &b[0], int32(len(b)))
62 | if err != nil {
63 | // ignore the error
64 | return 1 // continue enumeration
65 | }
66 | if strings.Contains(syscall.UTF16ToString(b), title) {
67 | // note the window
68 | hwnd = h
69 | return 0 // stop enumeration
70 | }
71 | return 1 // continue enumeration
72 | })
73 | enumWindows(cb, 0)
74 | if hwnd == 0 {
75 | return 0, fmt.Errorf("No window with title '%s' found", title)
76 | }
77 | return hwnd, nil
78 | }
79 |
80 | func virtualQueryEx(handle syscall.Handle, off int64) (region, error) {
81 | var reg region
82 | r1, _, e1 := syscall.Syscall6(
83 | procVirtualQueryEx.Addr(),
84 | 4,
85 | uintptr(handle),
86 | uintptr(off),
87 | uintptr(unsafe.Pointer(®)),
88 | uintptr(unsafe.Sizeof(reg)),
89 | 0, 0,
90 | )
91 | if r1 == 0 {
92 | if e1 != 0 {
93 | return region{}, e1
94 | } else {
95 | return region{}, syscall.EINVAL
96 | }
97 | }
98 | return reg, nil
99 | }
100 |
101 | func queryFullProcessImageName(hProcess syscall.Handle) (string, error) {
102 | var buf [syscall.MAX_PATH]uint16
103 | n := uint32(len(buf))
104 | r1, _, e1 := queryFullProcessImageNameW.Call(
105 | uintptr(hProcess),
106 | uintptr(0),
107 | uintptr(unsafe.Pointer(&buf[0])),
108 | uintptr(unsafe.Pointer(&n)))
109 | if r1 == 0 {
110 | if e1 != nil {
111 | return "", e1
112 | } else {
113 | return "", syscall.EINVAL
114 | }
115 | }
116 | return syscall.UTF16ToString(buf[:n]), nil
117 |
118 | }
119 |
120 | func FindProcess(re *regexp.Regexp, blacklistedTitles ...string) ([]Process, error) {
121 | var procs []Process
122 | pids, err := windows.EnumProcesses()
123 | if err != nil {
124 | return nil, err
125 | }
126 | for _, pid := range pids {
127 | handle, err := syscall.OpenProcess(
128 | syscall.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ,
129 | false, pid)
130 | if err != nil {
131 | continue
132 | }
133 | name, err := windows.GetProcessImageFileName(handle)
134 | if err != nil {
135 | syscall.CloseHandle(handle)
136 | continue
137 | }
138 | if re.MatchString(name) {
139 | var bannedHandles []syscall.Handle
140 | for _, title := range blacklistedTitles {
141 | h, _ := FindWindow(title)
142 | if h != 0 {
143 | bannedHandles = append(bannedHandles, h)
144 | }
145 | }
146 | isBanned := false
147 | for _, bHandle := range bannedHandles {
148 | if int32(pid) == GetWindowThreadProcessID(bHandle) {
149 | isBanned = true
150 | break
151 | }
152 | }
153 | if !isBanned {
154 | procs = append(procs, process{pid, handle})
155 | }
156 | }
157 | }
158 | if len(procs) < 1 {
159 | return nil, ErrNoProcess
160 | }
161 | return procs, nil
162 | }
163 |
164 | type process struct {
165 | pid uint32
166 | h syscall.Handle
167 | }
168 |
169 | func (p process) HandleFromTitle() (string, error) {
170 | return queryFullProcessImageName(p.h)
171 | }
172 |
173 | func (p process) ExecutablePath() (string, error) {
174 | return queryFullProcessImageName(p.h)
175 | }
176 |
177 | func (p process) Close() error {
178 | return syscall.CloseHandle(p.h)
179 | }
180 |
181 | func (p process) Pid() int {
182 | return int(p.pid)
183 | }
184 |
185 | func (p process) ReadAt(b []byte, off int64) (n int, err error) {
186 | un, err := windows.ReadProcessMemory(p.h, uintptr(off), b)
187 | logRead(b, int(un), off, err)
188 | return int(un), err
189 | }
190 |
191 | func (p process) Maps() ([]Map, error) {
192 | lastAddr := int64(0)
193 | var maps []Map
194 | for {
195 | reg, err := virtualQueryEx(p.h, lastAddr)
196 | if err != nil {
197 | if lastAddr == 0 {
198 | return nil, err
199 | }
200 | break
201 | }
202 | maps = append(maps, reg)
203 | lastAddr = reg.Start() + reg.Size()
204 | }
205 | return maps, nil
206 | }
207 |
208 | type region struct {
209 | baseAddress uintptr
210 | allocationBase uintptr
211 | allocationProtect int32
212 | regionSize int
213 | state int32
214 | protect int32
215 | type_ int32
216 | }
217 |
218 | func (r region) Start() int64 {
219 | return int64(r.baseAddress)
220 | }
221 |
222 | func (r region) Size() int64 {
223 | return int64(r.regionSize)
224 | }
225 |
--------------------------------------------------------------------------------
/memory/functions.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "log"
7 | "math"
8 | "path/filepath"
9 | "runtime"
10 | "strings"
11 | "time"
12 |
13 | "github.com/spf13/cast"
14 |
15 | "github.com/k0kubun/pp"
16 | "github.com/l3lackShark/gosumemory/mem"
17 | )
18 |
19 | func modsResolver(xor uint32) string {
20 | return Mods(xor).String()
21 | }
22 |
23 | // UpdateTime Intervall between value updates
24 | var UpdateTime int
25 |
26 | // UnderWine?
27 | var UnderWine bool
28 |
29 | // MemCycle test
30 | var MemCycle bool
31 | var isTournamentMode bool
32 | var tourneyProcs []mem.Process
33 | var tourneyErr error
34 |
35 | // Were we in the Result Screen?
36 | var dirtyResults bool = false
37 |
38 | // var proc, procerr = kiwi.GetProcessByFileName("osu!.exe")
39 | var leaderStart int32
40 |
41 | // SongsFolderPath is full path to osu! Songs. Gets set automatically on Windows (through memory)
42 | var SongsFolderPath string
43 |
44 | var allProcs []mem.Process
45 | var process mem.Process
46 | var procerr error
47 | var tempRetries int32
48 |
49 | // Init the whole thing and get osu! memory values to start working with it.
50 | func Init() {
51 | if UnderWine == true || runtime.GOOS != "windows" { //Arrays start at 0xC in Linux for some reason, has to be wine specific
52 | leaderStart = 0xC
53 | } else {
54 | leaderStart = 0x8
55 | }
56 |
57 | allProcs, procerr = mem.FindProcess(osuProcessRegex, "osu!lazer", "osu!framework")
58 | for {
59 | start := time.Now()
60 | if procerr != nil {
61 | DynamicAddresses.IsReady = false
62 | for procerr != nil {
63 | allProcs, procerr = mem.FindProcess(osuProcessRegex, "osu!lazer", "osu!framework")
64 | log.Println("It seems that we lost the process, retrying! ERROR:", procerr)
65 | time.Sleep(1 * time.Second)
66 | }
67 | err := initBase()
68 | for err != nil {
69 | log.Println("Failure mid getting offsets, retrying! ERROR:", err)
70 | err = initBase()
71 | time.Sleep(1 * time.Second)
72 | }
73 | }
74 | if DynamicAddresses.IsReady == false {
75 | err := initBase()
76 | for err != nil {
77 | log.Println("Failure mid getting offsets, retrying! ERROR:", err)
78 | err = initBase()
79 | time.Sleep(1 * time.Second)
80 | }
81 | } else {
82 | err := mem.Read(process,
83 | &patterns.PreSongSelectAddresses,
84 | &menuData.PreSongSelectData)
85 | if err != nil {
86 | DynamicAddresses.IsReady = false
87 | log.Println("It appears that we lost the process, retrying! ERROR:", err)
88 | continue
89 | }
90 | MenuData.OsuStatus = menuData.Status
91 |
92 | mem.Read(process, &patterns, &alwaysData)
93 | MenuData.ChatChecker = alwaysData.ChatStatus
94 | MenuData.Bm.Time.PlayTime = alwaysData.PlayTime
95 | SettingsData.Folders.Skin = alwaysData.SkinFolder
96 |
97 | SettingsData.ShowInterface = cast.ToBool(int(alwaysData.ShowInterface))
98 | switch menuData.Status {
99 | case 0:
100 | err = bmUpdateData()
101 | if err != nil {
102 | pp.Println(err)
103 | }
104 | mem.Read(process, &patterns, &mainMenuData)
105 | MenuData.MainMenuValues.BassDensity = calculateBassDensity(mainMenuData.AudioVelocityBase, &process)
106 | case 2:
107 | if MenuData.Bm.Time.PlayTime < 150 || menuData.Path == "" { //To catch up with the F2-->Enter
108 | err := bmUpdateData()
109 | if err != nil {
110 | pp.Println(err)
111 | }
112 | }
113 | if gameplayData.Retries > tempRetries || dirtyResults {
114 | tempRetries = gameplayData.Retries
115 | GameplayData = GameplayValues{}
116 | gameplayData = gameplayD{}
117 | dirtyResults = false
118 | }
119 | getGamplayData()
120 | case 1:
121 | err = bmUpdateData()
122 | if err != nil {
123 | pp.Println(err)
124 | }
125 | case 7:
126 | err = bmUpdateData()
127 | if err != nil {
128 | pp.Println(err)
129 | }
130 | mem.Read(process, &patterns, &resultsScreenData)
131 |
132 | if resultsScreenData.ModsXor1 == 0 { //not initialized yet
133 | for i := 0; i < 10; i++ {
134 | mem.Read(process, &patterns, &resultsScreenData)
135 | if resultsScreenData.ModsXor1 != 0 {
136 | break
137 | }
138 | time.Sleep(50 * time.Millisecond)
139 | }
140 | }
141 | ResultsScreenData.H300 = resultsScreenData.Hit300
142 | ResultsScreenData.H100 = resultsScreenData.Hit100
143 | ResultsScreenData.H50 = resultsScreenData.Hit50
144 | ResultsScreenData.H0 = resultsScreenData.HitMiss
145 | ResultsScreenData.MaxCombo = resultsScreenData.MaxCombo
146 | ResultsScreenData.Name = resultsScreenData.PlayerName
147 | ResultsScreenData.Score = resultsScreenData.Score
148 | ResultsScreenData.HGeki = resultsScreenData.HitGeki
149 | ResultsScreenData.HKatu = resultsScreenData.HitKatu
150 |
151 | ResultsScreenData.Mods.AppliedMods = resultsScreenData.ModsXor1 ^ resultsScreenData.ModsXor2
152 | if ResultsScreenData.Mods.AppliedMods == 0 {
153 | ResultsScreenData.Mods.PpMods = "NM"
154 | } else {
155 | ResultsScreenData.Mods.PpMods = Mods(resultsScreenData.ModsXor1 ^ resultsScreenData.ModsXor2).String()
156 | }
157 | if !dirtyResults {
158 | dirtyResults = true
159 | }
160 | default:
161 | tempRetries = -1
162 | GameplayData = GameplayValues{}
163 | gameplayData = gameplayD{}
164 | err = bmUpdateData()
165 | if err != nil {
166 | pp.Println(err)
167 | }
168 |
169 | }
170 | }
171 | if isTournamentMode {
172 |
173 | if err := getTourneyIPC(); err != nil {
174 | DynamicAddresses.IsReady = false
175 | log.Println("It appears that we lost the precess, retrying", err)
176 | continue
177 | }
178 | }
179 | if menuData.Status != 7 {
180 | ResultsScreenData = ResultsScreenValues{}
181 | }
182 | elapsed := time.Since(start)
183 | if MemCycle {
184 | log.Printf("Cycle took %s", elapsed)
185 | }
186 | time.Sleep(time.Duration(UpdateTime-int(elapsed.Milliseconds())) * time.Millisecond)
187 |
188 | }
189 |
190 | }
191 |
192 | var tempBeatmapString string
193 | var tempGameMode int32 = 5
194 |
195 | func bmUpdateData() error {
196 | mem.Read(process, &patterns, &menuData)
197 |
198 | bmString := menuData.Path
199 | if (strings.HasSuffix(bmString, ".osu") && tempBeatmapString != bmString) || (strings.HasSuffix(bmString, ".osu") && tempGameMode != menuData.MenuGameMode) { //On map/mode change
200 | for i := 0; i < 50; i++ {
201 | if menuData.BackgroundFilename != "" {
202 | break
203 | }
204 | time.Sleep(25 * time.Millisecond)
205 | mem.Read(process, &patterns, &menuData)
206 | }
207 | tempGameMode = menuData.MenuGameMode
208 | tempBeatmapString = bmString
209 | MenuData.Bm.BeatmapID = menuData.MapID
210 | MenuData.Bm.BeatmapSetID = menuData.SetID
211 | MenuData.Bm.Stats.MemoryAR = menuData.AR
212 | MenuData.Bm.Stats.MemoryCS = menuData.CS
213 | MenuData.Bm.Stats.MemoryHP = menuData.HP
214 | MenuData.Bm.Stats.MemoryOD = menuData.OD
215 | MenuData.Bm.Stats.TotalHitObjects = menuData.ObjectCount
216 | MenuData.Bm.Metadata.Artist = menuData.Artist
217 | MenuData.Bm.Metadata.ArtistOriginal = menuData.ArtistOriginal
218 | MenuData.Bm.Metadata.Title = menuData.Title
219 | MenuData.Bm.Metadata.TitleOriginal = menuData.TitleOriginal
220 | MenuData.Bm.Metadata.Mapper = menuData.Creator
221 | MenuData.Bm.Metadata.Version = menuData.Difficulty
222 | MenuData.GameMode = menuData.MenuGameMode
223 | MenuData.Bm.RandkedStatus = menuData.RankedStatus
224 | MenuData.Bm.BeatmapMD5 = menuData.MD5
225 | MenuData.Bm.Path = path{
226 | AudioPath: menuData.AudioFilename,
227 | BGPath: menuData.BackgroundFilename,
228 | BeatmapOsuFileString: menuData.Path,
229 | BeatmapFolderString: menuData.Folder,
230 | FullMP3Path: filepath.Join(SongsFolderPath, menuData.Folder, menuData.AudioFilename),
231 | FullDotOsu: filepath.Join(SongsFolderPath, menuData.Folder, bmString),
232 | InnerBGPath: filepath.Join(menuData.Folder, menuData.BackgroundFilename),
233 | }
234 | }
235 | if menuData.Status != 7 && menuData.Status != 14 {
236 | if alwaysData.MenuMods == 0 {
237 | MenuData.Mods.PpMods = "NM"
238 | MenuData.Mods.AppliedMods = int32(alwaysData.MenuMods)
239 | } else {
240 | MenuData.Mods.AppliedMods = int32(alwaysData.MenuMods)
241 | MenuData.Mods.PpMods = Mods(alwaysData.MenuMods).String()
242 | }
243 | }
244 |
245 | return nil
246 | }
247 | func getGamplayData() {
248 | err := mem.Read(process, &patterns, &gameplayData)
249 | if err != nil && !strings.Contains(err.Error(), "LeaderBoard") && !strings.Contains(err.Error(), "KeyOverlay") { //those could be disabled
250 | return //struct not initialized yet
251 | }
252 | //GameplayData.BitwiseKeypress = gameplayData.BitwiseKeypress
253 | GameplayData.Combo.Current = gameplayData.Combo
254 | GameplayData.Combo.Max = gameplayData.MaxCombo
255 | GameplayData.GameMode = gameplayData.Mode
256 | GameplayData.Hits.H100 = gameplayData.Hit100
257 | GameplayData.Hits.HKatu = gameplayData.HitKatu
258 | GameplayData.Hits.H300 = gameplayData.Hit300
259 | GameplayData.Hits.HGeki = gameplayData.HitGeki
260 | GameplayData.Hits.H50 = gameplayData.Hit50
261 | GameplayData.Hits.H0 = gameplayData.HitMiss
262 | if GameplayData.Combo.Temp > GameplayData.Combo.Max {
263 | GameplayData.Combo.Temp = 0
264 | }
265 | if GameplayData.Combo.Current < GameplayData.Combo.Temp && GameplayData.Hits.H0Temp == GameplayData.Hits.H0 {
266 | GameplayData.Hits.HSB++
267 | }
268 | GameplayData.Hits.H0Temp = GameplayData.Hits.H0
269 | GameplayData.Combo.Temp = GameplayData.Combo.Current
270 | GameplayData.Accuracy = cast.ToFloat64(fmt.Sprintf("%.2f", gameplayData.Accuracy))
271 | GameplayData.Hp.Normal = gameplayData.PlayerHP
272 | GameplayData.Hp.Smooth = gameplayData.PlayerHPSmooth
273 | GameplayData.Name = gameplayData.PlayerName
274 | MenuData.Mods.AppliedMods = gameplayData.ModsXor1 ^ gameplayData.ModsXor2
275 | if MenuData.Mods.AppliedMods == 0 {
276 | MenuData.Mods.PpMods = "NM"
277 | } else {
278 | MenuData.Mods.PpMods = Mods(gameplayData.ModsXor1 ^ gameplayData.ModsXor2).String()
279 | }
280 | // if strings.Contains(MenuData.Mods.PpMods, "V2") {
281 | GameplayData.Score = gameplayData.ScoreV2
282 | // } else {
283 | // GameplayData.Score = gameplayData.Score
284 | // }
285 | if GameplayData.Combo.Max > 0 {
286 | GameplayData.Hits.HitErrorArray = gameplayData.HitErrors
287 | baseUR, _ := calculateUR(GameplayData.Hits.HitErrorArray)
288 | if strings.Contains(MenuData.Mods.PpMods, "DT") || strings.Contains(MenuData.Mods.PpMods, "NC") {
289 | GameplayData.Hits.UnstableRate = baseUR / 1.5
290 | } else if strings.Contains(MenuData.Mods.PpMods, "HT") {
291 | GameplayData.Hits.UnstableRate = baseUR * 1.33
292 | } else {
293 | GameplayData.Hits.UnstableRate = baseUR
294 | }
295 | }
296 | getLeaderboard()
297 | getKeyOveraly()
298 | }
299 |
300 | func getLeaderboard() {
301 | var board leaderboard
302 | if gameplayData.LeaderBoard == 0 {
303 | board.DoesLeaderBoardExists = false
304 | GameplayData.Leaderboard = board
305 | return
306 | }
307 | board.DoesLeaderBoardExists = true
308 | ourPlayerStruct, _ := mem.ReadUint32(process, int64(gameplayData.LeaderBoard)+0x10, 0)
309 | board.OurPlayer, board.IsLeaderBoardVisible = readLeaderPlayerStruct(int64(ourPlayerStruct))
310 | board.OurPlayer.Mods = MenuData.Mods.PpMods //ourplayer mods is sometimes delayed so better default to PlayContainer Here
311 | playersArray, _ := mem.ReadUint32(process, int64(gameplayData.LeaderBoard)+0x4)
312 | amOfSlots, _ := mem.ReadInt32(process, int64(playersArray+0xC))
313 | if amOfSlots < 1 || amOfSlots > 64 {
314 | return
315 | }
316 | items, _ := mem.ReadInt32(process, int64(playersArray+0x4))
317 | board.Slots = make([]leaderPlayer, amOfSlots)
318 | for i, j := leaderStart, 0; j < int(amOfSlots); i, j = i+0x4, j+1 {
319 | slot, _ := mem.ReadUint32(process, int64(items), int64(i))
320 | board.Slots[j], _ = readLeaderPlayerStruct(int64(slot))
321 | }
322 | GameplayData.Leaderboard = board
323 | }
324 |
325 | type ManiaStars struct {
326 | NoMod float64
327 | DT float64
328 | HT float64
329 | }
330 |
331 | func ReadManiaStars() (ManiaStars, error) {
332 | addresses := struct{ Base int64 }{int64(menuData.StarRatingStruct)} //Beatmap + 0x88
333 | var entries struct {
334 | Data uint32 `mem:"[Base + 0x14] + 0x8"`
335 | }
336 | err := mem.Read(process, &addresses, &entries)
337 | if err != nil || entries.Data == 0 {
338 | pp.Println("[MEMORY] Could not find star rating for this map or converts not supported yet")
339 | return ManiaStars{}, nil
340 | }
341 | starRating := struct{ Base int64 }{int64(entries.Data)}
342 | var stars struct {
343 | NoMod float64 `mem:"Base + 0x18"`
344 | DT float64 `mem:"Base + 0x30"`
345 | HT float64 `mem:"Base + 0x48"`
346 | }
347 | err = mem.Read(process, &starRating, &stars)
348 | if err != nil {
349 | return ManiaStars{}, errors.New("[MEMORY] Empty star rating (internal)")
350 | }
351 | return ManiaStars{stars.NoMod, stars.DT, stars.HT}, nil
352 | }
353 |
354 | func readLeaderPlayerStruct(base int64) (leaderPlayer, bool) {
355 | addresses := struct{ Base int64 }{base}
356 | var player struct {
357 | Name string `mem:"[Base + 0x8]"`
358 | Score int32 `mem:"Base + 0x30"`
359 | Combo int16 `mem:"[Base + 0x20] + 0x94"`
360 | MaxCombo int16 `mem:"[Base + 0x20] + 0x68"`
361 | ModsXor1 uint32 `mem:"[[Base + 0x20] + 0x1C] + 0x8"`
362 | ModsXor2 uint32 `mem:"[[Base + 0x20] + 0x1C] + 0xC"`
363 | H300 int16 `mem:"[Base + 0x20] + 0x8A"`
364 | H100 int16 `mem:"[Base + 0x20] + 0x88"`
365 | H50 int16 `mem:"[Base + 0x20] + 0x8C"`
366 | H0 int16 `mem:"[Base + 0x20] + 0x92"`
367 | Team int32 `mem:"Base + 0x40"`
368 | Position int32 `mem:"Base + 0x2C"`
369 | IsPassing int8 `mem:"Base + 0x4B"`
370 | IsLeaderboardVisible int8 `mem:"[Base + 0x24] + 0x20"`
371 | }
372 | mem.Read(process, &addresses, &player)
373 | mods := modsResolver(player.ModsXor1 ^ player.ModsXor2)
374 | if mods == "" {
375 | mods = "NM"
376 | }
377 | return leaderPlayer{
378 | Name: player.Name,
379 | Score: player.Score,
380 | Combo: player.Combo,
381 | MaxCombo: player.MaxCombo,
382 | Mods: mods,
383 | H300: player.H300,
384 | H100: player.H100,
385 | H50: player.H50,
386 | H0: player.H0,
387 | Team: player.Team,
388 | Position: player.Position,
389 | IsPassing: player.IsPassing,
390 | }, cast.ToBool(int(player.IsLeaderboardVisible))
391 | }
392 |
393 | func calculateUR(HitErrorArray []int32) (float64, error) {
394 | if len(HitErrorArray) < 1 {
395 | return 0, errors.New("empty hit error array")
396 | }
397 | var totalAll float32 //double
398 | for _, hit := range HitErrorArray {
399 | totalAll += float32(hit)
400 | }
401 | var average = totalAll / float32(len(HitErrorArray))
402 | var variance float64 = 0
403 | for _, hit := range HitErrorArray {
404 | variance += math.Pow(float64(hit)-float64(average), 2)
405 | }
406 | variance = variance / float64(len(HitErrorArray))
407 | return math.Sqrt(variance) * 10, nil
408 |
409 | }
410 |
411 | var currentAudioVelocity float64
412 |
413 | func calculateBassDensity(base uint32, proc *mem.Process) float64 {
414 | var bass float32
415 | for i, j := leaderStart, 0; j < 40; i, j = i+0x4, j+1 {
416 | value, err := mem.ReadFloat32(*proc, int64(base), int64(i))
417 | if err != nil {
418 | return 0.5
419 | }
420 | bass += 2 * value * (40 - float32(j)) / 40
421 | }
422 | if math.IsNaN(currentAudioVelocity) || math.IsNaN(float64(bass)) {
423 | currentAudioVelocity = 0
424 | return 0.5
425 | }
426 | currentAudioVelocity = math.Max(currentAudioVelocity, math.Min(float64(bass)*1.5, 6))
427 | currentAudioVelocity *= 0.95
428 | return (1 + currentAudioVelocity) * 0.5
429 |
430 | }
431 |
432 | func getKeyOveraly() {
433 | addresses := struct{ Base int64 }{int64(gameplayData.KeyOverlayArrayAddr)}
434 | var entries struct {
435 | K1Pressed int8 `mem:"[Base + 0x8] + 0x1C"` //Pressed usually works with <20 update rate. It's recommended to create a buffer and predict presses by count to save CPU overhead
436 | K1Count int32 `mem:"[Base + 0x8] + 0x14"`
437 | K2Pressed int8 `mem:"[Base + 0xC] + 0x1C"`
438 | K2Count int32 `mem:"[Base + 0xC] + 0x14"`
439 | M1Pressed int8 `mem:"[Base + 0x10] + 0x1C"`
440 | M1Count int32 `mem:"[Base + 0x10] + 0x14"`
441 | M2Pressed int8 `mem:"[Base + 0x14] + 0x1C"`
442 | M2Count int32 `mem:"[Base + 0x14] + 0x14"`
443 | }
444 | err := mem.Read(process, &addresses, &entries)
445 | if err != nil {
446 | return
447 | }
448 |
449 | var out keyOverlay
450 |
451 | out.K1.IsPressed = cast.ToBool(int(entries.K1Pressed))
452 | out.K1.Count = entries.K1Count
453 | out.K2.IsPressed = cast.ToBool(int(entries.K2Pressed))
454 | out.K2.Count = entries.K2Count
455 | out.M1.IsPressed = cast.ToBool(int(entries.M1Pressed))
456 | out.M1.Count = entries.M1Count
457 | out.M2.IsPressed = cast.ToBool(int(entries.M2Pressed))
458 | out.M2.Count = entries.M2Count
459 | GameplayData.KeyOverlay = out //needs complete rewrite in 1.4.0
460 | }
461 |
--------------------------------------------------------------------------------
/memory/init.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "log"
7 | "path/filepath"
8 | "regexp"
9 | "runtime"
10 | "strings"
11 | "time"
12 |
13 | "github.com/l3lackShark/gosumemory/config"
14 | "github.com/l3lackShark/gosumemory/injctr"
15 | "github.com/l3lackShark/gosumemory/mem"
16 | "github.com/spf13/cast"
17 | )
18 |
19 | var osuProcessRegex = regexp.MustCompile(`.*osu!\.exe.*`)
20 | var patterns staticAddresses
21 | var tourneyPatterns []staticAddresses
22 | var tourneySpecificPatterns []tourneyStaticAddresses
23 | var tourneyMenuData []menuD
24 | var tourneyManagerData tourneyD
25 | var tourneyGameplayData []gameplayD
26 | var tourneyAlwaysData []allTimesD
27 |
28 | var menuData menuD
29 | var songsFolderData songsFolderD
30 | var gameplayData gameplayD
31 | var alwaysData allTimesD
32 | var mainMenuData mainMenuD
33 | var resultsScreenData resultsScreenD
34 |
35 | func resolveSongsFolder() (string, error) {
36 | var err error
37 | osuExecutablePath, err := process.ExecutablePath()
38 | if err != nil {
39 | return "", err
40 | }
41 | if !strings.Contains(osuExecutablePath, `:\`) {
42 | log.Println("Automatic executable path finder has failed. Please try again or manually specify it. (see --help) GOT: ", osuExecutablePath)
43 | time.Sleep(5 * time.Second)
44 | return "", errors.New("osu! executable was not found")
45 | }
46 | rootFolder := strings.TrimSuffix(osuExecutablePath, "osu!.exe")
47 | songsFolder := filepath.Join(rootFolder, "Songs")
48 | if songsFolderData.SongsFolder == "Songs" || songsFolderData.SongsFolder == "CompatibilityContext" { //dirty hack to fix old stable offset
49 | return songsFolder, nil
50 | }
51 | return songsFolderData.SongsFolder, nil
52 | }
53 |
54 | func initBase() error {
55 | var err error
56 | isTournamentMode = false
57 | allProcs, err = mem.FindProcess(osuProcessRegex, "osu!lazer", "osu!framework")
58 | if err != nil {
59 | return err
60 | }
61 | process = allProcs[0]
62 |
63 | err = mem.ResolvePatterns(process, &patterns.PreSongSelectAddresses)
64 | if err != nil {
65 | return err
66 | }
67 |
68 | err = mem.Read(process,
69 | &patterns.PreSongSelectAddresses,
70 | &menuData.PreSongSelectData)
71 | if err != nil {
72 | return err
73 | }
74 | fmt.Println("[MEMORY] Got osu!status addr...")
75 |
76 | if runtime.GOOS == "windows" && SongsFolderPath == "auto" {
77 | err = mem.Read(process,
78 | &patterns.PreSongSelectAddresses,
79 | &songsFolderData)
80 | if err != nil {
81 | return err
82 | }
83 | SongsFolderPath, err = resolveSongsFolder()
84 | if err != nil {
85 | log.Fatalln(err)
86 | }
87 | }
88 | fmt.Println("[MEMORY] Songs folder:", SongsFolderPath)
89 | pepath, err := process.ExecutablePath()
90 | if err != nil {
91 | panic(err)
92 | }
93 | SettingsData.Folders.Game = filepath.Dir(pepath)
94 |
95 | if menuData.PreSongSelectData.Status == 22 || len(allProcs) > 1 {
96 | fmt.Println("[MEMORY] Operating in tournament mode!")
97 | tourneyProcs, tourneyErr = resolveTourneyClients(allProcs)
98 | if tourneyErr != nil {
99 | return err
100 | }
101 | isTournamentMode = true
102 | tourneyPatterns = make([]staticAddresses, len(tourneyProcs))
103 | tourneySpecificPatterns = make([]tourneyStaticAddresses, len(tourneyProcs))
104 | TourneyData.IPCClients = make([]ipcClient, len(tourneyProcs))
105 | tourneyMenuData = make([]menuD, len(tourneyProcs))
106 | tourneyGameplayData = make([]gameplayD, len(tourneyProcs))
107 | tourneyAlwaysData = make([]allTimesD, len(tourneyProcs))
108 | for i, proc := range tourneyProcs {
109 | err = mem.ResolvePatterns(proc, &tourneyPatterns[i].PreSongSelectAddresses)
110 | if err != nil {
111 | return err
112 | }
113 | err = mem.Read(proc,
114 | &tourneyPatterns[i].PreSongSelectAddresses,
115 | &tourneyMenuData[i].PreSongSelectData)
116 | if err != nil {
117 | return err
118 | }
119 | fmt.Println(fmt.Sprintf("[MEMORY] Got osu!status addr for client #%d...", i))
120 | fmt.Println(fmt.Sprintf("[MEMORY] Resolving patterns for client #%d...", i))
121 | err = mem.ResolvePatterns(proc, &tourneyPatterns[i])
122 | if err != nil {
123 | return err
124 | }
125 | err = mem.ResolvePatterns(proc, &tourneySpecificPatterns[i])
126 | if err != nil {
127 | return err
128 | }
129 | }
130 |
131 | }
132 |
133 | fmt.Println("[MEMORY] Resolving patterns...")
134 | err = mem.ResolvePatterns(process, &patterns)
135 | if err != nil {
136 | return err
137 | }
138 |
139 | SettingsData.Folders.Songs = SongsFolderPath
140 |
141 | fmt.Println("[MEMORY] Got all patterns...")
142 | fmt.Println("WARNING: Mania pp calcualtion is experimental and only works if you choose mania gamemode in the SongSelect!")
143 | fmt.Println(fmt.Sprintf("Initialization complete, you can now visit http://%s or add it as a browser source in OBS", config.Config["serverip"]))
144 | DynamicAddresses.IsReady = true
145 | if cast.ToBool(config.Config["enabled"]) {
146 | err = injctr.Injct(process.Pid())
147 | if err != nil {
148 | log.Printf("Failed to inject into osu's process, in game overlay will be unavailabe. %e\n", err)
149 | }
150 | } else {
151 | fmt.Println("[MEMORY] In-Game overlay is disabled, but could be enabled in config.ini!")
152 | }
153 |
154 | return nil
155 | }
156 |
--------------------------------------------------------------------------------
/memory/mods.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | // Mods represents zero or more mods of an osu! score.
8 | type Mods int
9 |
10 | // Mods constants.
11 | // Names are taken from official osu! documentation.
12 | const (
13 | ModsNoFail Mods = 1 << iota
14 | ModsEasy
15 | ModsTouchDevice
16 | ModsHidden
17 | ModsHardRock
18 | ModsSuddenDeath
19 | ModsDoubleTime
20 | ModsRelax
21 | ModsHalfTime
22 | ModsNightcore
23 | ModsFlashlight
24 | ModsAutoplay
25 | ModsSpunOut
26 | ModsRelax2 // Autopilot
27 | ModsPerfect
28 | ModsKey4
29 | ModsKey5
30 | ModsKey6
31 | ModsKey7
32 | ModsKey8
33 | ModsFadeIn
34 | ModsRandom
35 | ModsCinema
36 | ModsTargetPractice
37 | ModsKey9
38 | ModsKeyCoop
39 | ModsKey1
40 | ModsKey3
41 | ModsKey2
42 | ModsScoreV2
43 | ModsMirror
44 |
45 | ModsKeyMod Mods = ModsKey1 | ModsKey2 | ModsKey3 | ModsKey4 | ModsKey5 | ModsKey6 | ModsKey7 | ModsKey8 | ModsKey9
46 | ModsFreeModAllowed Mods = ModsNoFail | ModsEasy | ModsHidden | ModsHardRock | ModsSuddenDeath | ModsFlashlight | ModsFadeIn | ModsRelax | ModsSpunOut | ModsKeyMod
47 | ModsScoreIncrease Mods = ModsHidden | ModsHardRock | ModsDoubleTime | ModsFlashlight | ModsFadeIn
48 |
49 | // modsSpeedChanging Mods = ModsDT | ModsHT | ModsNC
50 | // modsMapChanging Mods = ModsHR | ModsEZ | modsSpeedChanging
51 | )
52 |
53 | // Convenience aliases.
54 | const (
55 | NF Mods = ModsNoFail
56 | EZ Mods = ModsEasy
57 | TD Mods = ModsTouchDevice
58 | HD Mods = ModsHidden
59 | HR Mods = ModsHardRock
60 | SD Mods = ModsSuddenDeath
61 | DT Mods = ModsDoubleTime
62 | RX Mods = ModsRelax
63 | HT Mods = ModsHalfTime
64 | NC Mods = ModsNightcore
65 | FL Mods = ModsFlashlight
66 | SO Mods = ModsSpunOut
67 | PF Mods = ModsPerfect
68 |
69 | MR Mods = ModsMirror
70 | V2 Mods = ModsScoreV2
71 | AT Mods = ModsAutoplay
72 | AP Mods = ModsRelax2
73 | FI Mods = ModsFadeIn
74 | RD Mods = ModsRandom
75 | CN Mods = ModsCinema
76 | TP Mods = ModsTargetPractice
77 | CO Mods = ModsKeyCoop
78 |
79 | K1 Mods = ModsKey1
80 | K2 Mods = ModsKey2
81 | K3 Mods = ModsKey3
82 | K4 Mods = ModsKey4
83 | K5 Mods = ModsKey5
84 | K6 Mods = ModsKey6
85 | K7 Mods = ModsKey7
86 | K8 Mods = ModsKey8
87 | K9 Mods = ModsKey9
88 | )
89 |
90 | // This is a slice instead of a map because order matters
91 | var modStrings = []struct {
92 | mod Mods
93 | str string
94 | }{
95 | {NF, "NF"},
96 | {EZ, "EZ"},
97 | {TD, "TD"},
98 | {HD, "HD"},
99 | {HR, "HR"},
100 | {SD, "SD"},
101 | {DT, "DT"},
102 | {RX, "RX"},
103 | {HT, "HT"},
104 | {NC, "NC"},
105 | {FL, "FL"},
106 | {AT, "AT"},
107 | {SO, "SO"},
108 | {AP, "AP"},
109 | {PF, "PF"},
110 | {K4, "4K"},
111 | {K5, "5K"},
112 | {K6, "6K"},
113 | {K7, "7K"},
114 | {K8, "8K"},
115 | {FI, "FI"},
116 | {RD, "RD"},
117 | {CN, "CN"},
118 | {TP, "TP"},
119 | {K9, "9K"},
120 | {CO, "CO"},
121 | {K1, "1K"},
122 | {K3, "3K"},
123 | {K2, "2K"},
124 | {V2, "V2"},
125 | {MR, "MR"},
126 | }
127 |
128 | func (m Mods) String() string {
129 | if m == 0 {
130 | return ""
131 | }
132 |
133 | var s strings.Builder
134 |
135 | for _, v := range modStrings {
136 | if m&v.mod > 0 {
137 | s.WriteString(v.str)
138 | }
139 | }
140 |
141 | out := s.String()
142 | if strings.Contains(out, "NC") { // ex. DTNCATCN (4196928)
143 | out = strings.Replace(out, "DT", "", 1)
144 | }
145 | if strings.Contains(out, "CN") {
146 | out = strings.Replace(out, "AT", "", 1)
147 | }
148 |
149 | return out
150 | }
151 |
--------------------------------------------------------------------------------
/memory/read.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | type PreSongSelectAddresses struct {
4 | Status int64 `sig:"48 83 F8 04 73 1E"`
5 | SettingsClass int64 `sig:"83 E0 20 85 C0 7E 2F"`
6 | }
7 |
8 | type songsFolderD struct {
9 | SongsFolder string `mem:"[[Settings + 0xB8] + 0x4]"`
10 | }
11 |
12 | type PreSongSelectData struct {
13 | Status uint32 `mem:"[Status - 0x4]"`
14 | }
15 |
16 | type staticAddresses struct {
17 | PreSongSelectAddresses
18 | Base int64 `sig:"F8 01 74 04 83 65"`
19 | MenuMods int64 `sig:"C8 FF ?? ?? ?? ?? ?? 81 0D ?? ?? ?? ?? 00 08 00 00"`
20 | PlayTime int64 `sig:"5E 5F 5D C3 A1 ?? ?? ?? ?? 89 ?? 04"`
21 | ChatChecker int64 `sig:"0A D7 23 3C 00 00 ?? 01"`
22 | SkinData int64 `sig:"75 21 8B 1D"`
23 | Rulesets int64 `sig:"7D 15 A1 ?? ?? ?? ?? 85 C0"`
24 | ChatArea int64 `sig:"33 47 9D FF 5B 7F FF FF"`
25 | }
26 |
27 | type tourneyStaticAddresses struct {
28 | UserInfo int64 `sig:"52 30 8B C8 E8 ?? ?? ?? ?? 8B C8 8D"`
29 | }
30 |
31 | type tourneyD struct {
32 | IPCState int32 `mem:"Ruleset + 0x54"`
33 | LeftStars int32 `mem:"[Ruleset + 0x1C] + 0x2C"`
34 | RightStars int32 `mem:"[Ruleset + 0x20] + 0x2C"`
35 | BO int32 `mem:"[Ruleset + 0x20] + 0x30"`
36 | StarsVisible int8 `mem:"[Ruleset + 0x20] + 0x38"`
37 | ScoreVisible int8 `mem:"[Ruleset + 0x20] + 0x39"`
38 | TeamOneName string `mem:"[[[Ruleset + 0x1C] + 0x20] + 0x144]"`
39 | TeamTwoName string `mem:"[[[Ruleset + 0x20] + 0x20] + 0x144]"`
40 | TeamOneScore int32 `mem:"[Ruleset + 0x1C] + 0x28"`
41 | TeamTwoScore int32 `mem:"[Ruleset + 0x20] + 0x28"`
42 | IPCBaseAddr uint32 `mem:"[[Ruleset + 0x34] + 0x4] + 0x4"`
43 | }
44 |
45 | type resultsScreenD struct {
46 | PlayerName string `mem:"[[Ruleset + 0x38] + 0x28]"`
47 | ModsXor1 int32 `mem:"[[Ruleset + 0x38] + 0x1C] + 0xC"`
48 | ModsXor2 int32 `mem:"[[Ruleset + 0x38] + 0x1C] + 0x8"`
49 | Mode int32 `mem:"[Ruleset + 0x38] + 0x64"`
50 | MaxCombo int16 `mem:"[Ruleset + 0x38] + 0x68"`
51 | Score int32 `mem:"[Ruleset + 0x38] + 0x78"`
52 | Hit100 int16 `mem:"[Ruleset + 0x38] + 0x88"`
53 | Hit300 int16 `mem:"[Ruleset + 0x38] + 0x8A"`
54 | Hit50 int16 `mem:"[Ruleset + 0x38] + 0x8C"`
55 | HitGeki int16 `mem:"[Ruleset + 0x38] + 0x8E"`
56 | HitKatu int16 `mem:"[Ruleset + 0x38] + 0x90"`
57 | HitMiss int16 `mem:"[Ruleset + 0x38] + 0x92"`
58 | Accuracy float64 `mem:"[Ruleset + 0x48] + 0xC"`
59 | }
60 |
61 | func (staticAddresses) Ruleset() string {
62 | return "[[Rulesets - 0xB] + 0x4]"
63 | }
64 |
65 | func (staticAddresses) Beatmap() string {
66 | return "[Base - 0xC]"
67 | }
68 |
69 | func (PreSongSelectAddresses) Settings() string {
70 | return "[SettingsClass + 0x8]"
71 | }
72 |
73 | func (staticAddresses) PlayContainer() string {
74 | return "[[[[PlayContainerBase + 0x7] + 0x4] + 0xC4] + 0x4]"
75 | }
76 |
77 | func (staticAddresses) Leaderboard() string {
78 | return "[[[LeaderboardBase+0x1] + 0x4] + 0x7C] + 0x24"
79 | }
80 |
81 | type menuD struct {
82 | PreSongSelectData
83 | //SearchText string `mem:"[Ruleset + 0xCC]"`
84 | //GroupingType int32 `mem:"Ruleset + 0x104"`
85 | MenuGameMode int32 `mem:"[Base - 0x33]"`
86 | Plays int32 `mem:"[Base - 0x33] + 0xC"`
87 | Artist string `mem:"[[Beatmap] + 0x18]"`
88 | ArtistOriginal string `mem:"[[Beatmap] + 0x1C]"`
89 | Title string `mem:"[[Beatmap] + 0x24]"`
90 | TitleOriginal string `mem:"[[Beatmap] + 0x28]"`
91 | AR float32 `mem:"[Beatmap] + 0x2C"`
92 | CS float32 `mem:"[Beatmap] + 0x30"`
93 | HP float32 `mem:"[Beatmap] + 0x34"`
94 | OD float32 `mem:"[Beatmap] + 0x38"`
95 | StarRatingStruct uint32 `mem:"[Beatmap] + 0x8C"`
96 | AudioFilename string `mem:"[[Beatmap] + 0x64]"`
97 | BackgroundFilename string `mem:"[[Beatmap] + 0x68]"`
98 | Folder string `mem:"[[Beatmap] + 0x78]"`
99 | Creator string `mem:"[[Beatmap] + 0x7C]"`
100 | Name string `mem:"[[Beatmap] + 0x80]"`
101 | Path string `mem:"[[Beatmap] + 0x90]"`
102 | Difficulty string `mem:"[[Beatmap] + 0xAC]"`
103 | MapID int32 `mem:"[Beatmap] + 0xC8"`
104 | SetID int32 `mem:"[Beatmap] + 0xCC"`
105 | RankedStatus int32 `mem:"[Beatmap] + 0x12C"` // unknown, unsubmitted, pending/wip/graveyard, unused, ranked, approved, qualified
106 | MD5 string `mem:"[[Beatmap] + 0x6C]"`
107 | ObjectCount int32 `mem:"[Beatmap] + 0xFC"`
108 | //BeatmapMode int32 `mem:"[Beatmap] + 0x118"`
109 | //Tags string `mem:"[[Beatmap] + 0x20]"`
110 | //Length int32 `mem:"[Beatmap] + 0x12C"`
111 | //AudioLeadIn int32 `mem:"[Beatmap] + 0xC0"`
112 | //DrainTime int32 `mem:"[Beatmap] + 0xE8"`
113 | //DrainTime2 int32 `mem:"[Beatmap] + 0xEC"`
114 | //ScoreMenu int32 `mem:"[Beatmap] + 0xFC"` // Local, global, mod, friend, country
115 | //PreviewTime int32 `mem:"[Beatmap] + 0x118"`
116 | }
117 |
118 | type mainMenuD struct {
119 | AudioVelocityBase uint32 `mem:"[Ruleset + 0x44] + 0x10"`
120 | }
121 |
122 | type allTimesD struct {
123 | PlayTime int32 `mem:"[PlayTime + 0x5]"`
124 | MenuMods uint32 `mem:"[MenuMods + 0x9]"`
125 | ChatStatus int8 `mem:"ChatChecker - 0x20"`
126 | SkinFolder string `mem:"[[[SkinData + 4] + 0] + 68]"`
127 | ShowInterface int8 `mem:"[Settings + 0x4] + 0xC"`
128 | }
129 | type gameplayD struct {
130 | Retries int32 `mem:"[Base - 0x33] + 0x8"`
131 | PlayerName string `mem:"[[[Ruleset + 0x68] + 0x38] + 0x28]"`
132 | ModsXor1 int32 `mem:"[[[Ruleset + 0x68] + 0x38] + 0x1C] + 0xC"`
133 | ModsXor2 int32 `mem:"[[[Ruleset + 0x68] + 0x38] + 0x1C] + 0x8"`
134 | HitErrors []int32 `mem:"[[[Ruleset + 0x68] + 0x38] + 0x38]"`
135 | Mode int32 `mem:"[[Ruleset + 0x68] + 0x38] + 0x64"`
136 | MaxCombo int16 `mem:"[[Ruleset + 0x68] + 0x38] + 0x68"`
137 | ScoreV2 int32 `mem:"Ruleset + 0x100"`
138 | Hit100 int16 `mem:"[[Ruleset + 0x68] + 0x38] + 0x88"`
139 | Hit300 int16 `mem:"[[Ruleset + 0x68] + 0x38] + 0x8A"`
140 | Hit50 int16 `mem:"[[Ruleset + 0x68] + 0x38] + 0x8C"`
141 | HitGeki int16 `mem:"[[Ruleset + 0x68] + 0x38] + 0x8E"`
142 | HitKatu int16 `mem:"[[Ruleset + 0x68] + 0x38] + 0x90"`
143 | HitMiss int16 `mem:"[[Ruleset + 0x68] + 0x38] + 0x92"`
144 | Combo int16 `mem:"[[Ruleset + 0x68] + 0x38] + 0x94"`
145 | PlayerHPSmooth float64 `mem:"[[Ruleset + 0x68] + 0x40] + 0x14"`
146 | PlayerHP float64 `mem:"[[Ruleset + 0x68] + 0x40] + 0x1C"`
147 | Accuracy float64 `mem:"[[Ruleset + 0x68] + 0x48] + 0xC"`
148 | LeaderBoard uint32 `mem:"[Ruleset + 0x7C] + 0x24"`
149 | KeyOverlayArrayAddr uint32 `mem:"[[Ruleset + 0xB0] + 0x10] + 0x4"` //has to be at the end due to mem not liking dead pointers, TODO: Fix this mem-side
150 | // Score int32 `mem:"[[Ruleset + 0x68] + 0x38] + 0x78"`
151 | }
152 |
--------------------------------------------------------------------------------
/memory/tournament_linux.go:
--------------------------------------------------------------------------------
1 | //+build linux
2 |
3 | package memory
4 |
5 | import (
6 | "errors"
7 |
8 | "github.com/l3lackShark/gosumemory/mem"
9 | )
10 |
11 | func resolveTourneyClients(procs []mem.Process) ([]mem.Process, error) {
12 | return nil, errors.New("Not implemented!")
13 | }
14 |
15 | func getTourneyGameplayData(proc mem.Process, iterator int) {
16 | return
17 | }
18 |
19 | func getTourneyIPC() error {
20 | return errors.New("Not implemented!")
21 | }
22 |
--------------------------------------------------------------------------------
/memory/tournament_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | package memory
5 |
6 | import (
7 | "bufio"
8 | "errors"
9 | "fmt"
10 | "log"
11 | "os"
12 | "path/filepath"
13 | "strconv"
14 | "strings"
15 | "time"
16 |
17 | "github.com/l3lackShark/gosumemory/mem"
18 | "github.com/spf13/cast"
19 | )
20 |
21 | func resolveTourneyClients(procs []mem.Process) ([]mem.Process, error) {
22 | //read tournament.cfg to check how many clients we are expecting
23 | osuExecutablePath, err := procs[0].ExecutablePath()
24 | if err != nil {
25 | return nil, err
26 | }
27 | if !strings.Contains(osuExecutablePath, `:\`) {
28 | log.Println("Automatic executable path finder has failed. The program will now ext. GOT: ", osuExecutablePath)
29 | return nil, errors.New("osu! executable was not found")
30 | }
31 | cfgFile, err := os.Open(filepath.Join(filepath.Dir(osuExecutablePath), "tournament.cfg"))
32 | if err != nil {
33 | return nil, err
34 | }
35 | defer cfgFile.Close()
36 |
37 | var totalClients int
38 | scanner := bufio.NewScanner(cfgFile)
39 |
40 | for scanner.Scan() {
41 | if strings.Contains(scanner.Text(), "TeamSize") {
42 | teamSize, err := strconv.Atoi(scanner.Text()[len(scanner.Text())-1:])
43 | if err != nil {
44 | return nil, err
45 | }
46 | totalClients = teamSize * 2
47 | fmt.Println("Total expected amount of tournament clients:", totalClients)
48 | break
49 | }
50 | }
51 |
52 | if totalClients == 0 {
53 | return nil, errors.New("total clients is 0")
54 | }
55 | fmt.Println("[TOURNAMENT] Awaiting all clients to load...")
56 | var tourneyClients []mem.Process
57 | for len(procs) != totalClients+1 {
58 | procs, err = mem.FindProcess(osuProcessRegex)
59 | if err != nil {
60 | return nil, err
61 | }
62 | }
63 | for i := range procs {
64 | if i > len(procs)-2 {
65 | break
66 | }
67 | client, err := mem.FindWindow(fmt.Sprintf("Tournament Client %d", i))
68 | counter := 0
69 | for err != nil {
70 | if counter >= 30 {
71 | fmt.Println("Time's up! exiting tournament mode, failed after 30 attempts")
72 | return nil, errors.New("Tournament client timeout")
73 | }
74 | fmt.Println(fmt.Sprintf("[TOURNAMENT] %s, waiting for it...", err))
75 | time.Sleep(1 * time.Second)
76 | counter++
77 | client, err = mem.FindWindow(fmt.Sprintf("Tournament Client %d", i))
78 | }
79 | for _, proc := range procs {
80 | if int32(proc.Pid()) == mem.GetWindowThreadProcessID(client) {
81 | tourneyClients = append(tourneyClients, proc)
82 | break
83 | }
84 | }
85 | }
86 | return tourneyClients, nil
87 | }
88 |
89 | func getTourneyGameplayData(proc mem.Process, iterator int) {
90 | err := mem.Read(proc, &tourneyPatterns[iterator], &tourneyGameplayData[iterator])
91 | if err != nil && !strings.Contains(err.Error(), "LeaderBoard") && !strings.Contains(err.Error(), "KeyOverlay") { //TODO: fix this mem-side
92 | return //struct not initialized yet
93 | }
94 | TourneyData.IPCClients[iterator].Gameplay.Combo.Current = tourneyGameplayData[iterator].Combo
95 | TourneyData.IPCClients[iterator].Gameplay.Combo.Max = tourneyGameplayData[iterator].MaxCombo
96 | TourneyData.IPCClients[iterator].Gameplay.GameMode = tourneyGameplayData[iterator].Mode
97 | TourneyData.IPCClients[iterator].Gameplay.Score = tourneyGameplayData[iterator].ScoreV2
98 | TourneyData.IPCClients[iterator].Gameplay.Hits.H100 = tourneyGameplayData[iterator].Hit100
99 | TourneyData.IPCClients[iterator].Gameplay.Hits.HKatu = tourneyGameplayData[iterator].HitKatu
100 | TourneyData.IPCClients[iterator].Gameplay.Hits.H300 = tourneyGameplayData[iterator].Hit300
101 | TourneyData.IPCClients[iterator].Gameplay.Hits.HGeki = tourneyGameplayData[iterator].HitGeki
102 | TourneyData.IPCClients[iterator].Gameplay.Hits.H50 = tourneyGameplayData[iterator].Hit50
103 | TourneyData.IPCClients[iterator].Gameplay.Hits.H0 = tourneyGameplayData[iterator].HitMiss
104 | if TourneyData.IPCClients[iterator].Gameplay.Combo.Temp > TourneyData.IPCClients[iterator].Gameplay.Combo.Max {
105 | TourneyData.IPCClients[iterator].Gameplay.Combo.Temp = 0
106 | }
107 | if TourneyData.IPCClients[iterator].Gameplay.Combo.Current < TourneyData.IPCClients[iterator].Gameplay.Combo.Temp && TourneyData.IPCClients[iterator].Gameplay.Hits.H0Temp == TourneyData.IPCClients[iterator].Gameplay.Hits.H0 {
108 | TourneyData.IPCClients[iterator].Gameplay.Hits.HSB++
109 | }
110 | TourneyData.IPCClients[iterator].Gameplay.Hits.H0Temp = TourneyData.IPCClients[iterator].Gameplay.Hits.H0
111 | TourneyData.IPCClients[iterator].Gameplay.Combo.Temp = TourneyData.IPCClients[iterator].Gameplay.Combo.Current
112 | TourneyData.IPCClients[iterator].Gameplay.Accuracy = tourneyGameplayData[iterator].Accuracy
113 | TourneyData.IPCClients[iterator].Gameplay.Hp.Normal = tourneyGameplayData[iterator].PlayerHP
114 | TourneyData.IPCClients[iterator].Gameplay.Hp.Smooth = tourneyGameplayData[iterator].PlayerHPSmooth
115 | TourneyData.IPCClients[iterator].Gameplay.Name = tourneyGameplayData[iterator].PlayerName
116 | TourneyData.IPCClients[iterator].Gameplay.Mods.AppliedMods = int32(tourneyGameplayData[iterator].ModsXor1 ^ tourneyGameplayData[iterator].ModsXor2)
117 | if TourneyData.IPCClients[iterator].Gameplay.Mods.AppliedMods == 0 {
118 | TourneyData.IPCClients[iterator].Gameplay.Mods.PpMods = "NM"
119 | } else {
120 | TourneyData.IPCClients[iterator].Gameplay.Mods.PpMods = Mods(tourneyGameplayData[iterator].ModsXor1 ^ tourneyGameplayData[iterator].ModsXor2).String()
121 | }
122 | if TourneyData.IPCClients[iterator].Gameplay.Combo.Max > 0 {
123 | TourneyData.IPCClients[iterator].Gameplay.Hits.HitErrorArray = tourneyGameplayData[iterator].HitErrors
124 | baseUR, _ := calculateUR(TourneyData.IPCClients[iterator].Gameplay.Hits.HitErrorArray)
125 | if strings.Contains(TourneyData.IPCClients[iterator].Gameplay.Mods.PpMods, "DT") || strings.Contains(TourneyData.IPCClients[iterator].Gameplay.Mods.PpMods, "NC") {
126 | TourneyData.IPCClients[iterator].Gameplay.Hits.UnstableRate = baseUR / 1.5
127 | } else if strings.Contains(TourneyData.IPCClients[iterator].Gameplay.Mods.PpMods, "HT") {
128 | TourneyData.IPCClients[iterator].Gameplay.Hits.UnstableRate = baseUR * 1.33
129 | } else {
130 | TourneyData.IPCClients[iterator].Gameplay.Hits.UnstableRate = baseUR
131 | }
132 | }
133 | }
134 |
135 | func readTourneyIPCStruct(base int64) (int32, int32) {
136 | addresses := struct {
137 | Base int64
138 | }{base}
139 | var data struct {
140 | SpectatingID int32 `mem:"Base + 0x14"`
141 | Score int32 `mem:"Base + 0x18"`
142 | }
143 |
144 | mem.Read(process, &addresses, &data)
145 |
146 | return data.SpectatingID, data.Score
147 | }
148 |
149 | func readSpectatingUser(user int64, proc *mem.Process) (ipcSpec, error) {
150 | userAddr := struct {
151 | UserInfo int64
152 | }{user}
153 | var userData struct {
154 | Accuracy float64 `mem:"[[UserInfo - 0x5]] + 0x4"`
155 | RankedScore int64 `mem:"[[UserInfo - 0x5]] + 0xC"`
156 | PlayCount int32 `mem:"[[UserInfo - 0x5]] + 0x7C"`
157 | GlobalRank int32 `mem:"[[UserInfo - 0x5]] + 0x84"`
158 | PP int32 `mem:"[[UserInfo - 0x5]] + 0x9C"`
159 | Name string `mem:"[[[UserInfo - 0x5]] + 0x30]"`
160 | Country string `mem:"[[[UserInfo - 0x5]] + 0x2C]"`
161 | UserID int32 `mem:"[[UserInfo - 0x5]] + 0x70"`
162 | }
163 | err := mem.Read(*proc, &userAddr, &userData)
164 | if err != nil {
165 | return ipcSpec{}, errors.New("[TOURNAMENT] Could not read userData")
166 | }
167 |
168 | return ipcSpec{
169 | Accuracy: userData.Accuracy,
170 | GlobalPP: userData.PP,
171 | GlobalRank: userData.GlobalRank,
172 | Name: userData.Name,
173 | Country: userData.Country,
174 | ID: userData.UserID,
175 | PlayCount: userData.PlayCount,
176 | RankedScore: userData.RankedScore,
177 | }, nil
178 | }
179 |
180 | func getTourneyIPC() error {
181 | err := mem.Read(process,
182 | &patterns,
183 | &tourneyManagerData)
184 | if err != nil {
185 | return err
186 | }
187 | chatClass := patterns.ChatArea - 0x44
188 | TourneyData.Manager.Chat, err = readChatData(&chatClass)
189 | if err != nil {
190 | log.Println(err)
191 | DynamicAddresses.IsReady = false
192 | }
193 | TourneyData.Manager.BO = tourneyManagerData.BO
194 | TourneyData.Manager.IPCState = tourneyManagerData.IPCState
195 | TourneyData.Manager.Bools.ScoreVisible = cast.ToBool(int(tourneyManagerData.ScoreVisible))
196 | TourneyData.Manager.Bools.StarsVisible = cast.ToBool(int(tourneyManagerData.StarsVisible))
197 | TourneyData.Manager.Stars.Left = tourneyManagerData.LeftStars
198 | TourneyData.Manager.Stars.Right = tourneyManagerData.RightStars
199 | TourneyData.Manager.Name.Left = tourneyManagerData.TeamOneName
200 | TourneyData.Manager.Name.Right = tourneyManagerData.TeamTwoName
201 | TourneyData.Manager.Gameplay.Score.Left = tourneyManagerData.TeamOneScore
202 | TourneyData.Manager.Gameplay.Score.Right = tourneyManagerData.TeamTwoScore
203 | if TourneyData.Manager.IPCState != 3 && TourneyData.Manager.IPCState != 4 { //Playing, Ranking
204 | TourneyData.Manager.Gameplay = tmGameplay{}
205 | for i := range tourneyGameplayData {
206 | TourneyData.IPCClients[i].Gameplay = tourneyGameplay{}
207 | }
208 | }
209 |
210 | for i, j := leaderStart, 0; j < len(tourneyProcs); i, j = i+0x4, j+1 {
211 | slot, _ := mem.ReadUint32(process, int64(tourneyManagerData.IPCBaseAddr), int64(i))
212 |
213 | TourneyData.IPCClients[j].ID, TourneyData.IPCClients[j].Gameplay.Score = readTourneyIPCStruct(int64(slot))
214 |
215 | }
216 |
217 | for i, proc := range tourneyProcs {
218 | err := mem.Read(proc,
219 | &tourneyPatterns[i].PreSongSelectAddresses,
220 | &tourneyMenuData[i].PreSongSelectData)
221 | if err != nil {
222 | DynamicAddresses.IsReady = false
223 | log.Println("It appears that we lost the process, retrying", err)
224 | continue
225 | }
226 | if i == 0 {
227 | if tourneyMenuData[0].PreSongSelectData.Status == 0 {
228 | mem.Read(proc, &tourneyPatterns[0], &mainMenuData)
229 | MenuData.MainMenuValues.BassDensity = calculateBassDensity(mainMenuData.AudioVelocityBase, &proc)
230 | }
231 | }
232 | switch tourneyMenuData[i].PreSongSelectData.Status {
233 | case 2:
234 | getTourneyGameplayData(proc, i)
235 | }
236 | if TourneyData.IPCClients[i].ID > 0 {
237 | totalClients := len(TourneyData.IPCClients)
238 | if i < totalClients/2 {
239 | TourneyData.IPCClients[i].Team = "left"
240 | } else {
241 | TourneyData.IPCClients[i].Team = "right"
242 | }
243 | TourneyData.IPCClients[i].Spectating, _ = readSpectatingUser(tourneySpecificPatterns[i].UserInfo, &proc)
244 | } else {
245 | TourneyData.IPCClients[i] = ipcClient{}
246 | }
247 | }
248 | return nil
249 | }
250 |
251 | func readChatData(base *int64) (result []tourneyMessage, err error) {
252 | addresses := struct{ Base int64 }{*base}
253 | var data struct {
254 | Tabs uint32 `mem:"[Base + 0x1C] + 0x4"`
255 | }
256 | err = mem.Read(process, &addresses, &data)
257 | if err != nil {
258 | return nil, errors.New("[TOURNEY CHAT] Failed reading the main struct")
259 | }
260 | length, err := mem.ReadInt32(process, int64(data.Tabs), 4)
261 | for i, j := leaderStart, 0; j < int(length); i, j = i+0x4, j+1 {
262 | slot, _ := mem.ReadUint32(process, int64(data.Tabs), int64(i))
263 | if slot == 0 {
264 | continue
265 | }
266 | addrs := struct{ Base int64 }{int64(slot)}
267 | var chatData struct {
268 | ChatTag string `mem:"[[Base + 0xC] + 0x4]"`
269 | MessagesAddr uint32 `mem:"[[Base + 0xC] + 0x10] + 0x4"`
270 | }
271 | err := mem.Read(process, &addrs, &chatData)
272 | if err != nil || chatData.ChatTag != "#multiplayer" {
273 | continue
274 | }
275 | msgLength, err := mem.ReadInt32(process, int64(chatData.MessagesAddr), 4)
276 | var messages []tourneyMessage
277 | for n, k := leaderStart, 0; k < int(msgLength); n, k = n+0x4, k+1 {
278 | msgSlot, err := mem.ReadUint32(process, int64(chatData.MessagesAddr), int64(n))
279 | if err != nil {
280 | return nil, errors.New("[TOURNEY CHAT] Internal error")
281 | }
282 | msgAddrs := struct{ Base int64 }{int64(msgSlot)}
283 | var chatContent struct {
284 | TimeName string `mem:"[Base + 0x8]"`
285 | Content string `mem:"[Base + 0x4]"`
286 | }
287 | err = mem.Read(process, &msgAddrs, &chatContent)
288 | if chatContent.Content == "" || strings.HasPrefix(chatContent.Content, "!mp") {
289 | continue
290 | }
291 | spl := strings.SplitAfterN(chatContent.TimeName, " ", 2)
292 | if len(spl) < 2 {
293 | return nil, errors.New("[TOURNEY CHAT] Internal error, could not split")
294 | }
295 | var msg tourneyMessage
296 | msg.Time = strings.TrimSpace(spl[0])
297 | msg.Name = strings.TrimSuffix(spl[1], ":")
298 | msg.MessageBody = chatContent.Content
299 | for _, client := range TourneyData.IPCClients {
300 | if client.Spectating.Name == msg.Name {
301 | msg.Team = client.Team
302 | }
303 | }
304 | if msg.Team == "" {
305 | if msg.Name == "BanchoBot" {
306 | msg.Team = "bot"
307 | } else {
308 | msg.Team = "unknown"
309 | }
310 | }
311 |
312 | messages = append(messages, msg)
313 |
314 | }
315 | if len(messages) > 0 {
316 | return messages, nil
317 | }
318 | return nil, nil
319 | }
320 | return nil, nil
321 | }
322 |
--------------------------------------------------------------------------------
/memory/values.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | //InMenuValues inside osu!memory
4 | type InMenuValues struct {
5 | MainMenuValues MainMenuValues `json:"mainMenu"`
6 | OsuStatus uint32 `json:"state"`
7 | GameMode int32 `json:"gameMode"`
8 | ChatChecker int8 `json:"isChatEnabled"` //bool (1 byte)
9 | Bm bm `json:"bm"`
10 | Mods modsM `json:"mods"`
11 | PP ppM `json:"pp"`
12 | }
13 |
14 | type folders struct {
15 | Game string `json:"game"`
16 | Skin string `json:"skin"`
17 | Songs string `json:"songs"`
18 | }
19 |
20 | type ResultsScreenValues struct {
21 | Name string `json:"name"`
22 | Score int32 `json:"score"`
23 | MaxCombo int16 `json:"maxCombo"`
24 | Mods modsM `json:"mods"`
25 | H300 int16 `json:"300"`
26 | HGeki int16 `json:"geki"`
27 | H100 int16 `json:"100"`
28 | HKatu int16 `json:"katu"`
29 | H50 int16 `json:"50"`
30 | H0 int16 `json:"0"`
31 | }
32 |
33 | type MainMenuValues struct {
34 | BassDensity float64 `json:"bassDensity"`
35 | }
36 |
37 | //InSettingsValues are values represented inside settings class, could be dynamic
38 | type InSettingsValues struct {
39 | ShowInterface bool `json:"showInterface"` //dynamic in gameplay
40 | Folders folders `json:"folders"`
41 | }
42 |
43 | type TourneyValues struct {
44 | Manager tourneyManager `json:"manager"`
45 | IPCClients []ipcClient `json:"ipcClients"`
46 | }
47 |
48 | type tourneyManager struct {
49 | IPCState int32 `json:"ipcState"`
50 | BO int32 `json:"bestOF"`
51 | Name tName `json:"teamName"`
52 | Stars tStars `json:"stars"`
53 | Bools tBools `json:"bools"`
54 | Chat []tourneyMessage `json:"chat"`
55 | Gameplay tmGameplay `json:"gameplay"`
56 | }
57 |
58 | type tourneyMessage struct {
59 | Team string `json:"team"`
60 | Time string `json:"time"`
61 | Name string `json:"name"`
62 | MessageBody string `json:"messageBody"`
63 | }
64 |
65 | type tmGameplay struct {
66 | Score tScore `json:"score"`
67 | }
68 |
69 | type tBools struct {
70 | ScoreVisible bool `json:"scoreVisible"`
71 | StarsVisible bool `json:"starsVisible"`
72 | }
73 |
74 | type tName struct {
75 | Left string `json:"left"`
76 | Right string `json:"right"`
77 | }
78 | type tStars struct {
79 | Left int32 `json:"left"`
80 | Right int32 `json:"right"`
81 | }
82 | type tScore struct {
83 | Left int32 `json:"left"`
84 | Right int32 `json:"right"`
85 | }
86 |
87 | type ipcClient struct {
88 | ID int32 `json:"-"`
89 | Team string `json:"team"`
90 | Spectating ipcSpec `json:"spectating"`
91 | Gameplay tourneyGameplay `json:"gameplay"`
92 | }
93 |
94 | type ipcSpec struct {
95 | Name string `json:"name"`
96 | Country string `json:"country"`
97 | ID int32 `json:"userID"`
98 | Accuracy float64 `json:"accuracy"`
99 | RankedScore int64 `json:"rankedScore"`
100 | PlayCount int32 `json:"playCount"`
101 | GlobalRank int32 `json:"globalRank"`
102 | GlobalPP int32 `json:"totalPP"`
103 | }
104 |
105 | type tourneyGameplay struct {
106 | GameMode int32 `json:"gameMode"`
107 | Score int32 `json:"score"`
108 | Name string `json:"name"`
109 | Accuracy float64 `json:"accuracy"`
110 | Hits tourneyHits `json:"hits"`
111 | Combo combo `json:"combo"`
112 | Mods modsM `json:"mods"`
113 | Hp hp `json:"hp"`
114 | }
115 |
116 | type gGrade struct {
117 | Current string `json:"current"`
118 | Expected string `json:"maxThisPlay"`
119 | }
120 |
121 | //GameplayValues inside osu!memory
122 | type GameplayValues struct {
123 | GameMode int32 `json:"gameMode"`
124 | Name string `json:"name"`
125 | Score int32 `json:"score"`
126 | Accuracy float64 `json:"accuracy"`
127 | Combo combo `json:"combo"`
128 | Hp hp `json:"hp"`
129 | Hits hits `json:"hits"`
130 | PP ppG `json:"pp"`
131 | KeyOverlay keyOverlay `json:"keyOverlay"`
132 | Leaderboard leaderboard `json:"leaderboard"`
133 | }
134 |
135 | type keyOverlay struct {
136 | K1 keyOverlayButton `json:"k1"`
137 | K2 keyOverlayButton `json:"k2"`
138 | M1 keyOverlayButton `json:"m1"`
139 | M2 keyOverlayButton `json:"m2"`
140 | }
141 |
142 | type keyOverlayButton struct {
143 | IsPressed bool `json:"isPressed"`
144 | Count int32 `json:"count"`
145 | }
146 |
147 | type bm struct {
148 | Time tim `json:"time"`
149 | BeatmapID int32 `json:"id"`
150 | BeatmapSetID int32 `json:"set"`
151 | BeatmapMD5 string `json:"md5"`
152 | RandkedStatus int32 `json:"rankedStatus"` //unknown, unsubmitted, pending/wip/graveyard, unused, ranked, approved, qualified
153 | Metadata Metadata `json:"metadata"`
154 | Stats stats `json:"stats"`
155 | Path path `json:"path"`
156 | HitObjectStats string `json:"-"`
157 | BeatmapString string `json:"-"`
158 | }
159 |
160 | type tim struct {
161 | FirstObj int32 `json:"firstObj"`
162 | PlayTime int32 `json:"current"`
163 | FullTime int32 `json:"full"`
164 | Mp3Time int32 `json:"mp3"`
165 | }
166 |
167 | // Metadata Map data
168 | type Metadata struct {
169 | Artist string `json:"artist"`
170 | ArtistOriginal string `json:"artistOriginal"`
171 | Title string `json:"title"`
172 | TitleOriginal string `json:"titleOriginal"`
173 | Mapper string `json:"mapper"`
174 | Version string `json:"difficulty"`
175 | }
176 |
177 | type stats struct {
178 | BeatmapAR float32 `json:"AR"`
179 | BeatmapCS float32 `json:"CS"`
180 | BeatmapOD float32 `json:"OD"`
181 | BeatmapHP float32 `json:"HP"`
182 | BeatmapSR float32 `json:"SR"`
183 | BeatmapBPM bpm `json:"BPM"`
184 | BeatmapMaxCombo int32 `json:"maxCombo"`
185 | FullSR float32 `json:"fullSR"`
186 | MemoryAR float32 `json:"memoryAR"`
187 | MemoryCS float32 `json:"memoryCS"`
188 | MemoryOD float32 `json:"memoryOD"`
189 | MemoryHP float32 `json:"memoryHP"`
190 | TotalHitObjects int32 `json:"-"`
191 | }
192 |
193 | type bpm struct {
194 | Minimal int `json:"min"`
195 | Maximal int `json:"max"`
196 | }
197 |
198 | type path struct {
199 | InnerBGPath string `json:"full"`
200 | BeatmapFolderString string `json:"folder"`
201 | BeatmapOsuFileString string `json:"file"`
202 | BGPath string `json:"bg"`
203 | AudioPath string `json:"audio"`
204 | FullMP3Path string `json:"-"`
205 | FullDotOsu string `json:"-"`
206 | }
207 |
208 | type modsM struct {
209 | AppliedMods int32 `json:"num"`
210 | PpMods string `json:"str"`
211 | }
212 |
213 | type ppM struct {
214 | PpSS int32 `json:"100"`
215 | Pp99 int32 `json:"99"`
216 | Pp98 int32 `json:"98"`
217 | Pp97 int32 `json:"97"`
218 | Pp96 int32 `json:"96"`
219 | Pp95 int32 `json:"95"`
220 | PpStrains []float64 `json:"strains"`
221 | }
222 |
223 | type combo struct {
224 | Current int16 `json:"current"`
225 | Max int16 `json:"max"`
226 | Temp int16 `json:"-"`
227 | }
228 |
229 | type hp struct {
230 | Normal float64 `json:"normal"`
231 | Smooth float64 `json:"smooth"`
232 | }
233 |
234 | type hits struct {
235 | H300 int16 `json:"300"`
236 | HGeki int16 `json:"geki"`
237 | H100 int16 `json:"100"`
238 | HKatu int16 `json:"katu"`
239 | H50 int16 `json:"50"`
240 | H0 int16 `json:"0"`
241 | H0Temp int16 `json:"-"`
242 | HSB int16 `json:"sliderBreaks"`
243 | Grade gGrade `json:"grade"`
244 | UnstableRate float64 `json:"unstableRate"`
245 | HitErrorArray []int32 `json:"hitErrorArray"`
246 | }
247 |
248 | type tourneyHits struct {
249 | H300 int16 `json:"300"`
250 | HGeki int16 `json:"geki"`
251 | H100 int16 `json:"100"`
252 | HKatu int16 `json:"katu"`
253 | H50 int16 `json:"50"`
254 | H0 int16 `json:"0"`
255 | H0Temp int16 `json:"-"`
256 | HSB int16 `json:"sliderBreaks"`
257 | UnstableRate float64 `json:"unstableRate"`
258 | HitErrorArray []int32 `json:"-"`
259 | }
260 |
261 | type ppG struct {
262 | Pp int32 `json:"current"`
263 | PPifFC int32 `json:"fc"`
264 | PPMaxThisPlay int32 `json:"maxThisPlay"`
265 | }
266 |
267 | type dynamicAddresses struct {
268 | IsReady bool
269 | }
270 |
271 | type leaderPlayer struct {
272 | Name string `json:"name"`
273 | Score int32 `json:"score"`
274 | Combo int16 `json:"combo"`
275 | MaxCombo int16 `json:"maxCombo"`
276 | Mods string `json:"mods"`
277 | H300 int16 `json:"h300"`
278 | H100 int16 `json:"h100"`
279 | H50 int16 `json:"h50"`
280 | H0 int16 `json:"h0"`
281 | Team int32 `json:"team"`
282 | Position int32 `json:"position"`
283 | IsPassing int8 `json:"isPassing"` //bool
284 | }
285 |
286 | type leaderboard struct {
287 | DoesLeaderBoardExists bool `json:"hasLeaderboard"`
288 | IsLeaderBoardVisible bool `json:"isVisible"`
289 | OurPlayer leaderPlayer `json:"ourplayer"`
290 | Slots []leaderPlayer `json:"slots"`
291 | }
292 |
293 | //MenuData contains raw values taken from osu! memory
294 | var MenuData = InMenuValues{}
295 |
296 | //GameplayData contains raw values taken from osu! memory
297 | var GameplayData = GameplayValues{}
298 |
299 | //ResultsScreenData contains raw values taken from osu! memory
300 | var ResultsScreenData = ResultsScreenValues{}
301 |
302 | //SettingsData contains raw values taken from osu! memory
303 | var SettingsData = InSettingsValues{}
304 |
305 | //TourneyData contains raw values taken from osu! memory
306 | var TourneyData = TourneyValues{}
307 |
308 | //DynamicAddresses are in-between pointers that lead to values
309 | var DynamicAddresses = dynamicAddresses{}
310 |
--------------------------------------------------------------------------------
/out.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/l3lackShark/gosumemory/8b1915f594e218c3fbaa23d0dcddfadd4523c762/out.ico
--------------------------------------------------------------------------------
/pp/editor.go:
--------------------------------------------------------------------------------
1 | package pp
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "math"
7 | "strings"
8 | "time"
9 | "unsafe"
10 |
11 | "github.com/l3lackShark/gosumemory/memory"
12 | "github.com/spf13/cast"
13 | )
14 |
15 | //#cgo LDFLAGS: -lm
16 | //#cgo CPPFLAGS: -DOPPAI_STATIC_HEADER
17 | //#include
18 | //#include "oppai.c"
19 | import "C"
20 |
21 | var ezeditor C.ezpp_t
22 |
23 | func readEditorData(data *PP, ezeditor C.ezpp_t, needStrain bool) error {
24 | path := memory.MenuData.Bm.Path.FullDotOsu
25 |
26 | if strings.HasSuffix(path, ".osu") && memory.DynamicAddresses.IsReady == true {
27 | cpath := C.CString(path)
28 |
29 | defer C.free(unsafe.Pointer(cpath))
30 | if rc := C.ezpp(ezeditor, cpath); rc < 0 {
31 | return errors.New(C.GoString(C.errstr(rc)))
32 | }
33 | C.ezpp_set_base_ar(ezeditor, C.float(memory.MenuData.Bm.Stats.BeatmapAR))
34 | C.ezpp_set_base_od(ezeditor, C.float(memory.MenuData.Bm.Stats.BeatmapOD))
35 | C.ezpp_set_base_cs(ezeditor, C.float(memory.MenuData.Bm.Stats.BeatmapCS))
36 | C.ezpp_set_base_hp(ezeditor, C.float(memory.MenuData.Bm.Stats.BeatmapHP))
37 | C.ezpp_set_accuracy_percent(ezeditor, C.float(100.0))
38 | C.ezpp_set_nmiss(ezeditor, C.int(0))
39 | if needStrain == true {
40 | C.ezpp_set_end_time(ezeditor, 0)
41 | C.ezpp_set_combo(ezeditor, 0)
42 | C.ezpp_set_nmiss(ezeditor, 0)
43 | strainArray = nil
44 | seek := 0
45 | var window []float64
46 | var total []float64
47 | // for seek < int(C.ezpp_time_at(ezeditor, C.ezpp_nobjects(ezeditor)-1)) { //len-1
48 | for int32(seek) < memory.MenuData.Bm.Time.Mp3Time {
49 | for obj := 0; obj <= int(C.ezpp_nobjects(ezeditor)-1); obj++ {
50 | if tempBeatmapFile != memory.MenuData.Bm.Path.BeatmapOsuFileString {
51 | return nil //Interrupt calcualtion if user has changed the map.
52 | }
53 | if int(C.ezpp_time_at(ezeditor, C.int(obj))) >= seek && int(C.ezpp_time_at(ezeditor, C.int(obj))) <= seek+3000 {
54 | window = append(window, float64(C.ezpp_strain_at(ezeditor, C.int(obj), 0))+float64(C.ezpp_strain_at(ezeditor, C.int(obj), 1)))
55 | }
56 | }
57 | sum := 0.0
58 | for _, num := range window {
59 | sum += num
60 | }
61 | total = append(total, sum/math.Max(float64(len(window)), 1))
62 | window = nil
63 | seek += 500
64 | }
65 | strainArray = total
66 | memory.MenuData.Bm.Time.FirstObj = int32(C.ezpp_time_at(ezeditor, 0))
67 | memory.MenuData.Bm.Time.FullTime = int32(C.ezpp_time_at(ezeditor, C.ezpp_nobjects(ezeditor)-1))
68 |
69 | } else {
70 | C.ezpp_set_mods(ezeditor, C.int(0))
71 | C.ezpp_set_end_time(ezeditor, C.float(memory.MenuData.Bm.Time.PlayTime))
72 | C.ezpp_set_combo(ezeditor, C.int(-1))
73 | }
74 |
75 | *data = PP{
76 | Total: C.ezpp_pp(ezeditor),
77 | Strain: strainArray,
78 | AR: C.ezpp_ar(ezeditor),
79 | CS: C.ezpp_cs(ezeditor),
80 | OD: C.ezpp_od(ezeditor),
81 | HP: C.ezpp_hp(ezeditor),
82 | StarRating: C.ezpp_stars(ezeditor),
83 | }
84 | }
85 | return nil
86 | }
87 |
88 | var tempOsuMD5 string
89 |
90 | func GetEditorData() {
91 |
92 | ezeditor = C.ezpp_new()
93 | C.ezpp_set_autocalc(ezeditor, 1)
94 | //defer C.ezpp_free(ezeditor)
95 | var data PP
96 |
97 | for {
98 | if memory.DynamicAddresses.IsReady == true {
99 | if memory.MenuData.GameMode == 0 && memory.MenuData.OsuStatus == 1 {
100 | err := readEditorData(&data, ezeditor, false)
101 | if err != nil {
102 | fmt.Println(err)
103 | }
104 | memory.GameplayData.PP.Pp = int32(data.Total)
105 | memory.MenuData.Bm.Stats.BeatmapAR = float32(data.AR)
106 | memory.MenuData.Bm.Stats.BeatmapCS = float32(data.CS)
107 | memory.MenuData.Bm.Stats.BeatmapOD = float32(data.OD)
108 | memory.MenuData.Bm.Stats.BeatmapHP = float32(data.HP)
109 | memory.MenuData.Bm.Stats.BeatmapSR = cast.ToFloat32(fmt.Sprintf("%.2f", float32(data.StarRating)))
110 |
111 | md5, err := hashFileMD5(memory.MenuData.Bm.Path.FullDotOsu)
112 | if err != nil {
113 | continue
114 | }
115 | if tempOsuMD5 != md5 {
116 | tempOsuMD5 = md5
117 | readEditorData(&data, ezeditor, true)
118 | memory.MenuData.PP.PpStrains = data.Strain
119 | }
120 |
121 | }
122 |
123 | }
124 | time.Sleep(time.Duration(memory.UpdateTime) * time.Millisecond)
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/pp/functions.go:
--------------------------------------------------------------------------------
1 | package pp
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "fmt"
7 | "io"
8 | "os"
9 | "strings"
10 |
11 | "github.com/k0kubun/pp"
12 | "github.com/l3lackShark/gosumemory/memory"
13 | "github.com/tcolgate/mp3"
14 | )
15 |
16 | func hashFileMD5(filePath string) (string, error) {
17 | var returnMD5String string
18 | file, err := os.Open(filePath)
19 | if err != nil {
20 | return returnMD5String, err
21 | }
22 | defer file.Close()
23 | hash := md5.New()
24 | if _, err := io.Copy(hash, file); err != nil {
25 | return returnMD5String, err
26 | }
27 | hashInBytes := hash.Sum(nil)[:16]
28 | returnMD5String = hex.EncodeToString(hashInBytes)
29 | return returnMD5String, nil
30 |
31 | }
32 | func calculateMP3Time() (int32, error) {
33 | if memory.MenuData.Bm.Path.FullMP3Path == "" {
34 | return 0, nil
35 | }
36 | if !strings.HasSuffix(strings.ToLower(memory.MenuData.Bm.Path.FullMP3Path), ".mp3") {
37 | pp.Println("Expected mp3, got something else. Aborting mp3 time calculation. GOT: ", memory.MenuData.Bm.Path.FullMP3Path)
38 | return 0, nil
39 | }
40 | var t int64
41 | r, err := os.Open(memory.MenuData.Bm.Path.FullMP3Path)
42 | if err != nil {
43 | fmt.Println(err)
44 | return 0, err
45 | }
46 |
47 | d := mp3.NewDecoder(r)
48 | var f mp3.Frame
49 | skipped := 0
50 |
51 | for {
52 |
53 | if err := d.Decode(&f, &skipped); err != nil {
54 | if err != nil {
55 | break
56 | }
57 | }
58 |
59 | t = t + f.Duration().Milliseconds()
60 | }
61 |
62 | return int32(t), nil
63 | }
64 |
65 | func minMax(array []int) (int, int) {
66 | if len(array) < 1 {
67 | return 0, 0
68 | }
69 | var max int = array[0]
70 | var min int = array[0]
71 | for _, value := range array {
72 | if max < value {
73 | max = value
74 | }
75 | if min > value {
76 | min = value
77 | }
78 | }
79 | return min, max
80 | }
81 |
82 | func calculateAccuracy(h300 float32, h100 float32, h50 float32, h0 float32) float32 {
83 | return 100 * (h50*50 + h100*100 + h300*300) / (h50*300 + h100*300 + h300*300 + h0*300)
84 | }
85 |
86 | func calculateGrade(h300 float32, h100 float32, h50 float32, h0 float32) string { //https://osu.ppy.sh/help/wiki/FAQ#grades
87 | onePercent := (h300 + h100 + h50 + h0) / 100
88 | if h100 == 0 && h50 == 0 && h0 == 0 {
89 | return "SS"
90 | }
91 | if h0 == 0 && onePercent*90 < h300 && h50 < onePercent {
92 | return "S"
93 | }
94 | if h0 == 0 && onePercent*80 < h300 || onePercent*90 < h300 {
95 | return "A"
96 | }
97 | if h0 == 0 && onePercent*70 < h300 || onePercent*80 < h300 {
98 | return "B"
99 | }
100 | if onePercent*60 < h300 {
101 | return "C"
102 | }
103 | return "D"
104 | }
105 |
--------------------------------------------------------------------------------
/pp/iffc.go:
--------------------------------------------------------------------------------
1 | package pp
2 |
3 | //TODO: I need to figure out how to use only one calc.
4 |
5 | import (
6 | "errors"
7 | "math"
8 | "strings"
9 | "time"
10 | "unsafe"
11 |
12 | "github.com/k0kubun/pp"
13 | "github.com/l3lackShark/gosumemory/memory"
14 | "github.com/spf13/cast"
15 | )
16 |
17 | //#cgo LDFLAGS: -lm
18 | //#cgo CPPFLAGS: -DOPPAI_STATIC_HEADER
19 | //#include
20 | //#include "oppai.c"
21 | import "C"
22 |
23 | var ezfc C.ezpp_t
24 |
25 | type PPfc struct {
26 | RestSS C.float
27 | Acc C.float
28 | GradeCurrent string
29 | GradeExpected string
30 | }
31 |
32 | func readFCData(data *PPfc, ezfc C.ezpp_t, acc C.float) error {
33 | path := memory.MenuData.Bm.Path.FullDotOsu
34 |
35 | if strings.HasSuffix(path, ".osu") && memory.DynamicAddresses.IsReady == true {
36 | cpath := C.CString(path)
37 |
38 | defer C.free(unsafe.Pointer(cpath))
39 | if rc := C.ezpp(ezfc, cpath); rc < 0 {
40 | return errors.New(C.GoString(C.errstr(rc)))
41 | }
42 | C.ezpp_set_base_ar(ezfc, C.float(memory.MenuData.Bm.Stats.BeatmapAR))
43 | C.ezpp_set_base_od(ezfc, C.float(memory.MenuData.Bm.Stats.BeatmapOD))
44 | C.ezpp_set_base_cs(ezfc, C.float(memory.MenuData.Bm.Stats.BeatmapCS))
45 | C.ezpp_set_base_hp(ezfc, C.float(memory.MenuData.Bm.Stats.BeatmapHP))
46 | C.ezpp_set_mods(ezfc, C.int(memory.MenuData.Mods.AppliedMods))
47 | totalObj := C.ezpp_nobjects(ezfc)
48 |
49 | if memory.GameplayData.Hits.H0+memory.GameplayData.Hits.HSB == 0 {
50 | totalCombo := C.ezpp_max_combo(ezfc)
51 | diff := currMaxCombo - C.int(memory.GameplayData.Combo.Max)
52 | C.ezpp_set_combo(ezfc, C.int(totalCombo-diff))
53 | } else {
54 | C.ezpp_set_combo(ezfc, C.int(-1)) //since we are not freeing the counter every time we need to clear the combo TODO: Consider dropped sliderends
55 | }
56 | C.ezpp_set_nmiss(ezfc, C.int(0))
57 |
58 | remaining := int16(totalObj) - memory.GameplayData.Hits.H300 - memory.GameplayData.Hits.H100 - memory.GameplayData.Hits.H50 - memory.GameplayData.Hits.H0
59 | ifRestSSACC := float64(calculateAccuracy(float32(memory.GameplayData.Hits.H300+remaining), float32(memory.GameplayData.Hits.H100), float32(memory.GameplayData.Hits.H50), float32(memory.GameplayData.Hits.H0)))
60 | ifRestSSACC = math.Round(ifRestSSACC*100) / 100
61 | C.ezpp_set_accuracy_percent(ezfc, C.float(ifRestSSACC))
62 | ifRestSS := C.ezpp_pp(ezfc)
63 | C.ezpp_set_combo(ezfc, C.int(-1))
64 | C.ezpp_set_accuracy_percent(ezfc, C.float(acc))
65 | //C.ezpp_set_score_version(ezfc)
66 | *data = PPfc{
67 | RestSS: ifRestSS,
68 | Acc: C.ezpp_pp(ezfc),
69 | GradeCurrent: calculateGrade(float32(memory.GameplayData.Hits.H300), float32(memory.GameplayData.Hits.H100), float32(memory.GameplayData.Hits.H50), float32(memory.GameplayData.Hits.H0)),
70 | GradeExpected: calculateGrade(float32(memory.GameplayData.Hits.H300+remaining), float32(memory.GameplayData.Hits.H100), float32(memory.GameplayData.Hits.H50), float32(memory.GameplayData.Hits.H0)),
71 | }
72 | }
73 |
74 | return nil
75 | }
76 |
77 | func GetFCData() {
78 | ezfc = C.ezpp_new()
79 | C.ezpp_set_autocalc(ezfc, 1)
80 | for {
81 |
82 | if memory.DynamicAddresses.IsReady == true {
83 |
84 | switch memory.GameplayData.GameMode {
85 | case 0, 1:
86 |
87 | if memory.MenuData.OsuStatus == 2 && memory.GameplayData.Combo.Max > 0 {
88 | var data PPfc
89 | readFCData(&data, ezfc, C.float(memory.GameplayData.Accuracy))
90 | res, err := wiekuCalcCrutch(memory.MenuData.Bm.Path.FullDotOsu, int16(memory.MenuData.Bm.Stats.BeatmapMaxCombo), int16(C.ezpp_nobjects(ezfc)-1)-memory.GameplayData.Hits.H100-memory.GameplayData.Hits.H50, memory.GameplayData.Hits.H100, memory.GameplayData.Hits.H50, 0)
91 | if err != nil {
92 | pp.Println(err)
93 | memory.GameplayData.PP.PPifFC = cast.ToInt32(float64(data.RestSS))
94 | } else {
95 | memory.GameplayData.PP.PPifFC = res
96 | }
97 |
98 | memory.GameplayData.Hits.Grade.Current = data.GradeCurrent
99 | memory.GameplayData.Hits.Grade.Expected = data.GradeExpected
100 | }
101 | switch memory.MenuData.OsuStatus {
102 | case 1, 4, 5, 13, 2:
103 | if memory.MenuData.OsuStatus == 2 && memory.MenuData.Bm.Time.PlayTime > 150 { //To catch up with the F2-->Enter
104 | time.Sleep(250 * time.Millisecond)
105 | continue
106 | }
107 | //TODO: figure out how to calc %% pp on the new rework
108 | // if memory.GameplayData.GameMode == 0 {
109 | // wiekuCalcCrutch(memory.MenuData.Bm.Path.FullDotOsu, int16(memory.MenuData.Bm.Stats.BeatmapMaxCombo), desired300Hits())
110 | // }
111 | var data PPfc
112 | readFCData(&data, ezfc, 100.0)
113 | memory.MenuData.PP.PpSS = cast.ToInt32(float64(data.Acc))
114 | readFCData(&data, ezfc, 99.0)
115 | memory.MenuData.PP.Pp99 = cast.ToInt32(float64(data.Acc))
116 | readFCData(&data, ezfc, 98.0)
117 | memory.MenuData.PP.Pp98 = cast.ToInt32(float64(data.Acc))
118 | readFCData(&data, ezfc, 97.0)
119 | memory.MenuData.PP.Pp97 = cast.ToInt32(float64(data.Acc))
120 | readFCData(&data, ezfc, 96.0)
121 | memory.MenuData.PP.Pp96 = cast.ToInt32(float64(data.Acc))
122 | readFCData(&data, ezfc, 95.0)
123 | memory.MenuData.PP.Pp95 = cast.ToInt32(float64(data.Acc))
124 | }
125 | }
126 |
127 | }
128 |
129 | time.Sleep(250 * time.Millisecond)
130 | }
131 | }
132 |
133 | func desired300Hits(maxcombo float32, acc float32) int16 {
134 | return int16(maxcombo / 100.0 * acc)
135 | }
136 |
--------------------------------------------------------------------------------
/pp/mania.go:
--------------------------------------------------------------------------------
1 | package pp
2 |
3 | import (
4 | "math"
5 | )
6 |
7 | func calculateManiaPP(od float64, stars float64, noteCount float64, score float64) float64 {
8 |
9 | //var strainStep := 400 * timeScale
10 | hit300Window := 34 + 3*(math.Min(10, math.Max(0, 10-od)))
11 | strainValue := math.Pow(5*math.Max(1, stars/0.2)-4, 2.2) / 135 * (1 + 0.1*math.Min(1, noteCount/1500))
12 | //if score <= 500000 {
13 | // strainValue = 0
14 | //}
15 | if score <= 600000 {
16 | strainValue *= (score - 500000) / 100000 * 0.3
17 | } else if score <= 700000 {
18 | strainValue *= 0.3 + (score-600000)/100000*0.25
19 | } else if score <= 800000 {
20 | strainValue *= 0.55 + (score-700000)/100000*0.20
21 | } else if score <= 900000 {
22 | strainValue *= 0.75 + (score-800000)/100000*0.15
23 | } else {
24 | strainValue *= 0.9 + (score-900000)/100000*0.1
25 | }
26 | accValue := math.Max(0, 0.2-((hit300Window-34)*0.006667)) * strainValue * math.Pow(math.Max(0, score-960000)/40000, 1.1)
27 | ppMultiplier := 0.8
28 |
29 | return math.Pow(math.Pow(strainValue, 1.1)+math.Pow(accValue, 1.1), 1/1.1) * ppMultiplier
30 | }
31 |
--------------------------------------------------------------------------------
/pp/max.go:
--------------------------------------------------------------------------------
1 | package pp
2 |
3 | //TODO: I need to figure out how to use only one calc.
4 |
5 | //#cgo LDFLAGS: -lm
6 | //#cgo CPPFLAGS: -DOPPAI_STATIC_HEADER
7 | //#include
8 | //#include "oppai.c"
9 | import "C"
10 | import (
11 | "errors"
12 | "math"
13 | "strings"
14 | "time"
15 | "unsafe"
16 |
17 | "github.com/l3lackShark/gosumemory/memory"
18 | "github.com/spf13/cast"
19 | )
20 |
21 | var ezmax C.ezpp_t
22 | var tempBadJudgments int16
23 | var possibleMax float64
24 |
25 | type PPmax struct {
26 | MaxThisPlay C.float
27 | }
28 |
29 | func readMaxData(data *PPmax, ezmax C.ezpp_t) error {
30 | path := memory.MenuData.Bm.Path.FullDotOsu
31 |
32 | if strings.HasSuffix(path, ".osu") && memory.DynamicAddresses.IsReady == true {
33 | cpath := C.CString(path)
34 |
35 | defer C.free(unsafe.Pointer(cpath))
36 | if rc := C.ezpp(ezmax, cpath); rc < 0 {
37 | return errors.New(C.GoString(C.errstr(rc)))
38 | }
39 | C.ezpp_set_base_ar(ezmax, C.float(memory.MenuData.Bm.Stats.BeatmapAR))
40 | C.ezpp_set_base_od(ezmax, C.float(memory.MenuData.Bm.Stats.BeatmapOD))
41 | C.ezpp_set_base_cs(ezmax, C.float(memory.MenuData.Bm.Stats.BeatmapCS))
42 | C.ezpp_set_base_hp(ezmax, C.float(memory.MenuData.Bm.Stats.BeatmapHP))
43 | C.ezpp_set_mods(ezmax, C.int(memory.MenuData.Mods.AppliedMods))
44 | totalObj := C.ezpp_nobjects(ezmax)
45 | totalCombo := C.ezpp_max_combo(ezmax)
46 | remaining := int16(totalObj) - memory.GameplayData.Hits.H300 - memory.GameplayData.Hits.H100 - memory.GameplayData.Hits.H50 - memory.GameplayData.Hits.H0
47 | ifRestSSACC := float64(calculateAccuracy(float32(memory.GameplayData.Hits.H300+remaining), float32(memory.GameplayData.Hits.H100), float32(memory.GameplayData.Hits.H50), float32(memory.GameplayData.Hits.H0)))
48 | ifRestSSACC = math.Round(ifRestSSACC*100) / 100
49 | C.ezpp_set_accuracy_percent(ezmax, C.float(ifRestSSACC))
50 |
51 | //Get Possible max combo in the current play
52 | //var lessThanMaxCombo bool
53 |
54 | combinedBadJudgments := memory.GameplayData.Hits.H0 + memory.GameplayData.Hits.HSB
55 | if combinedBadJudgments > 0 {
56 | if tempBadJudgments != combinedBadJudgments {
57 | tempBadJudgments = combinedBadJudgments
58 | possibleMax = math.Max(float64(totalCombo-currMaxCombo), float64(memory.GameplayData.Combo.Max))
59 | }
60 | //lessThanMaxCombo = true
61 | } else {
62 | possibleMax = float64(totalCombo)
63 | //lessThanMaxCombo = false
64 | }
65 |
66 | if memory.MenuData.OsuStatus == 2 {
67 | C.ezpp_set_nmiss(ezmax, C.int(memory.GameplayData.Hits.H0))
68 | C.ezpp_set_combo(ezmax, C.int(possibleMax))
69 | }
70 |
71 | maxThisPlay := C.ezpp_pp(ezmax)
72 | *data = PPmax{
73 | MaxThisPlay: maxThisPlay,
74 | }
75 | // type test struct {
76 | // expectedAccuracy float64
77 | // RemainingHitObj int16
78 | // lessThanMaxCombo bool
79 | // maxPossibleCombo int32
80 | // maxThisPlayPP int32
81 | // currentPP int32
82 | // ifFC int32
83 | // }
84 | // testing := test{ifRestSSACC, remaining, lessThanMaxCombo, int32(possibleMax), int32(maxThisPlay), memory.GameplayData.PP.Pp, memory.GameplayData.PP.PPifFC}
85 | // pp.Println(testing)
86 |
87 | }
88 |
89 | return nil
90 | }
91 |
92 | func GetMaxData() {
93 | ezmax = C.ezpp_new()
94 | C.ezpp_set_autocalc(ezmax, 1)
95 | for {
96 |
97 | if memory.DynamicAddresses.IsReady == true {
98 |
99 | switch memory.GameplayData.GameMode {
100 | case 0, 1:
101 |
102 | if memory.MenuData.OsuStatus == 2 && memory.GameplayData.Combo.Max > 0 {
103 | var data PPmax
104 | readMaxData(&data, ezmax)
105 | memory.GameplayData.PP.PPMaxThisPlay = cast.ToInt32(float64(data.MaxThisPlay))
106 | }
107 | }
108 | }
109 |
110 | time.Sleep(250 * time.Millisecond)
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/pp/oppai_impl.c:
--------------------------------------------------------------------------------
1 | #define OPPAI_IMPLEMENTATION
2 | #include "oppai.c"
--------------------------------------------------------------------------------
/pp/pp.go:
--------------------------------------------------------------------------------
1 | package pp
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "math"
7 | "os"
8 | "strings"
9 | "time"
10 | "unsafe"
11 |
12 | "github.com/Wieku/gosu-pp/beatmap"
13 | "github.com/Wieku/gosu-pp/beatmap/difficulty"
14 | "github.com/Wieku/gosu-pp/performance/osu"
15 | "github.com/k0kubun/pp"
16 | "github.com/l3lackShark/gosumemory/memory"
17 | "github.com/spf13/cast"
18 | )
19 |
20 | //#cgo LDFLAGS: -lm
21 | //#cgo CPPFLAGS: -DOPPAI_STATIC_HEADER
22 | //#include
23 | //#include "oppai.c"
24 | import "C"
25 |
26 | var ez C.ezpp_t
27 |
28 | type PP struct {
29 | Total C.float
30 | FC C.float
31 | Strain []float64
32 | StarRating C.float
33 | AimStars C.float
34 | SpeedStars C.float
35 | AimPP C.float
36 | SpeedPP C.float
37 | Accuracy C.float
38 | N300 C.int
39 | N100 C.int
40 | N50 C.int
41 | NMiss C.int
42 | AR C.float
43 | CS C.float
44 | OD C.float
45 | HP C.float
46 | Artist string
47 | ArtistUnicode string
48 | Title string
49 | TitleUnicode string
50 | Version string
51 | Creator string
52 | NCircles C.int
53 | NSliders C.int
54 | NSpinners C.int
55 | ODMS C.float
56 | Mode C.int
57 | Combo C.int
58 | MaxCombo C.int
59 | Mods C.int
60 | ScoreVersion C.int
61 | }
62 |
63 | var strainArray []float64
64 | var tempBeatmapFile string
65 | var tempGameMode int32 = 4
66 | var currMaxCombo C.int
67 |
68 | func readData(data *PP, ez C.ezpp_t, needStrain bool, path string) error {
69 |
70 | if strings.HasSuffix(path, ".osu") {
71 | cpath := C.CString(path)
72 |
73 | defer C.free(unsafe.Pointer(cpath))
74 | if rc := C.ezpp(ez, cpath); rc < 0 {
75 | memory.MenuData.PP.PpStrains = []float64{0}
76 | return errors.New(C.GoString(C.errstr(rc)))
77 | }
78 | C.ezpp_set_base_ar(ez, C.float(memory.MenuData.Bm.Stats.MemoryAR))
79 | C.ezpp_set_base_od(ez, C.float(memory.MenuData.Bm.Stats.MemoryOD))
80 | C.ezpp_set_base_cs(ez, C.float(memory.MenuData.Bm.Stats.MemoryCS))
81 | C.ezpp_set_base_hp(ez, C.float(memory.MenuData.Bm.Stats.MemoryHP))
82 | C.ezpp_set_accuracy_percent(ez, C.float(memory.GameplayData.Accuracy))
83 | C.ezpp_set_mods(ez, C.int(memory.MenuData.Mods.AppliedMods))
84 | *data = PP{
85 | Artist: C.GoString(C.ezpp_artist(ez)),
86 | Title: C.GoString(C.ezpp_title(ez)),
87 | Version: C.GoString(C.ezpp_version(ez)),
88 | Creator: C.GoString(C.ezpp_creator(ez)),
89 | AR: C.ezpp_ar(ez),
90 | CS: C.ezpp_cs(ez),
91 | OD: C.ezpp_od(ez),
92 | HP: C.ezpp_hp(ez),
93 | StarRating: C.ezpp_stars(ez),
94 | }
95 | memory.MenuData.Bm.Stats.BeatmapSR = cast.ToFloat32(fmt.Sprintf("%.2f", float32(data.StarRating)))
96 | memory.MenuData.Bm.Stats.BeatmapAR = cast.ToFloat32(fmt.Sprintf("%.2f", float32(data.AR)))
97 | memory.MenuData.Bm.Stats.BeatmapCS = cast.ToFloat32(fmt.Sprintf("%.2f", float32(data.CS)))
98 | memory.MenuData.Bm.Stats.BeatmapOD = cast.ToFloat32(fmt.Sprintf("%.2f", float32(data.OD)))
99 | memory.MenuData.Bm.Stats.BeatmapHP = cast.ToFloat32(fmt.Sprintf("%.2f", float32(data.HP)))
100 |
101 | if needStrain == true {
102 | C.ezpp_set_end_time(ez, 0)
103 | C.ezpp_set_combo(ez, 0)
104 | C.ezpp_set_nmiss(ez, 0)
105 | memory.MenuData.Bm.Stats.BeatmapMaxCombo = int32(C.ezpp_max_combo(ez))
106 | memory.MenuData.Bm.Stats.FullSR = cast.ToFloat32(fmt.Sprintf("%.2f", float32(C.ezpp_stars(ez))))
107 | var bpmChanges []int
108 | var bpmMultiplier float64 = 1
109 | if strings.Contains(memory.MenuData.Mods.PpMods, "DT") || strings.Contains(memory.MenuData.Mods.PpMods, "NC") {
110 | bpmMultiplier = 1.5
111 | } else if strings.Contains(memory.MenuData.Mods.PpMods, "HT") {
112 | bpmMultiplier = 0.75
113 | }
114 | for i := 0; i < int(C.ezpp_ntiming_points(ez)); i++ {
115 | msPerBeat := float64(C.ezpp_timing_ms_per_beat(ez, C.int(i)))
116 | timingChanges := int(C.ezpp_timing_change(ez, C.int(i)))
117 | if timingChanges == 1 {
118 | bpmFormula := int(math.Round(1 / msPerBeat * 1000 * 60 * bpmMultiplier))
119 | if bpmFormula > 0 {
120 | bpmChanges = append(bpmChanges, bpmFormula)
121 | }
122 | }
123 | }
124 | memory.MenuData.Bm.Stats.BeatmapBPM.Minimal, memory.MenuData.Bm.Stats.BeatmapBPM.Maximal = minMax(bpmChanges)
125 | strainArray = nil
126 | seek := 0
127 | var window []float64
128 | var total []float64
129 | // for seek < int(C.ezpp_time_at(ez, C.ezpp_nobjects(ez)-1)) { //len-1
130 | for int32(seek) < memory.MenuData.Bm.Time.Mp3Time {
131 | for obj := 0; obj <= int(C.ezpp_nobjects(ez)-1); obj++ {
132 | if tempBeatmapFile != memory.MenuData.Bm.Path.BeatmapOsuFileString {
133 | return nil //Interrupt calcualtion if user has changed the map.
134 | }
135 | if int(C.ezpp_time_at(ez, C.int(obj))) >= seek && int(C.ezpp_time_at(ez, C.int(obj))) <= seek+3000 {
136 | window = append(window, float64(C.ezpp_strain_at(ez, C.int(obj), 0))+float64(C.ezpp_strain_at(ez, C.int(obj), 1)))
137 | }
138 | }
139 | sum := 0.0
140 | for _, num := range window {
141 | sum += num
142 | }
143 | total = append(total, sum/math.Max(float64(len(window)), 1))
144 | window = nil
145 | seek += 500
146 | }
147 | strainArray = total
148 | memory.MenuData.Bm.Time.FirstObj = int32(C.ezpp_time_at(ez, 0))
149 | memory.MenuData.Bm.Time.FullTime = int32(C.ezpp_time_at(ez, C.ezpp_nobjects(ez)-1))
150 | } else {
151 | C.ezpp_set_end_time(ez, C.float(memory.MenuData.Bm.Time.PlayTime))
152 | currMaxCombo = C.ezpp_max_combo(ez) //for RestSS
153 | C.ezpp_set_combo(ez, C.int(memory.GameplayData.Combo.Max))
154 | C.ezpp_set_nmiss(ez, C.int(memory.GameplayData.Hits.H0))
155 | }
156 |
157 | *data = PP{
158 | Total: C.ezpp_pp(ez),
159 | Strain: strainArray,
160 |
161 | AimStars: C.ezpp_aim_stars(ez),
162 | SpeedStars: C.ezpp_speed_stars(ez),
163 | AimPP: C.ezpp_aim_pp(ez),
164 | SpeedPP: C.ezpp_speed_pp(ez),
165 | Accuracy: C.ezpp_accuracy_percent(ez),
166 | N300: C.ezpp_n300(ez),
167 | N100: C.ezpp_n100(ez),
168 | N50: C.ezpp_n50(ez),
169 | NMiss: C.ezpp_nmiss(ez),
170 | //ArtistUnicode: C.GoString(C.ezpp_artist_unicode(ez)),
171 | // TitleUnicode: C.GoString(C.ezpp_title_unicode(ez)),
172 | NCircles: C.ezpp_ncircles(ez),
173 | NSliders: C.ezpp_nsliders(ez),
174 | NSpinners: C.ezpp_nspinners(ez),
175 | ODMS: C.ezpp_odms(ez),
176 | Mode: C.ezpp_mode(ez),
177 | Combo: C.ezpp_combo(ez),
178 | MaxCombo: C.ezpp_max_combo(ez),
179 | Mods: C.ezpp_mods(ez),
180 | ScoreVersion: C.ezpp_score_version(ez),
181 | }
182 | memory.MenuData.PP.PpStrains = data.Strain
183 | }
184 | return nil
185 | }
186 |
187 | var maniaSR float64
188 | var maniaHitObjects float64
189 | var tempMods string
190 |
191 | func GetData() {
192 |
193 | ez = C.ezpp_new()
194 | C.ezpp_set_autocalc(ez, 1)
195 | //defer C.ezpp_free(ez)
196 |
197 | for {
198 |
199 | if memory.DynamicAddresses.IsReady == true {
200 | switch memory.MenuData.GameMode {
201 | case 0, 1:
202 | var data PP
203 | if tempBeatmapFile != memory.MenuData.Bm.Path.BeatmapOsuFileString || memory.MenuData.Mods.PpMods != tempMods || memory.MenuData.GameMode != tempGameMode { //On map/mods change
204 | tempGameMode = memory.MenuData.GameMode // looks very ugly but will rewrite everything in 1.4.0
205 | tempBadJudgments = 0
206 | path := memory.MenuData.Bm.Path.FullDotOsu
207 | tempBeatmapFile = memory.MenuData.Bm.Path.BeatmapOsuFileString
208 | tempMods = memory.MenuData.Mods.PpMods
209 | tempGameMode = memory.MenuData.GameMode
210 | mp3Time, err := calculateMP3Time()
211 | if err == nil {
212 | memory.MenuData.Bm.Time.Mp3Time = mp3Time
213 | }
214 | //Get Strains
215 | readData(&data, ez, true, path)
216 |
217 | //pp.Println(memory.MenuData.Bm.Metadata)
218 | }
219 |
220 | switch memory.MenuData.OsuStatus {
221 | case 2:
222 | path := memory.MenuData.Bm.Path.FullDotOsu
223 | readData(&data, ez, false, path)
224 | if memory.GameplayData.Combo.Max > 1 && float64(data.Total) > 0 {
225 | //pre-Wieku rewrite crutch
226 | if memory.GameplayData.GameMode == 0 {
227 |
228 | res, err := wiekuCalcCrutch(path, memory.GameplayData.Combo.Max, memory.GameplayData.Hits.H300, memory.GameplayData.Hits.H100, memory.GameplayData.Hits.H50, memory.GameplayData.Hits.H0)
229 | if err != nil {
230 | pp.Println(err)
231 | memory.GameplayData.PP.Pp = cast.ToInt32(float64(data.Total))
232 | }
233 | memory.GameplayData.PP.Pp = cast.ToInt32(res)
234 |
235 | } else {
236 | memory.GameplayData.PP.Pp = cast.ToInt32(float64(data.Total))
237 | }
238 | }
239 | case 7:
240 | //idle
241 | case 5:
242 | memory.GameplayData.PP.Pp = 0
243 | }
244 |
245 | case 3:
246 |
247 | if tempBeatmapFile != memory.MenuData.Bm.Path.BeatmapOsuFileString || memory.MenuData.Mods.PpMods != tempMods || memory.MenuData.GameMode != tempGameMode { //On map/mods/mode change
248 | tempGameMode = memory.MenuData.GameMode // looks very ugly but will rewrite everything in 1.4.0
249 | tempBeatmapFile = memory.MenuData.Bm.Path.BeatmapOsuFileString
250 | tempMods = memory.MenuData.Mods.PpMods
251 |
252 | maniaSR = 0.0
253 | memory.MenuData.Bm.Time.FullTime = 0 //Not implemented for mania yet
254 | memory.MenuData.Bm.Stats.BeatmapAR = 0 //Not implemented for mania yet
255 | memory.MenuData.Bm.Stats.BeatmapCS = 0 //Not implemented for mania yet
256 | memory.MenuData.Bm.Stats.BeatmapOD = 0 //Not implemented for mania yet
257 | memory.MenuData.Bm.Stats.BeatmapHP = 0 //Not implemented for mania yet
258 | memory.MenuData.PP.PpStrains = []float64{0} //Not implemented for mania yet
259 |
260 | maniaStars, err := memory.ReadManiaStars()
261 | if err != nil {
262 | pp.Println(err)
263 | }
264 | if maniaStars.NoMod == 0 { //diff calc in progress
265 | for i := 0; i < 50; i++ {
266 | maniaStars, _ = memory.ReadManiaStars()
267 | if maniaStars.NoMod > 0 {
268 | break
269 | }
270 | time.Sleep(100 * time.Millisecond)
271 | }
272 | }
273 |
274 | maniaHitObjects = float64(memory.MenuData.Bm.Stats.TotalHitObjects)
275 |
276 | if strings.Contains(memory.MenuData.Mods.PpMods, "DT") {
277 | maniaSR = maniaStars.DT
278 | } else if strings.Contains(memory.MenuData.Mods.PpMods, "HT") {
279 | maniaSR = maniaStars.HT
280 | } else {
281 | maniaSR = maniaStars.NoMod //assuming NM
282 | }
283 | memory.MenuData.Bm.Stats.BeatmapSR = cast.ToFloat32(fmt.Sprintf("%.2f", float32(maniaSR)))
284 | memory.MenuData.Bm.Stats.FullSR = memory.MenuData.Bm.Stats.BeatmapSR
285 | memory.MenuData.PP.PpSS = int32(calculateManiaPP(float64(memory.MenuData.Bm.Stats.MemoryOD), maniaSR, maniaHitObjects, 1000000.0)) // LiveSR not implemented yet
286 | }
287 | }
288 | if memory.GameplayData.GameMode == 3 {
289 | if maniaSR > 0 {
290 | memory.GameplayData.PP.PPifFC = int32(calculateManiaPP(float64(memory.MenuData.Bm.Stats.MemoryOD), maniaSR, maniaHitObjects, 1000000.0)) //PP if SS
291 | if memory.GameplayData.Score >= 500000 {
292 | memory.GameplayData.PP.Pp = int32(calculateManiaPP(float64(memory.MenuData.Bm.Stats.MemoryOD), maniaSR, maniaHitObjects, float64(memory.GameplayData.Score)))
293 | } else {
294 | memory.GameplayData.PP.Pp = 0
295 | }
296 | }
297 | }
298 | }
299 |
300 | time.Sleep(time.Duration(memory.UpdateTime) * time.Millisecond)
301 | }
302 | }
303 |
304 | var (
305 | tempWiekuFileName string
306 | tempWiekuMods int32
307 | beatMap *beatmap.BeatMap
308 | attribs []osu.Attributes
309 | )
310 |
311 | func wiekuCalcCrutch(path string, combo int16, h300 int16, h100 int16, h50 int16, h0 int16) (int32, error) {
312 | if tempWiekuFileName != path || tempWiekuMods != memory.MenuData.Mods.AppliedMods {
313 | tempWiekuFileName = path
314 | tempWiekuMods = memory.MenuData.Mods.AppliedMods
315 |
316 | osuFile, err := os.Open(path)
317 | if err != nil {
318 | return 0, fmt.Errorf("Failed to calc via wieku calculator, falling back to oppai, ERROR: %w", err)
319 | }
320 | defer osuFile.Close()
321 |
322 | beatMap, err = beatmap.ParseFromReader(osuFile)
323 | if err != nil {
324 | return 0, fmt.Errorf("Failed to calc via wieku calculator, falling back to oppai, ERROR: %w", err)
325 | }
326 |
327 | beatMap.Difficulty.SetMods(difficulty.Modifier(memory.MenuData.Mods.AppliedMods))
328 | attribs = osu.CalculateStep(beatMap.HitObjects, beatMap.Difficulty)
329 | }
330 |
331 | ppWieku := &osu.PPv2{}
332 |
333 | currAttrib := int(math.Max(0, float64(h300+h100+h50+h0-1)))
334 |
335 | if len(attribs)-1 < currAttrib {
336 | return 0, nil //rade condition hell
337 | }
338 |
339 | ppWieku.PPv2x(attribs[currAttrib], int(combo), int(h300), int(h100), int(h50), int(h0), beatMap.Difficulty)
340 |
341 | return cast.ToInt32(ppWieku.Results.Total), nil
342 | }
343 |
--------------------------------------------------------------------------------
/updater/updater.go:
--------------------------------------------------------------------------------
1 | package updater
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | "path/filepath"
8 | "time"
9 |
10 | "github.com/blang/semver"
11 | "github.com/rhysd/go-github-selfupdate/selfupdate"
12 | "github.com/skratchdot/open-golang/open"
13 | )
14 |
15 | const version = "1.3.9"
16 |
17 | // DoSelfUpdate updates the application
18 | func DoSelfUpdate() {
19 | fmt.Println("Checking Updates... (can take some time if you have bad routing to GitHub)")
20 | name, err := os.Executable()
21 | if err != nil {
22 | log.Fatalln(err)
23 | }
24 | v := semver.MustParse(version)
25 | latest, err := selfupdate.UpdateSelf(v, "l3lackShark/gosumemory")
26 | if err != nil {
27 | log.Println("Binary update failed:", err)
28 | return
29 | }
30 | if latest.Version.Equals(v) {
31 | // latest version is the same as current version. It means current binary is up to date.
32 | log.Println("Current binary is the latest version", version)
33 | log.Println("Release notes:\n", latest.ReleaseNotes)
34 | full, _ := os.Executable()
35 | path, executable := filepath.Split(full)
36 | oldName := filepath.Join(path, "."+executable+".old")
37 | os.Remove(oldName)
38 | } else {
39 | log.Println("Successfully updated to version", latest.Version)
40 | log.Println("Release notes:\n", latest.ReleaseNotes)
41 | time.Sleep(3 * time.Second)
42 | open.Start(name)
43 | os.Exit(0)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/web/structure.go:
--------------------------------------------------------------------------------
1 | package web
2 |
3 | import (
4 | "encoding/json"
5 | "time"
6 |
7 | "github.com/k0kubun/pp"
8 | "github.com/l3lackShark/gosumemory/memory"
9 | )
10 |
11 | //SetupStructure sets up ws and json output
12 | func SetupStructure() {
13 | var err error
14 | type wsStruct struct { //order sets here
15 | A memory.InSettingsValues `json:"settings"`
16 | B memory.InMenuValues `json:"menu"`
17 | C memory.GameplayValues `json:"gameplay"`
18 | D memory.ResultsScreenValues `json:"resultsScreen"`
19 | E memory.TourneyValues `json:"tourney"`
20 | }
21 | for {
22 | group := wsStruct{
23 | A: memory.SettingsData,
24 | B: memory.MenuData,
25 | C: memory.GameplayData,
26 | D: memory.ResultsScreenData,
27 | E: memory.TourneyData,
28 | }
29 |
30 | JSONByte, err = json.Marshal(group)
31 | if err != nil {
32 | pp.Println("JSON Marshall error: ", err, group)
33 | }
34 | time.Sleep(time.Duration(memory.UpdateTime) * time.Millisecond)
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/web/web.go:
--------------------------------------------------------------------------------
1 | package web
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | "os"
8 | "path/filepath"
9 | "time"
10 |
11 | "github.com/gorilla/websocket"
12 | "github.com/l3lackShark/gosumemory/config"
13 | "github.com/l3lackShark/gosumemory/memory"
14 | "github.com/spf13/cast"
15 | )
16 |
17 | //JSONByte contains data that will be sent to the client
18 | var JSONByte []byte
19 |
20 | var upgrader = websocket.Upgrader{
21 | CheckOrigin: func(r *http.Request) bool {
22 | return true
23 | },
24 | }
25 |
26 | func reader(conn *websocket.Conn) {
27 | for {
28 | messageType, p, err := conn.ReadMessage()
29 | if err != nil {
30 | log.Println(err)
31 | return
32 | }
33 | fmt.Println(string(p))
34 |
35 | if err := conn.WriteMessage(messageType, p); err != nil {
36 | log.Println(err)
37 | return
38 | }
39 |
40 | }
41 | }
42 |
43 | func wsEndpoint(w http.ResponseWriter, r *http.Request) {
44 | if cast.ToBool(config.Config["cors"]) {
45 | enableCors(&w)
46 | }
47 | ws, err := upgrader.Upgrade(w, r, nil)
48 | if err != nil {
49 | log.Println(err)
50 | }
51 | for {
52 | if memory.DynamicAddresses.IsReady == true {
53 | ws.WriteMessage(1, []byte(JSONByte)) //sending data to the client
54 |
55 | }
56 | time.Sleep(time.Duration(memory.UpdateTime) * time.Millisecond)
57 | }
58 |
59 | }
60 |
61 | //SetupRoutes creates websocket connection
62 | func SetupRoutes() {
63 | http.HandleFunc("/ws", wsEndpoint)
64 | }
65 |
66 | func enableCors(w *http.ResponseWriter) {
67 | (*w).Header().Set("Access-Control-Allow-Origin", "*")
68 | }
69 |
70 | //HTTPServer handles json and static files output
71 | func HTTPServer() {
72 |
73 | for memory.DynamicAddresses.IsReady != true {
74 | time.Sleep(100 * time.Millisecond)
75 | }
76 | ex, err := os.Executable()
77 | if err != nil {
78 | panic(err)
79 | }
80 | exPath := filepath.Dir(ex)
81 | fs := http.FileServer(http.Dir(filepath.Join(exPath, "static")))
82 |
83 | http.Handle("/", fs)
84 |
85 | var songsOrigin = http.StripPrefix("/Songs/", http.FileServer(http.Dir(memory.SongsFolderPath)))
86 | var songsWrapped = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
87 | if cast.ToBool(config.Config["cors"]) {
88 | enableCors(&w)
89 | }
90 | songsOrigin.ServeHTTP(w, r)
91 | })
92 | http.Handle("/Songs/", songsWrapped)
93 |
94 | http.HandleFunc("/json", handler)
95 | err = http.ListenAndServe(config.Config["serverip"], nil)
96 | if err != nil {
97 | fmt.Println(err)
98 | time.Sleep(5 * time.Second)
99 | log.Fatalln(err)
100 | }
101 | }
102 | func handler(w http.ResponseWriter, r *http.Request) {
103 | if memory.DynamicAddresses.IsReady == true {
104 | w.Header().Set("Content-Type", "application/json")
105 | fmt.Fprint(w, string(JSONByte))
106 |
107 | } else {
108 | fmt.Fprintf(w, `{"error": "osu! is not fully loaded!"}`)
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------