pyclashbot / py-clash-bot

An inherently undetectable Clash Royale automation bot
https://pyclashbot.app/
93 stars 14 forks source link

Add support for any ADB connection #389

Open trigger337 opened 6 months ago

trigger337 commented 6 months ago

Is your feature request related to a problem? Please describe. I don't have a separate Windows machine, and I don't have an additional GPU to give to a VM

Describe the solution you'd like It would be useful even for Windows to add support for running on an actual device. I'm guessing most things are done through ADB(and if not already, it can be implemented). So on a rooted device, you can force a resolution and run the game.

Describe alternatives you've considered Tried a VM without hardware acceleration. Didn't go well. Android VM under a Windows VM even sounds scary

martinmiglio commented 6 months ago

This would be a desired feature to add at some point, but right now the bot is bound pretty tight to MEmu as it configures a lot of settings on the emulator for the user, things which cant be controlled through ADB.

The core functionality is exclusively ADB once the bot is running though, so it might be worth while to make a mode to unbind it from MEmu and use any given ADB connection. But that would require the user to ensure their emulator has those correct settings.

trigger337 commented 6 months ago

Would you mind pointing me to the exact settings/configuring mechanism? If it's not a lot, I might even give it a try, like writing a shell script to run on the device to force absolutely necessary things.

trigger337 commented 6 months ago

I looked through the code under /src/memu and it looks pretty much possible to remake that into anything. If I have enough free time, I might even try it, or, at least, make a script that will do some configurations on the device.

martinmiglio commented 6 months ago

All MEmu interaction happens through memuc.exe which is controlled by the PyMemuc module. If you can replace every call to that module with a native ADB call, then that should work fine.

The only issue is we would need to skip the emulator creation step and leave that to the user.

For this to get implemented into this project it would need to be toggleable from the GUI with a path input to an ADB executable.

Keep me updated on your progress, thanks for investigating this.

trigger337 commented 6 months ago

Started to do some things. Would like to know if I'm doing anything wrong so far. https://github.com/trigger337/py-clash-bot/tree/non-emulator Also, how many parts of the script call PyMemuc? I really don't want to make a bigass separate controller, so it would be great to keep it all inside of adb.py. But if it's absolutely needed, I will see what I can do.

martinmiglio commented 6 months ago

This looks good so far, you might benefit from using an existing ADB library such as adb-shell or pure-python-adb to avoid managing subprocess calls yourself.

Also, it might be worthwhile to create an abstraction for an emulator controller class which has all the necessary definitions for the bot, which can then be extended into a MEmu controller or pure ADB controller. I think I will investigate this and keep you updated.

martinmiglio commented 6 months ago

This is what I'm thinking for the abstraction:

"""
This module contains the base class for emulator controllers.
"""

import numpy as np

class BaseEmulatorController:
    """
    Base class for emulator controllers.
    This class is used to define the interface for all emulator controllers.
    """

    def __init__(self):
        raise NotImplementedError

    def __del__(self):
        raise NotImplementedError

    def create(self):
        """
        This method is used to create the emulator.
        """
        raise NotImplementedError

    def configure(self):
        """
        This method is used to configure the emulator.
        """
        raise NotImplementedError

    def start(self):
        """
        This method is used to start the emulator.
        """
        raise NotImplementedError

    def stop(self):
        """
        This method is used to stop the emulator.
        """
        raise NotImplementedError

    def click(self, position: tuple[int, int]):
        """
        This method is used to click on the emulator screen.
        """
        raise NotImplementedError

    def swipe(
        self,
        start_position: tuple[int, int],
        end_position: tuple[int, int],
        duration: float,
    ):
        """
        This method is used to swipe on the emulator screen.
        """
        raise NotImplementedError

    def screenshot(self) -> np.ndarray:
        """
        This method is used to take a screenshot of the emulator screen.
        """
        raise NotImplementedError

    def install_apk(self, apk_path: str):
        """
        This method is used to install an APK on the emulator.
        """
        raise NotImplementedError

    def start_app(self, package_name: str):
        """
        This method is used to start an app on the emulator.
        """
        raise NotImplementedError
trigger337 commented 6 months ago

Seems good. Then how would I make it work? I'm really not an advanced coder and haven't touched modular projects. I would accept any guidance. Also should we use additional libraries? I was making the raw commands so there aren't any new dependencies, but if it's alright, and existing libraries are better, I can make it do.

martinmiglio commented 6 months ago

For this case, I would prefer us to use a library to make calls to ADB, just to bring us back a layer of abstraction and not have to worry about parsing ADB commands and output.

For implementing base class, here's how I'm starting with the Memu implementation:

from pyclashbot.emulator.base import BaseEmulatorController
from pyclashbot.emulator.memu.screenshot import screen_shotter
from pyclashbot.emulator.memu.client import send_click, send_swipe
from pyclashbot.emulator.memu.configure import configure_vm

class MemuEmulatorController(BaseEmulatorController):
    """
    Class for controlling a MEmu emulator.
    """

    def __init__(self, vm_index: int):
        self.vm_index = vm_index
        super().__init__()

    def create(self):
        """
        This method is used to create the emulator.
        """
        raise NotImplementedError

    def configure(self):
        """
        This method is used to configure the emulator.
        """
        configure_vm(self.vm_index)

    def start(self):
        """
        This method is used to start the emulator.
        """
        raise NotImplementedError

    def stop(self):
        """
        This method is used to stop the emulator.
        """
        raise NotImplementedError

    def click(self, position: tuple[int, int]):
        """
        This method is used to click on the emulator screen.
        """
        send_click(self.vm_index, position[0], position[1])

    def swipe(
        self,
        start_position: tuple[int, int],
        end_position: tuple[int, int],
    ):
        """
        This method is used to swipe on the emulator screen.
        """
        send_swipe(
            self.vm_index,
            start_position[0],
            start_position[1],
            end_position[0],
            end_position[1],
        )

    def screenshot(self):
        """
        This method is used to take a screenshot of the emulator screen.
        """
        return screen_shotter[self.vm_index]

I will need to rework how the bot logic calls these functions, but the plan with this is when the bot is initialized from the main thread, a specific controller class will be selected to handle the interaction with the emulator.

I'm starting this work in the controller branch, you might want to branch of from there so we can work with the same base controller interface.

martinmiglio commented 6 months ago

If you have specific questions about implementing this join the contributing discussion in the discord