Skip to content

Commit 37d96da

Browse files
committed
implement isInitialized method, improve address search, version 1.0.4
1 parent 39ad4cc commit 37d96da

File tree

9 files changed

+185
-29
lines changed

9 files changed

+185
-29
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ repositories {
1414
}
1515
1616
dependencies {
17-
implementation 'com.github.LabyStudio:java-spotify-api:1.0.4:all'
17+
implementation 'com.github.LabyStudio:java-spotify-api:1.0.5:all'
1818
}
1919
```
2020

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
}
55

66
group 'de.labystudio'
7-
version '1.0.4'
7+
version '1.0.5'
88

99
compileJava {
1010
sourceCompatibility = '1.8'

src/main/java/de/labystudio/spotifyapi/SpotifyAPI.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ default boolean hasTrack() {
7878
*/
7979
boolean isConnected();
8080

81+
82+
/**
83+
* Returns true if the background process is running.
84+
*
85+
* @return true if the background process is running
86+
*/
87+
boolean isInitialized();
88+
8189
/**
8290
* Registers a listener to be notified about changes.
8391
*

src/main/java/de/labystudio/spotifyapi/platform/osx/OSXSpotifyApi.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ public SpotifyAPI initialize() {
2020
return this;
2121
}
2222

23+
@Override
24+
public boolean isInitialized() {
25+
return false;
26+
}
27+
2328
@Override
2429
public Track getTrack() {
2530
return null; // TODO Implement OSX SpotifyAPI

src/main/java/de/labystudio/spotifyapi/platform/windows/WinSpotifyAPI.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class WinSpotifyAPI extends AbstractSpotifyAPI {
4040
* @throws IllegalStateException if the API is already initialized
4141
*/
4242
public SpotifyAPI initialize() {
43-
if (this.task != null) {
43+
if (this.isInitialized()) {
4444
throw new IllegalStateException("The SpotifyAPI is already initialized");
4545
}
4646

@@ -158,6 +158,11 @@ public boolean isConnected() {
158158
return this.process != null && this.process.isOpen();
159159
}
160160

161+
@Override
162+
public boolean isInitialized() {
163+
return this.task != null;
164+
}
165+
161166
@Override
162167
public void stop() {
163168
if (this.task != null) {

src/main/java/de/labystudio/spotifyapi/platform/windows/api/WinProcess.java

Lines changed: 111 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
*/
1818
public class WinProcess implements WinApi {
1919

20-
private final int processId;
21-
private final WinNT.HANDLE handle;
22-
private final WinDef.HWND window;
20+
protected final int processId;
21+
protected final WinNT.HANDLE handle;
22+
protected final WinDef.HWND window;
23+
24+
protected final long maxContentAddress;
2325

2426
/**
2527
* Creates a new instance of the {@link WinProcess} class.
@@ -42,6 +44,8 @@ public WinProcess(String executableName) {
4244
if (this.getWindowTitle().isEmpty()) {
4345
throw new IllegalStateException("Window for process " + this.processId + " not found");
4446
}
47+
48+
this.maxContentAddress = this.getModuleAddress("wow64cpu.dll");
4549
}
4650

4751
/**
@@ -105,15 +109,15 @@ public byte[] readBytes(long address, int length) {
105109
* <p>
106110
* It will return -1 if no address was found.
107111
*
108-
* @param address The address to start searching from.
109-
* @param size The maximum amount of bytes to search.
112+
* @param minAddress The minimum address to start searching from.
113+
* @param maxAddress The maximum address to stop searching at.
110114
* @param searchBytes The bytes to search for.
111115
* @return The address of the first matching bytes.
112116
*/
113-
public long findInMemory(long address, long size, byte[] searchBytes) {
117+
public long findInMemory(long minAddress, long maxAddress, byte[] searchBytes) {
114118
int chunkSize = 1024 * 64;
115119

116-
for (long cursor = address; cursor < (address + size); cursor += chunkSize) {
120+
for (long cursor = minAddress; cursor < maxAddress; cursor += chunkSize) {
117121
byte[] chunk = this.readBytes(cursor, chunkSize + searchBytes.length);
118122

119123
for (int i = 0; i < chunk.length - searchBytes.length; i++) {
@@ -139,15 +143,14 @@ public long findInMemory(long address, long size, byte[] searchBytes) {
139143
* <p>
140144
* It will return -1 if no address was found.
141145
*
142-
* @param address The address to start searching from.
143-
* @param size The maximum amount of bytes to search.
146+
* @param minAddress The address to start searching from.
147+
* @param maxAddress The address to stop searching at.
144148
* @param searchBytes The bytes to search for.
145149
* @param condition The condition function to call for each matching address.
146150
* @return The address of the first matching bytes.
147151
*/
148-
public long findInMemory(long address, long size, byte[] searchBytes, SearchCondition condition) {
149-
long cursor = address;
150-
long maxAddress = size - address;
152+
public long findInMemory(long minAddress, long maxAddress, byte[] searchBytes, SearchCondition condition) {
153+
long cursor = minAddress;
151154
int index = 0;
152155
while (cursor < maxAddress) {
153156
long target = this.findInMemory(cursor, maxAddress, searchBytes);
@@ -160,6 +163,23 @@ public long findInMemory(long address, long size, byte[] searchBytes, SearchCond
160163
return -1;
161164
}
162165

166+
/**
167+
* Check if the given bytes are at the given address.
168+
*
169+
* @param address The address to check.
170+
* @param bytes The bytes to check.
171+
* @return True if the bytes are at the given address.
172+
*/
173+
public boolean hasBytes(long address, int... bytes) {
174+
byte[] chunk = this.readBytes(address, bytes.length);
175+
for (int i = 0; i < chunk.length; i++) {
176+
if (chunk[i] != (byte) bytes[i]) {
177+
return false;
178+
}
179+
}
180+
return true;
181+
}
182+
163183
/**
164184
* Find the address of a text inside the memory.
165185
* If there are multiple matches of the text, the given index will be used to select the correct one.
@@ -170,8 +190,20 @@ public long findInMemory(long address, long size, byte[] searchBytes, SearchCond
170190
* @return The address of the text at the given index
171191
*/
172192
public long findAddressOfText(long start, String text, int index) {
173-
long maxAddress = this.getMaxProcessAddress();
174-
return this.findInMemory(start, maxAddress, text.getBytes(), (address, matchIndex) -> matchIndex == index);
193+
return this.findAddressOfText(start, text, (address, matchIndex) -> matchIndex == index);
194+
}
195+
196+
/**
197+
* Find the address of a text inside the memory.
198+
* If there are multiple matches of the text, the given index will be used to select the correct one.
199+
*
200+
* @param start The address to start searching from.
201+
* @param text The text to search for.
202+
* @param condition The condition function to call for each matching address.
203+
* @return The address of the text at the given index
204+
*/
205+
public long findAddressOfText(long start, String text, SearchCondition condition) {
206+
return this.findInMemory(start, this.maxContentAddress, text.getBytes(), condition);
175207
}
176208

177209
/**
@@ -192,6 +224,43 @@ public long findAddressUsingPath(String... path) {
192224
return cursor;
193225
}
194226

227+
/**
228+
* Find an address by matching multiple rules inside the memory.
229+
* It will start searching for the first rule and continues the next at the position where the previous match was found.
230+
*
231+
* @param rules The list of rules to search for.
232+
* @return The final addresses after all rules were matched.
233+
*/
234+
public long findAddressUsingRules(SearchRule... rules) {
235+
long cursor = -1;
236+
for (SearchRule rule : rules) {
237+
cursor = this.findAddressOfText(cursor + 1, rule.getText(), rule.getCondition());
238+
if (cursor == -1) {
239+
return -1;
240+
}
241+
}
242+
return cursor;
243+
}
244+
245+
/**
246+
* Find the base address of the given module name.
247+
*
248+
* @param moduleName The name of the module.
249+
* @return The base address of the module.
250+
*/
251+
public long getModuleAddress(String moduleName) {
252+
return this.getModuleAddress(this.processId, moduleName);
253+
}
254+
255+
/**
256+
* Collect all module addresses and their size of the given process.
257+
*
258+
* @return A list of module addresses and their size.
259+
*/
260+
public Map<Long, Long> getModules() {
261+
return this.getModules(this.processId);
262+
}
263+
195264
/**
196265
* Find the first address of the modules.
197266
*
@@ -262,6 +331,15 @@ public void close() {
262331
Kernel32.INSTANCE.CloseHandle(this.handle);
263332
}
264333

334+
/**
335+
* Get the highest memory address with relevant content.
336+
*
337+
* @return The highest memory address with relevant content.
338+
*/
339+
public long getMaxContentAddress() {
340+
return this.maxContentAddress;
341+
}
342+
265343
/**
266344
* Search condition function to check if the address matches additional criteria.
267345
*/
@@ -277,4 +355,23 @@ public interface SearchCondition {
277355
boolean matches(long address, int index);
278356
}
279357

358+
public static class SearchRule {
359+
360+
private final String text;
361+
private final SearchCondition condition;
362+
363+
public SearchRule(String text, SearchCondition condition) {
364+
this.text = text;
365+
this.condition = condition;
366+
}
367+
368+
public String getText() {
369+
return this.text;
370+
}
371+
372+
public SearchCondition getCondition() {
373+
return this.condition;
374+
}
375+
}
376+
280377
}

src/main/java/de/labystudio/spotifyapi/platform/windows/api/playback/PlaybackAccessor.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package de.labystudio.spotifyapi.platform.windows.api.playback;
22

3-
import de.labystudio.spotifyapi.platform.windows.api.spotify.SpotifyProcess;
3+
import de.labystudio.spotifyapi.platform.windows.api.WinProcess;
44

55
/**
66
* Accessor to read the duration, position and playing state from the Spotify process.
@@ -12,7 +12,7 @@ public class PlaybackAccessor {
1212
private static final long MIN_TRACK_DURATION = 1000; // 1 second
1313
private static final long MAX_TRACK_DURATION = 1000 * 60 * 10; // 10 minutes
1414

15-
private final SpotifyProcess process;
15+
private final WinProcess process;
1616
private final PointerRegistry pointerRegistry;
1717

1818
private int length;
@@ -25,7 +25,7 @@ public class PlaybackAccessor {
2525
* @param process The Spotify process to read from.
2626
* @param contextBaseAddress The base address of the context.
2727
*/
28-
public PlaybackAccessor(SpotifyProcess process, long contextBaseAddress) {
28+
public PlaybackAccessor(WinProcess process, long contextBaseAddress) {
2929
this.process = process;
3030

3131
// Create pointer registry to calculate the absolute addresses using the relative offsets

src/main/java/de/labystudio/spotifyapi/platform/windows/api/spotify/SpotifyProcess.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*/
1111
public class SpotifyProcess extends WinProcess {
1212

13+
private static final boolean DEBUG = false;
1314
private static final byte[] PREFIX_CONTEXT = new byte[]{0x63, 0x6F, 0x6E, 0x74, 0x65, 0x78, 0x74};
1415

1516
private final long addressTrackId;
@@ -28,27 +29,32 @@ public class SpotifyProcess extends WinProcess {
2829
public SpotifyProcess() {
2930
super("Spotify.exe");
3031

31-
// Get the highest addresses of the modules
32-
long highestAddress = this.getMaxProcessAddress();
32+
if (DEBUG) {
33+
System.out.println("Spotify process loaded! Searching for addresses...");
34+
}
35+
36+
long timeScanStart = System.currentTimeMillis();
3337

3438
// Find addresses of playback states (Located in the chrome_elf.dll module)
35-
this.addressTrackId = this.findAddressUsingPath(
36-
"This program cannot be run in DOS mode",
37-
"This program cannot be run in DOS mode",
38-
"chrome_elf.dll",
39-
"spotify:track:"
39+
this.addressTrackId = this.findAddressOfText(
40+
this.maxContentAddress / 2,
41+
"spotify:track:",
42+
(address, index) -> this.hasBytes(address + 1028, 0xDC, 0xA1)
4043
);
4144
if (this.addressTrackId == -1 || !this.isTrackIdValid(this.getTrackId())) {
4245
throw new IllegalStateException("Could not find track id in memory");
4346
}
47+
if (DEBUG) {
48+
System.out.println("Found track id address: " + Long.toHexString(this.addressTrackId));
49+
}
4450

4551
// Check if the song is currently playing using the title bar
4652
boolean isPlaying = this.isPlayingUsingTitle();
4753

48-
// Find addresses of track id
54+
// Find addresses of track id;
4955
this.addressPlayBack = this.findInMemory(
5056
0,
51-
highestAddress,
57+
this.addressTrackId,
5258
PREFIX_CONTEXT,
5359
(address, index) -> {
5460
PlaybackAccessor accessor = new PlaybackAccessor(this, address);
@@ -58,6 +64,10 @@ public SpotifyProcess() {
5864
if (this.addressPlayBack == -1) {
5965
throw new IllegalStateException("Could not find playback in memory");
6066
}
67+
if (DEBUG) {
68+
System.out.println("Found playback address at: " + Long.toHexString(this.addressPlayBack));
69+
System.out.println("Scanning took " + (System.currentTimeMillis() - timeScanStart) + "ms");
70+
}
6171

6272
// Create the playback accessor with the found address
6373
this.playbackAccessor = new PlaybackAccessor(this, this.addressPlayBack);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import de.labystudio.spotifyapi.platform.windows.api.WinProcess;
2+
import de.labystudio.spotifyapi.platform.windows.api.playback.PlaybackAccessor;
3+
4+
public class SpotifyProcessTest {
5+
6+
private static final byte[] PREFIX_CONTEXT = new byte[]{0x63, 0x6F, 0x6E, 0x74, 0x65, 0x78, 0x74};
7+
8+
public static void main(String[] args) {
9+
WinProcess process = new WinProcess("Spotify.exe");
10+
11+
long addressTrackId = process.findAddressOfText(
12+
process.getMaxContentAddress() / 2,
13+
"spotify:track:",
14+
(address, index) -> process.hasBytes(address + 1028, 0xDC, 0xA1)
15+
);
16+
System.out.println("Track Id Address: " + Long.toHexString(addressTrackId));
17+
18+
long addressPlayBack = process.findInMemory(
19+
0,
20+
addressTrackId,
21+
PREFIX_CONTEXT,
22+
(address, index) -> {
23+
PlaybackAccessor accessor = new PlaybackAccessor(process, address);
24+
return accessor.isValid();
25+
}
26+
);
27+
System.out.println("Playback Address: " + Long.toHexString(addressPlayBack));
28+
29+
}
30+
31+
}

0 commit comments

Comments
 (0)