barneygale / quarry

Python library that implements the Minecraft network protocol and data types
Other
527 stars 75 forks source link

how do i set up my quarry proxy for online mode servers? #135

Open thatITfox opened 2 years ago

ghost commented 2 years ago

there dosn't seems to any support for microsoft accounts and require a mojang account. consider looking at #123

thatITfox commented 2 years ago

damn, alr then, thanks man

Jerrylum commented 2 years ago

I got a work around solution. It fixed #132, #123, and https://github.com/LiveOverflow/minecraft-hacked/issues/1.

The reason why online mode is not supported is because of the Auth failed issue and the Microsoft Login issue. And here's the explanation:

About the Auth failed issue

The error message should look like this: Auth failed: [<twisted.python.failure.Failure OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]>]

Here is a discussion on Stackoverflow: https://stackoverflow.com/questions/33602478/how-to-handle-openssl-ssl-error-while-using-twisted-web-client-agent-on-facebook.

It is a problem related to Twisted, OpenSSL and trust roots configuration 10 years ago. This is one of the reasons why it only works on Linux. To fix this, all you have to do is run the python file via the command SSL_CERT_FILE="$(python -m certifi)" python xxx.py on Git Bash. You don't need to install WSL if you're using Windows.

About the Microsoft Login issue

Microsoft made the login process too complicated. I don't think it is possible to login to the account in command line enviroment.

However, you don't have to login or provide your user name and password in the command line. I first go to minecraft.net, then press F12 key to open my developer console. Type JavaScript to get my Minecraft bearer token. Finally paste the token in the code so I can get access to the account. You can follow the instructions on https://kqzz.github.io/mc-bearer-token/

Here is a full example. Oh, btw my example also fixed the Auth failed issue by using requests library. That's mean you don't even have to use Git Bash. Enjoy! :)

import json
from xmlrpc.client import ProtocolError
import requests
from twisted.python import failure

from twisted.internet import reactor
from quarry.types.uuid import UUID
from quarry.net.proxy import UpstreamFactory, Upstream, DownstreamFactory, Downstream, Bridge
from quarry.net import auth, crypto
from twisted.internet import reactor

class MyUpstream(Upstream):
    def packet_login_encryption_request(self, buff):
        p_server_id = buff.unpack_string()

        # 1.7.x
        if self.protocol_version <= 5:
            def unpack_array(b): return b.read(b.unpack('h'))
        # 1.8.x
        else:
            def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))

        p_public_key = unpack_array(buff)
        p_verify_token = unpack_array(buff)

        if not self.factory.profile.online:
            raise ProtocolError("Can't log into online-mode server while using"
                                " offline profile")

        self.shared_secret = crypto.make_shared_secret()
        self.public_key = crypto.import_public_key(p_public_key)
        self.verify_token = p_verify_token

        # make digest
        digest = crypto.make_digest(
            p_server_id.encode('ascii'),
            self.shared_secret,
            p_public_key)

        # do auth
        # deferred = self.factory.profile.join(digest)
        # deferred.addCallbacks(self.auth_ok, self.auth_failed)

        url = "https://sessionserver.mojang.com/session/minecraft/join"

        payload = json.dumps({
            "accessToken": self.factory.profile.access_token,
            "selectedProfile": self.factory.profile.uuid.to_hex(False),
            "serverId": digest
        })
        headers = {
            'Content-Type': 'application/json'
        }

        r = requests.request(
            "POST", "https://sessionserver.mojang.com/session/minecraft/join", headers=headers, data=payload)

        if r.status_code == 204:
            self.auth_ok(r.text)
        else:
            self.auth_failed(failure.Failure(
                auth.AuthException('unverified', 'unverified username')))

class MyDownstream(Downstream):
    def packet_login_encryption_response(self, buff):
        if self.login_expecting != 1:
            raise ProtocolError("Out-of-order login")

        # 1.7.x
        if self.protocol_version <= 5:
            def unpack_array(b): return b.read(b.unpack('h'))
        # 1.8.x
        else:
            def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))

        p_shared_secret = unpack_array(buff)
        p_verify_token = unpack_array(buff)

        shared_secret = crypto.decrypt_secret(
            self.factory.keypair,
            p_shared_secret)

        verify_token = crypto.decrypt_secret(
            self.factory.keypair,
            p_verify_token)

        self.login_expecting = None

        if verify_token != self.verify_token:
            raise ProtocolError("Verify token incorrect")

        # enable encryption
        self.cipher.enable(shared_secret)
        self.logger.debug("Encryption enabled")

        # make digest
        digest = crypto.make_digest(
            self.server_id.encode('ascii'),
            shared_secret,
            self.factory.public_key)

        # do auth
        remote_host = None
        if self.factory.prevent_proxy_connections:
            remote_host = self.remote_addr.host

        # deferred = auth.has_joined(
        #     self.factory.auth_timeout,
        #     digest,
        #     self.display_name,
        #     remote_host)
        # deferred.addCallbacks(self.auth_ok, self.auth_failed)

        r = requests.get('https://sessionserver.mojang.com/session/minecraft/hasJoined',
                         params={'username': self.display_name, 'serverId': digest, 'ip': remote_host})

        if r.status_code == 200:
            self.auth_ok(r.json())
        else:
            self.auth_failed(failure.Failure(
                auth.AuthException('invalid', 'invalid session')))

class MyUpstreamFactory(UpstreamFactory):
    protocol = MyUpstream

    connection_timeout = 10

class MyBridge(Bridge):
    upstream_factory_class = MyUpstreamFactory

    def make_profile(self):
        """
        Support online mode
        """

        # follow: https://kqzz.github.io/mc-bearer-token/

        accessToken = '<YOUR TOKEN>'

        url = "https://api.minecraftservices.com/minecraft/profile"
        headers = {'Authorization': 'Bearer ' + accessToken}
        response = requests.request("GET", url, headers=headers)
        result = response.json()
        myUuid = UUID.from_hex(result['id'])
        myUsername = result['name']
        return auth.Profile('(skip)', accessToken, myUsername, myUuid)

class MyDownstreamFactory(DownstreamFactory):
    protocol = MyDownstream
    bridge_class = MyBridge
    motd = "Proxy Server"

def main(argv):
    # Parse options
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("-a1", "--listen-host1", default="",
                        help="address to listen on")
    parser.add_argument("-p1", "--listen-port1", default=25566,
                        type=int, help="port to listen on")
    parser.add_argument("-b", "--connect-host",
                        default="127.0.0.1", help="address to connect to")
    parser.add_argument("-q", "--connect-port", default=25565,
                        type=int, help="port to connect to")
    args = parser.parse_args(argv)

    # Create factory
    factory = MyDownstreamFactory()
    factory.connect_host = args.connect_host
    factory.connect_port = args.connect_port

    # Listen
    factory.listen(args.listen_host1, args.listen_port1)
    reactor.run()

if __name__ == "__main__":
    import sys
    main(sys.argv[1:])
thatITfox commented 2 years ago

the code works amazingly, thanks for the help @Jerrylum

3arthquake3 commented 2 years ago

It doesn't work for some reason.. it just says connection refused

and yes.. i did paste in my token

robigan commented 2 years ago

Same here, I'm getting connection timed out

3arthquake3 commented 2 years ago

Also, if I use the 1st method, using linux.. it just says: Auth failed: [<twisted.python.failure.Failure twisted.internet._sslverify.SimpleVerificationError: 'mojang.com'!='sessionserver.mojang.com'>]

robigan commented 2 years ago

Also, if I use the 1st method, using linux.. it just says: Auth failed: [<twisted.python.failure.Failure twisted.internet._sslverify.SimpleVerificationError: 'mojang.com'!='sessionserver.mojang.com'>]

You'll need to set up your trust root certificates (Usually they're certificates issued by trusted entities that are preinstalled on systems that verify other certificates). For example, I'm on macOS and the Twisted library isn't able to read the system trust root certs. So for me to patch it, I extracted the location of the trust root certificates offered by the python package certifi and set an environment variable so that Twisted can find it. My command to run the proxy server looks like this: SSL_CERT_FILE="/Users/MY_USERNAME/homebrew/lib/python3.9/site-packages/certifi/cacert.pem" python3 ./src/proxy.py -b 2b2t.org

3arthquake3 commented 2 years ago

Also, if I use the 1st method, using linux.. it just says: Auth failed: [<twisted.python.failure.Failure twisted.internet._sslverify.SimpleVerificationError: 'mojang.com'!='sessionserver.mojang.com'>]

You'll need to set up your trust root certificates (Usually they're certificates issued by trusted entities that are preinstalled on systems that verify other certificates). For example, I'm on macOS and the Twisted library isn't able to read the system trust root certs. So for me to patch it, I extracted the location of the trust root certificates offered by the python package certifi and set an environment variable so that Twisted can find it. My command to run the proxy server looks like this: SSL_CERT_FILE="/Users/MY_USERNAME/homebrew/lib/python3.9/site-packages/certifi/cacert.pem" python3 ./src/proxy.py -b 2b2t.org

Yes I've done SSL_CERT_FILE="$(python -m certifi)" but it still says the same error.. the cert if offered by certifi

robigan commented 2 years ago

Also, if I use the 1st method, using linux.. it just says: Auth failed: [<twisted.python.failure.Failure twisted.internet._sslverify.SimpleVerificationError: 'mojang.com'!='sessionserver.mojang.com'>]

You'll need to set up your trust root certificates (Usually they're certificates issued by trusted entities that are preinstalled on systems that verify other certificates). For example, I'm on macOS and the Twisted library isn't able to read the system trust root certs. So for me to patch it, I extracted the location of the trust root certificates offered by the python package certifi and set an environment variable so that Twisted can find it. My command to run the proxy server looks like this: SSL_CERT_FILE="/Users/MY_USERNAME/homebrew/lib/python3.9/site-packages/certifi/cacert.pem" python3 ./src/proxy.py -b 2b2t.org

Yes I've done SSL_CERT_FILE="$(python -m certifi)" but it still says the same error.. the cert if offered by certifi

Hmmm, I'd have no idea then

Jerrylum commented 2 years ago

That's really interesting. I ran my code on my MacBook and PC and it was complety fine.

About the first method you tried before @3arthquake3, I understand the situation. In this case, I think my code is the only workaround right now since the SSL problem is too complicated.

Also, you said It doesn't work for some reason. I would like to know what doesn't work? Why it is connection refused? Would you please execute my code without any modification and tell me what you got?

About the connection timed out problem @robigan, I don't quite understand what's going on. So maybe you can give us more details.

robigan commented 2 years ago

@Jerrylum See the issue I just mentioned over at https://github.com/LiveOverflow/minecraft-hacked/issues/6 for more details on the logs of my connection. But basically I am stuck in the Log In phase as shown in the MC client. Also, if you don't mind, could you explain in a bit more detail how you patch connecting to online servers via the proxy?

3arthquake3 commented 2 years ago

That's really interesting. I ran my code on my MacBook and PC and it was complety fine.

About the first method you tried before @3arthquake3, I understand the situation. In this case, I think my code is the only workaround right now since the SSL problem is too complicated.

Also, you said It doesn't work for some reason. I would like to know what doesn't work? Why it is connection refused? Would you please execute my code without any modification and tell me what you got?

About the connection timed out problem @robigan, I don't quite understand what's going on. So maybe you can give us more details.

When I ran the code exactly, and connect to the proxy, it just says connection refused.. and if I try the first method, when i join, it just shows: encrypting then joining then it just freezes and eventually it would say connection time out

robigan commented 2 years ago

That's really interesting. I ran my code on my MacBook and PC and it was complety fine. About the first method you tried before @3arthquake3, I understand the situation. In this case, I think my code is the only workaround right now since the SSL problem is too complicated. Also, you said It doesn't work for some reason. I would like to know what doesn't work? Why it is connection refused? Would you please execute my code without any modification and tell me what you got? About the connection timed out problem @robigan, I don't quite understand what's going on. So maybe you can give us more details.

When I ran the code exactly, and connect to the proxy, it just says connection refused.. and if I try the first method, when i join, it just shows: encrypting then joining then it just freezes and eventually it would say connection time out

Are you sure you have properly setup trust roots for your executable? Also, the timeout is caused because you're connecting to the wrong URL. Minecraft internally does some magic to via alternate methods to find the IP to connect to, I would recommend reading up on what Jerry had written on SRV records over at https://github.com/LiveOverflow/minecraft-hacked/issues/6

In my case despite setting the connection URL to 2b2t.org, the actual server is resolved to connect.2b2t.org via the use of SRV records, most likely what you're trying to connect to have the same setup.

3arthquake3 commented 2 years ago

That's really interesting. I ran my code on my MacBook and PC and it was complety fine. About the first method you tried before @3arthquake3, I understand the situation. In this case, I think my code is the only workaround right now since the SSL problem is too complicated. Also, you said It doesn't work for some reason. I would like to know what doesn't work? Why it is connection refused? Would you please execute my code without any modification and tell me what you got? About the connection timed out problem @robigan, I don't quite understand what's going on. So maybe you can give us more details.

When I ran the code exactly, and connect to the proxy, it just says connection refused.. and if I try the first method, when i join, it just shows: encrypting then joining then it just freezes and eventually it would say connection time out

Are you sure you have properly setup trust roots for your executable? Also, the timeout is caused because you're connecting to the wrong URL. Minecraft internally does some magic to via alternate methods to find the IP to connect to, I would recommend reading up on what Jerry had written on SRV records over at LiveOverflow/minecraft-hacked#6

In my case despite setting the connection URL to 2b2t.org, the actual server is resolved to connect.2b2t.org via the use of SRV records, most likely what you're trying to connect to have the same setup.

Yes I took a look at it and tried it, it did fix the timeout problem but now it's saying I can't connect to an online server with an offline profile.. do you have any idea what it says that?

Lilyp3892 commented 2 years ago

I got a work around solution. It fixed #132, #123, and LiveOverflow/minecraft-hacked#1.

The reason why online mode is not supported is because of the Auth failed issue and the Microsoft Login issue. And here's the explanation:

About the Auth failed issue

The error message should look like this: Auth failed: [<twisted.python.failure.Failure OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]>]

Here is a discussion on Stackoverflow: https://stackoverflow.com/questions/33602478/how-to-handle-openssl-ssl-error-while-using-twisted-web-client-agent-on-facebook.

It is a problem related to Twisted, OpenSSL and trust roots configuration 10 years ago. This is one of the reasons why it only works on Linux. To fix this, all you have to do is run the python file via the command SSL_CERT_FILE="$(python -m certifi)" python xxx.py on Git Bash. You don't need to install WSL if you're using Windows.

About the Microsoft Login issue

Microsoft made the login process too complicated. I don't think it is possible to login to the account in command line enviroment.

However, you don't have to login or provide your user name and password in the command line. I first go to minecraft.net, then press F12 key to open my developer console. Type JavaScript to get my Minecraft bearer token. Finally paste the token in the code so I can get access to the account. You can follow the instructions on https://kqzz.github.io/mc-bearer-token/

Here is a full example. Oh, btw my example also fixed the Auth failed issue by using requests library. That's mean you don't even have to use Git Bash. Enjoy! :)

import json
from xmlrpc.client import ProtocolError
import requests
from twisted.python import failure

from twisted.internet import reactor
from quarry.types.uuid import UUID
from quarry.net.proxy import UpstreamFactory, Upstream, DownstreamFactory, Downstream, Bridge
from quarry.net import auth, crypto
from twisted.internet import reactor

class MyUpstream(Upstream):
    def packet_login_encryption_request(self, buff):
        p_server_id = buff.unpack_string()

        # 1.7.x
        if self.protocol_version <= 5:
            def unpack_array(b): return b.read(b.unpack('h'))
        # 1.8.x
        else:
            def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))

        p_public_key = unpack_array(buff)
        p_verify_token = unpack_array(buff)

        if not self.factory.profile.online:
            raise ProtocolError("Can't log into online-mode server while using"
                                " offline profile")

        self.shared_secret = crypto.make_shared_secret()
        self.public_key = crypto.import_public_key(p_public_key)
        self.verify_token = p_verify_token

        # make digest
        digest = crypto.make_digest(
            p_server_id.encode('ascii'),
            self.shared_secret,
            p_public_key)

        # do auth
        # deferred = self.factory.profile.join(digest)
        # deferred.addCallbacks(self.auth_ok, self.auth_failed)

        url = "https://sessionserver.mojang.com/session/minecraft/join"

        payload = json.dumps({
            "accessToken": self.factory.profile.access_token,
            "selectedProfile": self.factory.profile.uuid.to_hex(False),
            "serverId": digest
        })
        headers = {
            'Content-Type': 'application/json'
        }

        r = requests.request(
            "POST", "https://sessionserver.mojang.com/session/minecraft/join", headers=headers, data=payload)

        if r.status_code == 204:
            self.auth_ok(r.text)
        else:
            self.auth_failed(failure.Failure(
                auth.AuthException('unverified', 'unverified username')))

class MyDownstream(Downstream):
    def packet_login_encryption_response(self, buff):
        if self.login_expecting != 1:
            raise ProtocolError("Out-of-order login")

        # 1.7.x
        if self.protocol_version <= 5:
            def unpack_array(b): return b.read(b.unpack('h'))
        # 1.8.x
        else:
            def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))

        p_shared_secret = unpack_array(buff)
        p_verify_token = unpack_array(buff)

        shared_secret = crypto.decrypt_secret(
            self.factory.keypair,
            p_shared_secret)

        verify_token = crypto.decrypt_secret(
            self.factory.keypair,
            p_verify_token)

        self.login_expecting = None

        if verify_token != self.verify_token:
            raise ProtocolError("Verify token incorrect")

        # enable encryption
        self.cipher.enable(shared_secret)
        self.logger.debug("Encryption enabled")

        # make digest
        digest = crypto.make_digest(
            self.server_id.encode('ascii'),
            shared_secret,
            self.factory.public_key)

        # do auth
        remote_host = None
        if self.factory.prevent_proxy_connections:
            remote_host = self.remote_addr.host

        # deferred = auth.has_joined(
        #     self.factory.auth_timeout,
        #     digest,
        #     self.display_name,
        #     remote_host)
        # deferred.addCallbacks(self.auth_ok, self.auth_failed)

        r = requests.get('https://sessionserver.mojang.com/session/minecraft/hasJoined',
                         params={'username': self.display_name, 'serverId': digest, 'ip': remote_host})

        if r.status_code == 200:
            self.auth_ok(r.json())
        else:
            self.auth_failed(failure.Failure(
                auth.AuthException('invalid', 'invalid session')))

class MyUpstreamFactory(UpstreamFactory):
    protocol = MyUpstream

    connection_timeout = 10

class MyBridge(Bridge):
    upstream_factory_class = MyUpstreamFactory

    def make_profile(self):
        """
        Support online mode
        """

        # follow: https://kqzz.github.io/mc-bearer-token/

        accessToken = '<YOUR TOKEN>'

        url = "https://api.minecraftservices.com/minecraft/profile"
        headers = {'Authorization': 'Bearer ' + accessToken}
        response = requests.request("GET", url, headers=headers)
        result = response.json()
        myUuid = UUID.from_hex(result['id'])
        myUsername = result['name']
        return auth.Profile('(skip)', accessToken, myUsername, myUuid)

class MyDownstreamFactory(DownstreamFactory):
    protocol = MyDownstream
    bridge_class = MyBridge
    motd = "Proxy Server"

def main(argv):
    # Parse options
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("-a1", "--listen-host1", default="",
                        help="address to listen on")
    parser.add_argument("-p1", "--listen-port1", default=25566,
                        type=int, help="port to listen on")
    parser.add_argument("-b", "--connect-host",
                        default="127.0.0.1", help="address to connect to")
    parser.add_argument("-q", "--connect-port", default=25565,
                        type=int, help="port to connect to")
    args = parser.parse_args(argv)

    # Create factory
    factory = MyDownstreamFactory()
    factory.connect_host = args.connect_host
    factory.connect_port = args.connect_port

    # Listen
    factory.listen(args.listen_host1, args.listen_port1)
    reactor.run()

if __name__ == "__main__":
    import sys
    main(sys.argv[1:])

This code seems to break in 1.19 #164 @Jerrylum are you able to reproduce this issue?

Edit: Something to note: If you add print(p_verify_token) just before line 85 (the line where it fails) you will see that with a 1.19 client it prints out b'' or a blank byte, which it does not do in 1.18.2, it instead in 1.18.2 it prints out a long byte like you would expect, so the problem seems to be with getting p_verify_token

Lilyp3892 commented 2 years ago

I solved this. For anyone curious here's the code

import json
from xmlrpc.client import ProtocolError
import requests
from twisted.python import failure

from twisted.internet import reactor
from quarry.types.uuid import UUID
from quarry.net.proxy import UpstreamFactory, Upstream, DownstreamFactory, Downstream, Bridge
from quarry.net import auth, crypto
from twisted.internet import reactor

class MyUpstream(Upstream):
    def packet_login_encryption_request(self, buff):
        p_server_id = buff.unpack_string()

        # 1.7.x
        if self.protocol_version <= 5:
            def unpack_array(b): return b.read(b.unpack('h'))
        # 1.8.x
        else:
            def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))

        p_public_key = unpack_array(buff)
        p_verify_token = unpack_array(buff)

        if not self.factory.profile.online:
            raise ProtocolError("Can't log into online-mode server while using"
                                " offline profile")

        self.shared_secret = crypto.make_shared_secret()
        self.public_key = crypto.import_public_key(p_public_key)
        self.verify_token = p_verify_token

        # make digest
        digest = crypto.make_digest(
            p_server_id.encode('ascii'),
            self.shared_secret,
            p_public_key)

        # do auth
        # deferred = self.factory.profile.join(digest)
        # deferred.addCallbacks(self.auth_ok, self.auth_failed)

        url = "https://sessionserver.mojang.com/session/minecraft/join"

        payload = json.dumps({
            "accessToken": self.factory.profile.access_token,
            "selectedProfile": self.factory.profile.uuid.to_hex(False),
            "serverId": digest
        })
        headers = {
            'Content-Type': 'application/json'
        }

        r = requests.request(
            "POST", "https://sessionserver.mojang.com/session/minecraft/join", headers=headers, data=payload)

        if r.status_code == 204:
            self.auth_ok(r.text)
        else:
            self.auth_failed(failure.Failure(
                auth.AuthException('unverified', 'unverified username')))

class MyDownstream(Downstream):
    def packet_login_encryption_response(self, buff):
        if self.login_expecting != 1:
            raise ProtocolError("Out-of-order login")

        # 1.7.x
        if self.protocol_version <= 5:
            def unpack_array(b): return b.read(b.unpack('h'))
        # 1.8.x
        else:
            def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))

        p_shared_secret = unpack_array(buff)
        p_verify_token = unpack_array(buff)

        buff.read()

        shared_secret = crypto.decrypt_secret(
            self.factory.keypair,
            p_shared_secret)

        ###verify_token = crypto.decrypt_secret(
        ###    self.factory.keypair,
        ###    p_verify_token)

        self.login_expecting = None

        ###if verify_token != self.verify_token:
        ###    raise ProtocolError("Verify token incorrect")

        # enable encryption
        self.cipher.enable(shared_secret)
        self.logger.debug("Encryption enabled")

        # make digest
        digest = crypto.make_digest(
            self.server_id.encode('ascii'),
            shared_secret,
            self.factory.public_key)

        # do auth
        remote_host = None
        if self.factory.prevent_proxy_connections:
            remote_host = self.remote_addr.host

        # deferred = auth.has_joined(
        #     self.factory.auth_timeout,
        #     digest,
        #     self.display_name,
        #     remote_host)
        # deferred.addCallbacks(self.auth_ok, self.auth_failed)

        r = requests.get('https://sessionserver.mojang.com/session/minecraft/hasJoined',
                         params={'username': self.display_name, 'serverId': digest, 'ip': remote_host})

        if r.status_code == 200:
            self.auth_ok(r.json())
        else:
            self.auth_failed(failure.Failure(
                auth.AuthException('invalid', 'invalid session')))

class MyUpstreamFactory(UpstreamFactory):
    protocol = MyUpstream

    connection_timeout = 10

class MyBridge(Bridge):
    upstream_factory_class = MyUpstreamFactory

    def make_profile(self):
        """
        Support online mode
        """

        # follow: https://kqzz.github.io/mc-bearer-token/

        accessToken = '<YOUR TOKEN>'

        url = "https://api.minecraftservices.com/minecraft/profile"
        headers = {'Authorization': 'Bearer ' + accessToken}
        response = requests.request("GET", url, headers=headers)
        result = response.json()
        myUuid = UUID.from_hex(result['id'])
        myUsername = result['name']
        return auth.Profile('(skip)', accessToken, myUsername, myUuid)

class MyDownstreamFactory(DownstreamFactory):
    protocol = MyDownstream
    bridge_class = MyBridge
    motd = "Proxy Server"

def main(argv):
    # Parse options
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("-a", "--listen-host", default="0.0.0.0",
                        help="address to listen on")
    parser.add_argument("-p", "--listen-port", default=25565,
                        type=int, help="port to listen on")
    parser.add_argument("-b", "--connect-host",
                        default="127.0.0.1", help="address to connect to")
    parser.add_argument("-q", "--connect-port", default=25565,
                        type=int, help="port to connect to")
    args = parser.parse_args(argv)

    # Create factory
    factory = MyDownstreamFactory()
    factory.connect_host = args.connect_host
    factory.connect_port = args.connect_port

    # Listen
    factory.listen(args.listen_host, args.listen_port)
    reactor.run()

if __name__ == "__main__":
    import sys
    main(sys.argv[1:])
Xyphyn commented 1 year ago

I got a work around solution. It fixed #132, #123, and LiveOverflow/minecraft-hacked#1.

The reason why online mode is not supported is because of the Auth failed issue and the Microsoft Login issue. And here's the explanation:

About the Auth failed issue

The error message should look like this: Auth failed: [<twisted.python.failure.Failure OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]>]

Here is a discussion on Stackoverflow: https://stackoverflow.com/questions/33602478/how-to-handle-openssl-ssl-error-while-using-twisted-web-client-agent-on-facebook.

It is a problem related to Twisted, OpenSSL and trust roots configuration 10 years ago. This is one of the reasons why it only works on Linux. To fix this, all you have to do is run the python file via the command SSL_CERT_FILE="$(python -m certifi)" python xxx.py on Git Bash. You don't need to install WSL if you're using Windows.

About the Microsoft Login issue

Microsoft made the login process too complicated. I don't think it is possible to login to the account in command line enviroment.

However, you don't have to login or provide your user name and password in the command line. I first go to minecraft.net, then press F12 key to open my developer console. Type JavaScript to get my Minecraft bearer token. Finally paste the token in the code so I can get access to the account. You can follow the instructions on https://kqzz.github.io/mc-bearer-token/

Here is a full example. Oh, btw my example also fixed the Auth failed issue by using requests library. That's mean you don't even have to use Git Bash. Enjoy! :)

import json
from xmlrpc.client import ProtocolError
import requests
from twisted.python import failure

from twisted.internet import reactor
from quarry.types.uuid import UUID
from quarry.net.proxy import UpstreamFactory, Upstream, DownstreamFactory, Downstream, Bridge
from quarry.net import auth, crypto
from twisted.internet import reactor

class MyUpstream(Upstream):
    def packet_login_encryption_request(self, buff):
        p_server_id = buff.unpack_string()

        # 1.7.x
        if self.protocol_version <= 5:
            def unpack_array(b): return b.read(b.unpack('h'))
        # 1.8.x
        else:
            def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))

        p_public_key = unpack_array(buff)
        p_verify_token = unpack_array(buff)

        if not self.factory.profile.online:
            raise ProtocolError("Can't log into online-mode server while using"
                                " offline profile")

        self.shared_secret = crypto.make_shared_secret()
        self.public_key = crypto.import_public_key(p_public_key)
        self.verify_token = p_verify_token

        # make digest
        digest = crypto.make_digest(
            p_server_id.encode('ascii'),
            self.shared_secret,
            p_public_key)

        # do auth
        # deferred = self.factory.profile.join(digest)
        # deferred.addCallbacks(self.auth_ok, self.auth_failed)

        url = "https://sessionserver.mojang.com/session/minecraft/join"

        payload = json.dumps({
            "accessToken": self.factory.profile.access_token,
            "selectedProfile": self.factory.profile.uuid.to_hex(False),
            "serverId": digest
        })
        headers = {
            'Content-Type': 'application/json'
        }

        r = requests.request(
            "POST", "https://sessionserver.mojang.com/session/minecraft/join", headers=headers, data=payload)

        if r.status_code == 204:
            self.auth_ok(r.text)
        else:
            self.auth_failed(failure.Failure(
                auth.AuthException('unverified', 'unverified username')))

class MyDownstream(Downstream):
    def packet_login_encryption_response(self, buff):
        if self.login_expecting != 1:
            raise ProtocolError("Out-of-order login")

        # 1.7.x
        if self.protocol_version <= 5:
            def unpack_array(b): return b.read(b.unpack('h'))
        # 1.8.x
        else:
            def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))

        p_shared_secret = unpack_array(buff)
        p_verify_token = unpack_array(buff)

        shared_secret = crypto.decrypt_secret(
            self.factory.keypair,
            p_shared_secret)

        verify_token = crypto.decrypt_secret(
            self.factory.keypair,
            p_verify_token)

        self.login_expecting = None

        if verify_token != self.verify_token:
            raise ProtocolError("Verify token incorrect")

        # enable encryption
        self.cipher.enable(shared_secret)
        self.logger.debug("Encryption enabled")

        # make digest
        digest = crypto.make_digest(
            self.server_id.encode('ascii'),
            shared_secret,
            self.factory.public_key)

        # do auth
        remote_host = None
        if self.factory.prevent_proxy_connections:
            remote_host = self.remote_addr.host

        # deferred = auth.has_joined(
        #     self.factory.auth_timeout,
        #     digest,
        #     self.display_name,
        #     remote_host)
        # deferred.addCallbacks(self.auth_ok, self.auth_failed)

        r = requests.get('https://sessionserver.mojang.com/session/minecraft/hasJoined',
                         params={'username': self.display_name, 'serverId': digest, 'ip': remote_host})

        if r.status_code == 200:
            self.auth_ok(r.json())
        else:
            self.auth_failed(failure.Failure(
                auth.AuthException('invalid', 'invalid session')))

class MyUpstreamFactory(UpstreamFactory):
    protocol = MyUpstream

    connection_timeout = 10

class MyBridge(Bridge):
    upstream_factory_class = MyUpstreamFactory

    def make_profile(self):
        """
        Support online mode
        """

        # follow: https://kqzz.github.io/mc-bearer-token/

        accessToken = '<YOUR TOKEN>'

        url = "https://api.minecraftservices.com/minecraft/profile"
        headers = {'Authorization': 'Bearer ' + accessToken}
        response = requests.request("GET", url, headers=headers)
        result = response.json()
        myUuid = UUID.from_hex(result['id'])
        myUsername = result['name']
        return auth.Profile('(skip)', accessToken, myUsername, myUuid)

class MyDownstreamFactory(DownstreamFactory):
    protocol = MyDownstream
    bridge_class = MyBridge
    motd = "Proxy Server"

def main(argv):
    # Parse options
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("-a1", "--listen-host1", default="",
                        help="address to listen on")
    parser.add_argument("-p1", "--listen-port1", default=25566,
                        type=int, help="port to listen on")
    parser.add_argument("-b", "--connect-host",
                        default="127.0.0.1", help="address to connect to")
    parser.add_argument("-q", "--connect-port", default=25565,
                        type=int, help="port to connect to")
    args = parser.parse_args(argv)

    # Create factory
    factory = MyDownstreamFactory()
    factory.connect_host = args.connect_host
    factory.connect_port = args.connect_port

    # Listen
    factory.listen(args.listen_host1, args.listen_port1)
    reactor.run()

if __name__ == "__main__":
    import sys
    main(sys.argv[1:])

It gets stuck on "Joining world..." and then eventually times out for me.

ShayBox commented 1 year ago

I solved this. For anyone curious here's the code ...

This works, but is missing profile public key, so can't connect to servers requiring secure profiles (default)
I don't know if that would ever be supported by Quarry

davidawesome02 commented 1 year ago

you can do it your self, with modifications or just with moding the lib

virus-rpi commented 1 year ago

i get this "builtins.ValueError: Ciphertext length must be equal to key size. " error when i try run this code

KTibow commented 1 year ago

How would you do the version @Jerrylum posted without using quarry as a proxy, directly connecting to the server?

Pepperjacked commented 1 year ago

I solved this. For anyone curious here's the code

import json
from xmlrpc.client import ProtocolError
import requests
from twisted.python import failure

from twisted.internet import reactor
from quarry.types.uuid import UUID
from quarry.net.proxy import UpstreamFactory, Upstream, DownstreamFactory, Downstream, Bridge
from quarry.net import auth, crypto
from twisted.internet import reactor

class MyUpstream(Upstream):
    def packet_login_encryption_request(self, buff):
        p_server_id = buff.unpack_string()

        # 1.7.x
        if self.protocol_version <= 5:
            def unpack_array(b): return b.read(b.unpack('h'))
        # 1.8.x
        else:
            def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))

        p_public_key = unpack_array(buff)
        p_verify_token = unpack_array(buff)

        if not self.factory.profile.online:
            raise ProtocolError("Can't log into online-mode server while using"
                                " offline profile")

        self.shared_secret = crypto.make_shared_secret()
        self.public_key = crypto.import_public_key(p_public_key)
        self.verify_token = p_verify_token

        # make digest
        digest = crypto.make_digest(
            p_server_id.encode('ascii'),
            self.shared_secret,
            p_public_key)

        # do auth
        # deferred = self.factory.profile.join(digest)
        # deferred.addCallbacks(self.auth_ok, self.auth_failed)

        url = "https://sessionserver.mojang.com/session/minecraft/join"

        payload = json.dumps({
            "accessToken": self.factory.profile.access_token,
            "selectedProfile": self.factory.profile.uuid.to_hex(False),
            "serverId": digest
        })
        headers = {
            'Content-Type': 'application/json'
        }

        r = requests.request(
            "POST", "https://sessionserver.mojang.com/session/minecraft/join", headers=headers, data=payload)

        if r.status_code == 204:
            self.auth_ok(r.text)
        else:
            self.auth_failed(failure.Failure(
                auth.AuthException('unverified', 'unverified username')))

class MyDownstream(Downstream):
    def packet_login_encryption_response(self, buff):
        if self.login_expecting != 1:
            raise ProtocolError("Out-of-order login")

        # 1.7.x
        if self.protocol_version <= 5:
            def unpack_array(b): return b.read(b.unpack('h'))
        # 1.8.x
        else:
            def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))

        p_shared_secret = unpack_array(buff)
        p_verify_token = unpack_array(buff)

        buff.read()

        shared_secret = crypto.decrypt_secret(
            self.factory.keypair,
            p_shared_secret)

        ###verify_token = crypto.decrypt_secret(
        ###    self.factory.keypair,
        ###    p_verify_token)

        self.login_expecting = None

        ###if verify_token != self.verify_token:
        ###    raise ProtocolError("Verify token incorrect")

        # enable encryption
        self.cipher.enable(shared_secret)
        self.logger.debug("Encryption enabled")

        # make digest
        digest = crypto.make_digest(
            self.server_id.encode('ascii'),
            shared_secret,
            self.factory.public_key)

        # do auth
        remote_host = None
        if self.factory.prevent_proxy_connections:
            remote_host = self.remote_addr.host

        # deferred = auth.has_joined(
        #     self.factory.auth_timeout,
        #     digest,
        #     self.display_name,
        #     remote_host)
        # deferred.addCallbacks(self.auth_ok, self.auth_failed)

        r = requests.get('https://sessionserver.mojang.com/session/minecraft/hasJoined',
                         params={'username': self.display_name, 'serverId': digest, 'ip': remote_host})

        if r.status_code == 200:
            self.auth_ok(r.json())
        else:
            self.auth_failed(failure.Failure(
                auth.AuthException('invalid', 'invalid session')))

class MyUpstreamFactory(UpstreamFactory):
    protocol = MyUpstream

    connection_timeout = 10

class MyBridge(Bridge):
    upstream_factory_class = MyUpstreamFactory

    def make_profile(self):
        """
        Support online mode
        """

        # follow: https://kqzz.github.io/mc-bearer-token/

        accessToken = '<YOUR TOKEN>'

        url = "https://api.minecraftservices.com/minecraft/profile"
        headers = {'Authorization': 'Bearer ' + accessToken}
        response = requests.request("GET", url, headers=headers)
        result = response.json()
        myUuid = UUID.from_hex(result['id'])
        myUsername = result['name']
        return auth.Profile('(skip)', accessToken, myUsername, myUuid)

class MyDownstreamFactory(DownstreamFactory):
    protocol = MyDownstream
    bridge_class = MyBridge
    motd = "Proxy Server"

def main(argv):
    # Parse options
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("-a", "--listen-host", default="0.0.0.0",
                        help="address to listen on")
    parser.add_argument("-p", "--listen-port", default=25565,
                        type=int, help="port to listen on")
    parser.add_argument("-b", "--connect-host",
                        default="127.0.0.1", help="address to connect to")
    parser.add_argument("-q", "--connect-port", default=25565,
                        type=int, help="port to connect to")
    args = parser.parse_args(argv)

    # Create factory
    factory = MyDownstreamFactory()
    factory.connect_host = args.connect_host
    factory.connect_port = args.connect_port

    # Listen
    factory.listen(args.listen_host, args.listen_port)
    reactor.run()

if __name__ == "__main__":
    import sys
    main(sys.argv[1:])

This code was working for me for a min but has since stopped working for some reason image

TechDudie commented 11 months ago

I got a work around solution. It fixed #132, #123, and LiveOverflow/minecraft-hacked#1.

The reason why online mode is not supported is because of the Auth failed issue and the Microsoft Login issue. And here's the explanation:

About the Auth failed issue

The error message should look like this: Auth failed: [<twisted.python.failure.Failure OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]>]

Here is a discussion on Stackoverflow: https://stackoverflow.com/questions/33602478/how-to-handle-openssl-ssl-error-while-using-twisted-web-client-agent-on-facebook.

It is a problem related to Twisted, OpenSSL and trust roots configuration 10 years ago. This is one of the reasons why it only works on Linux. To fix this, all you have to do is run the python file via the command SSL_CERT_FILE="$(python -m certifi)" python xxx.py on Git Bash. You don't need to install WSL if you're using Windows.

About the Microsoft Login issue

Microsoft made the login process too complicated. I don't think it is possible to login to the account in command line enviroment.

However, you don't have to login or provide your user name and password in the command line. I first go to minecraft.net, then press F12 key to open my developer console. Type JavaScript to get my Minecraft bearer token. Finally paste the token in the code so I can get access to the account. You can follow the instructions on https://kqzz.github.io/mc-bearer-token/

Here is a full example. Oh, btw my example also fixed the Auth failed issue by using requests library. That's mean you don't even have to use Git Bash. Enjoy! :)

import json
from xmlrpc.client import ProtocolError
import requests
from twisted.python import failure

from twisted.internet import reactor
from quarry.types.uuid import UUID
from quarry.net.proxy import UpstreamFactory, Upstream, DownstreamFactory, Downstream, Bridge
from quarry.net import auth, crypto
from twisted.internet import reactor

class MyUpstream(Upstream):
    def packet_login_encryption_request(self, buff):
        p_server_id = buff.unpack_string()

        # 1.7.x
        if self.protocol_version <= 5:
            def unpack_array(b): return b.read(b.unpack('h'))
        # 1.8.x
        else:
            def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))

        p_public_key = unpack_array(buff)
        p_verify_token = unpack_array(buff)

        if not self.factory.profile.online:
            raise ProtocolError("Can't log into online-mode server while using"
                                " offline profile")

        self.shared_secret = crypto.make_shared_secret()
        self.public_key = crypto.import_public_key(p_public_key)
        self.verify_token = p_verify_token

        # make digest
        digest = crypto.make_digest(
            p_server_id.encode('ascii'),
            self.shared_secret,
            p_public_key)

        # do auth
        # deferred = self.factory.profile.join(digest)
        # deferred.addCallbacks(self.auth_ok, self.auth_failed)

        url = "https://sessionserver.mojang.com/session/minecraft/join"

        payload = json.dumps({
            "accessToken": self.factory.profile.access_token,
            "selectedProfile": self.factory.profile.uuid.to_hex(False),
            "serverId": digest
        })
        headers = {
            'Content-Type': 'application/json'
        }

        r = requests.request(
            "POST", "https://sessionserver.mojang.com/session/minecraft/join", headers=headers, data=payload)

        if r.status_code == 204:
            self.auth_ok(r.text)
        else:
            self.auth_failed(failure.Failure(
                auth.AuthException('unverified', 'unverified username')))

class MyDownstream(Downstream):
    def packet_login_encryption_response(self, buff):
        if self.login_expecting != 1:
            raise ProtocolError("Out-of-order login")

        # 1.7.x
        if self.protocol_version <= 5:
            def unpack_array(b): return b.read(b.unpack('h'))
        # 1.8.x
        else:
            def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))

        p_shared_secret = unpack_array(buff)
        p_verify_token = unpack_array(buff)

        shared_secret = crypto.decrypt_secret(
            self.factory.keypair,
            p_shared_secret)

        verify_token = crypto.decrypt_secret(
            self.factory.keypair,
            p_verify_token)

        self.login_expecting = None

        if verify_token != self.verify_token:
            raise ProtocolError("Verify token incorrect")

        # enable encryption
        self.cipher.enable(shared_secret)
        self.logger.debug("Encryption enabled")

        # make digest
        digest = crypto.make_digest(
            self.server_id.encode('ascii'),
            shared_secret,
            self.factory.public_key)

        # do auth
        remote_host = None
        if self.factory.prevent_proxy_connections:
            remote_host = self.remote_addr.host

        # deferred = auth.has_joined(
        #     self.factory.auth_timeout,
        #     digest,
        #     self.display_name,
        #     remote_host)
        # deferred.addCallbacks(self.auth_ok, self.auth_failed)

        r = requests.get('https://sessionserver.mojang.com/session/minecraft/hasJoined',
                         params={'username': self.display_name, 'serverId': digest, 'ip': remote_host})

        if r.status_code == 200:
            self.auth_ok(r.json())
        else:
            self.auth_failed(failure.Failure(
                auth.AuthException('invalid', 'invalid session')))

class MyUpstreamFactory(UpstreamFactory):
    protocol = MyUpstream

    connection_timeout = 10

class MyBridge(Bridge):
    upstream_factory_class = MyUpstreamFactory

    def make_profile(self):
        """
        Support online mode
        """

        # follow: https://kqzz.github.io/mc-bearer-token/

        accessToken = '<YOUR TOKEN>'

        url = "https://api.minecraftservices.com/minecraft/profile"
        headers = {'Authorization': 'Bearer ' + accessToken}
        response = requests.request("GET", url, headers=headers)
        result = response.json()
        myUuid = UUID.from_hex(result['id'])
        myUsername = result['name']
        return auth.Profile('(skip)', accessToken, myUsername, myUuid)

class MyDownstreamFactory(DownstreamFactory):
    protocol = MyDownstream
    bridge_class = MyBridge
    motd = "Proxy Server"

def main(argv):
    # Parse options
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("-a1", "--listen-host1", default="",
                        help="address to listen on")
    parser.add_argument("-p1", "--listen-port1", default=25566,
                        type=int, help="port to listen on")
    parser.add_argument("-b", "--connect-host",
                        default="127.0.0.1", help="address to connect to")
    parser.add_argument("-q", "--connect-port", default=25565,
                        type=int, help="port to connect to")
    args = parser.parse_args(argv)

    # Create factory
    factory = MyDownstreamFactory()
    factory.connect_host = args.connect_host
    factory.connect_port = args.connect_port

    # Listen
    factory.listen(args.listen_host1, args.listen_port1)
    reactor.run()

if __name__ == "__main__":
    import sys
    main(sys.argv[1:])

I got an error when i joined saying invalid session with my token. To fix this, replace this image

with this

image

i mean technically 204 OK isnt 200 OK but computers will be computers the only thing needed from the hasJoined endpoint is the uuid, just provide it seperately! i was finally able to join hypixel

(the whole reason why im doing this is so i can disable online mode on the proxy listener but log in as me on hypixel, the endpoints needed for join online mode server are blocked by school blocker 😁)