package it.paspiz85.nanobot.platform.win;
import it.paspiz85.nanobot.exception.BotConfigurationException;
import it.paspiz85.nanobot.platform.AbstractPlatform;
import it.paspiz85.nanobot.platform.Platform;
import it.paspiz85.nanobot.platform.win.KeyboardMapping.Key;
import it.paspiz85.nanobot.util.OS;
import it.paspiz85.nanobot.util.Point;
import it.paspiz85.nanobot.util.Size;
import it.paspiz85.nanobot.util.Utils;
import java.awt.AWTException;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.awt.im.InputContext;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.logging.Level;
import org.jnativehook.GlobalScreen;
import org.jnativehook.mouse.NativeMouseEvent;
import org.jnativehook.mouse.NativeMouseListener;
import com.sun.jna.platform.win32.Advapi32Util;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef.LPARAM;
import com.sun.jna.platform.win32.WinDef.POINT;
import com.sun.jna.platform.win32.WinDef.WPARAM;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.WinReg;
import com.sun.jna.platform.win32.WinReg.HKEYByReference;
/**
* Platform implementation of BlueStacks for Windows.
*
* @author paspiz85
*
*/
public class BlueStacksWinPlatform extends AbstractPlatform {
public static final Size BS_SIZE = new Size(Platform.GAME_SIZE.x(), Platform.GAME_SIZE.y() + 47);
private static final String BS_WINDOW_NAME = "BlueStacks App Player";
private static final int SWP_NOMOVE = 0x0002;
private static final int SWP_NOSIZE = 0x0001;
private static final int TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE;
private static final int WM_KEYDOWN = 0x100;
private static final int WM_KEYUP = 0x101;
private static final int WM_LBUTTONDOWN = 0x201;
private static final int WM_LBUTTONUP = 0x202;
public static BlueStacksWinPlatform instance() {
return Utils.singleton(BlueStacksWinPlatform.class, () -> new BlueStacksWinPlatform());
}
public static boolean isSupported() {
final OS os = OS.getCurrent();
return os.getFamily() == OS.Family.WINDOWS && os.getVersion().getMajor() < 10;
}
private HWND handler;
private final Robot robot;
protected BlueStacksWinPlatform() {
try {
robot = new Robot();
} catch (final AWTException e) {
throw new IllegalStateException("Unable to init robot", e);
}
}
private Point clientToScreen(final Point clientPoint) {
final POINT point = new POINT(clientPoint.x(), clientPoint.y());
User32.INSTANCE.ClientToScreen(handler, point);
return new Point(point.x, point.y);
}
@Override
protected void doApplySize(final Size size) throws BotConfigurationException {
final int width = size.x();
final int height = size.y();
final HKEYByReference key = Advapi32Util.registryGetKey(WinReg.HKEY_LOCAL_MACHINE,
"SOFTWARE\\BlueStacks\\Guests\\Android\\FrameBuffer\\0", WinNT.KEY_READ | WinNT.KEY_WRITE);
final int w1 = Advapi32Util.registryGetIntValue(key.getValue(), "WindowWidth");
final int h1 = Advapi32Util.registryGetIntValue(key.getValue(), "WindowHeight");
final int w2 = Advapi32Util.registryGetIntValue(key.getValue(), "GuestWidth");
final int h2 = Advapi32Util.registryGetIntValue(key.getValue(), "GuestHeight");
if (w1 != width || h1 != height || w2 != width || h2 != height) {
Advapi32Util.registrySetIntValue(key.getValue(), "WindowWidth", width);
Advapi32Util.registrySetIntValue(key.getValue(), "WindowHeight", height);
Advapi32Util.registrySetIntValue(key.getValue(), "GuestWidth", width);
Advapi32Util.registrySetIntValue(key.getValue(), "GuestHeight", height);
Advapi32Util.registrySetIntValue(key.getValue(), "FullScreen", 0);
}
throw new BotConfigurationException(String.format("Please restart %s to fix resolution", BS_WINDOW_NAME));
}
@Override
protected void doKeyPress(final int keyCode, final boolean shifted) throws InterruptedException {
logger.log(Level.FINER, "doKeyPress " + keyCode + " " + shifted);
while (isCtrlKeyDown()) {
Thread.sleep(100);
}
final int lParam = 0x00000001 | 0x50 /* scancode */<< 16 | 0x01000000 /* extended */;
final WPARAM wparam = new WinDef.WPARAM(keyCode);
final WPARAM wparamShift = new WinDef.WPARAM(KeyEvent.VK_SHIFT);
final LPARAM lparamDown = new WinDef.LPARAM(lParam);
final LPARAM lparamUp = new WinDef.LPARAM(lParam | 1 << 30 | 1 << 31);
if (shifted) {
User32.INSTANCE.PostMessage(handler, WM_KEYDOWN, wparamShift, lparamDown);
}
User32.INSTANCE.PostMessage(handler, WM_KEYDOWN, wparam, lparamDown);
User32.INSTANCE.PostMessage(handler, WM_KEYUP, wparam, lparamUp);
if (shifted) {
User32.INSTANCE.PostMessage(handler, WM_KEYUP, wparamShift, lparamUp);
}
}
@Override
protected void doLeftClick(final Point point) throws InterruptedException {
final int x = point.x();
final int y = point.y();
final int lParam = y << 16 | x << 16 >>> 16;
while (isCtrlKeyDown()) {
Thread.sleep(100);
}
User32.INSTANCE.SendMessage(handler, WM_LBUTTONDOWN, 0x00000001, lParam);
User32.INSTANCE.SendMessage(handler, WM_LBUTTONUP, 0x00000000, lParam);
}
@Override
protected BufferedImage doScreenshot(final Point p1, final Point p2) {
final Point anchor = clientToScreen(p1);
final int width = p2.x() - p1.x();
final int height = p2.y() - p1.y();
return robot.createScreenCapture(new Rectangle(anchor.x(), anchor.y(), width, height));
}
@Override
protected void doSingleZoomUp() throws InterruptedException {
doKeyPress(KeyEvent.VK_DOWN, false);
}
@Override
protected void doWrite(final String s) throws InterruptedException {
final char[] chars = s.toCharArray();
for (final char ch : chars) {
if (Character.isLetter(ch)) {
if (Character.isUpperCase(ch)) {
doKeyPress(ch, true);
} else {
doKeyPress(Character.toUpperCase(ch), false);
}
} else if (Character.isDigit(ch)) {
doKeyPress(ch, false);
} else {
Key key = null;
final KeyboardMapping mapping = KeyboardMapping.get(InputContext.getInstance().getLocale());
if (mapping != null) {
key = mapping.getKey(ch);
}
if (key != null) {
doKeyPress(key.getCode(), key.isShifted());
} else {
logger.log(Level.WARNING, "Unable to write character '" + ch + "'");
}
}
sleepRandom(60);
}
}
@Override
protected Size getActualSize() {
final HWND control = User32.INSTANCE.GetDlgItem(handler, 0);
final int[] rect = new int[4];
User32.INSTANCE.GetWindowRect(control, rect);
final Size bsSize = new Size(rect[2] - rect[0], rect[3] - rect[1]);
return bsSize;
}
@Override
protected Color getColor(final Point point) {
final Point screenPoint = clientToScreen(point);
return robot.getPixelColor(screenPoint.x(), screenPoint.y());
}
@Override
public Size getExpectedSize() {
return BS_SIZE;
}
private boolean isCtrlKeyDown() {
return User32.INSTANCE.GetKeyState(KeyEvent.VK_CONTROL) < 0;
}
@Override
protected boolean registerForClick(final Consumer<Point> clickConsumer) {
boolean result;
try {
GlobalScreen.registerNativeHook();
GlobalScreen.getInstance().addNativeMouseListener(new NativeMouseListener() {
@Override
public void nativeMouseClicked(final NativeMouseEvent e) {
final int x = e.getX();
final int y = e.getY();
logger.finest(String.format("clicked %d %d", e.getX(), e.getY()));
final POINT screenPoint = new POINT(x, y);
User32.INSTANCE.ScreenToClient(handler, screenPoint);
clickConsumer.accept(new Point(screenPoint.x, screenPoint.y));
}
@Override
public void nativeMousePressed(final NativeMouseEvent e) {
}
@Override
public void nativeMouseReleased(final NativeMouseEvent e) {
}
});
result = true;
} catch (final Exception e) {
logger.log(Level.WARNING, "Unable to capture mouse movement", e);
result = false;
}
return result;
}
@Override
protected void setup() throws BotConfigurationException {
handler = User32.INSTANCE.FindWindow(null, BS_WINDOW_NAME);
if (handler == null) {
throw new BotConfigurationException(BS_WINDOW_NAME + " is not found");
}
final int[] rect = { 0, 0, 0, 0 };
final int result = User32.INSTANCE.GetWindowRect(handler, rect);
if (result == 0) {
throw new BotConfigurationException(BS_WINDOW_NAME + " is not found");
}
logger.log(
Level.FINE,
String.format("The corner locations for the window \"%s\" are %s", BS_WINDOW_NAME,
Arrays.toString(rect)));
// set bs always on top
User32.INSTANCE.SetWindowPos(handler, -1, 0, 0, 0, 0, TOPMOST_FLAGS);
}
}