Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated to use Docker #6

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__pycache__
.vs
pokemon/
*.log
Pokemon/*
logs/*
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ This Python script allows the hosting of a HTTP server to which retail Nintendo

## Requirements

- Python 3.8 (Available from http://www.python.org)
- Docker (Available from https://www.docker.com/)
- Generation 4 Pokemon game
- Wireless network (WEP or passwordless)
- Administrator priviliges
- Administrator priviliges for installing docker

## Installation

Expand All @@ -19,9 +19,9 @@ git clone https://github.com/DevreeseJorik/IR-GTS-MG.git
```bash
cd /path/to/project/root
```
3. Install all dependencies using pip
3. run docker compose
```bash
pip install -r requirements.txt
docker compose up
```

## Setting up the network
Expand Down Expand Up @@ -62,12 +62,15 @@ The exact steps to perform this are highly dependent on the router/provider you

# Usage

1. Run the main.py script to start the DNS spoofer and HTTP server:
1. edit the docker-compose.yaml and replace YOUR_HOST_IP_HERE with your computer's IP address
- Linux: ifconfig
- Windows: ipconfig
2. Run docker compose to start the DNS spoofer and HTTP server:
```bash
python3 main.py
docker compose up
```
2. Make note of the 'Primary DNS server' ip address provided by the script, as it will be required for the next step.
3. On your Nintendo DS:
3. Make note of the 'Primary DNS server' ip address provided by the script, as it will be required for the next step.
4. On your Nintendo DS:
- Boot up the game and navigate to `NINTENDO WFC SETTINGS`, then `Nintendo Wi-FI Connection Settings`.
- Create a new connection and connect to the insecure network.
- Set the Primary DNS to the IP address provided by the script. The Secondary should be left blank/the same as the Primary.
Expand All @@ -76,18 +79,16 @@ python3 main.py

to send a Pokémon file using the GTS (Global Trade Station), follow these steps:

1. Enter the GTS within the Pokémon game.
2. When prompted, type/copy-and-paste the file path to the PKM file you want to send. For example:
- If the file is located at `C:\Users\InfiniteRecursion\MAWILE.pkm`, insert the full path.
- If the file is located within the `Pokemon` directory within the `ir-gts` folder, you can simply insert `Pokemon\MAWILE.pkm`.
- Alternatively, you can drag the file into the prompt window, and it will automatically enter the path for you.
1. go to http://HOST_MACHINE_IP/pokemondpds/worldexchange/select to choose the pkm file you want to send
2. Enter the GTS within the Pokémon game.
3. When prompted, type/copy-and-paste the file path to the PKM file you want to send. For example:
- After a short time, the Pokémon will appear on the DS and be placed in either an empty spot in your party or the first available PC box. This can take a few seconds, as for some reason the connection for this command is rather slow.

Note: Sending more than one Pokémon at a time is not currently possible. You'll need to exit and re-enter the GTS to send another Pokémon.

**Receive a Pokemon from the DS game**

Whenever you `offer` a Pokemon in the GTS, it's data will be received on the host machine automatically. You will receive an error on the DS stating that the Pokemon cannot be offered for trade - this ensures the Pokémon remains in your game. On your host machine it will automatically save the Pokémon under the `Pokemon` directory in the root of the project. It will check if the Pokémon's data has been saved before, to prevent creating duplicates.
Whenever you `offer` a Pokemon in the GTS, it's data will be received on the host machine automatically. You will receive an error on the DS stating that the Pokemon cannot be offered for trade - this ensures the Pokémon remains in your game. On your host machine it will automatically save the Pokémon under the `Pokemon` directory in the root of the project. It will check if the Pokémon's data has been saved before, to prevent creating duplicates.


## Support
Expand Down
19 changes: 19 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: '3.9'

services:
dns:
environment:
- IP=YOUR_HOST_IP_HERE
build: ./src/dns_server
ports:
- "53:53/udp"

http_server:
volumes:
- ./Pokemon:/Pokemon
- ./logs:/logs
build: ./src/http_server
depends_on:
- dns
ports:
- 80:80
19 changes: 0 additions & 19 deletions main.py

This file was deleted.

Empty file added src/dns_server/__init__.py
Empty file.
40 changes: 22 additions & 18 deletions src/dns_server.py → src/dns_server/dns_server.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
from datetime import datetime
import socket, threading
import dns.message
import dns.query
import dns.resolver
import dns.reversename
from .loghandler import LogHandler
from loguru import logger

dns_logging = LogHandler('dns_server', 'network.log').get_logger()
start_time = datetime.now()
logger.add(f"/logs/{start_time.strftime('%Y%m%d%H%M%S')}")

class DNSServer:
def __init__(self, dns_ip:str="178.62.43.212") -> None:
def __init__(self, dns_ip:str="178.62.43.212", prox_ip=None) -> None:
self.dns_ip = dns_ip

self.proxy_ip = self.get_proxy_ip()
self._proxy_ip = prox_ip
self.proxy_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.proxy_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.proxy_socket.bind(("0.0.0.0", 53))


def get_proxy_ip(self) -> str:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect((self.dns_ip, 53))
s_name = s.getsockname()[0]
return s_name
@property
def proxy_ip(self) -> str:
if not self._proxy_ip:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect((self.dns_ip, 53))
self._proxy_ip = s.getsockname()[0]
return self._proxy_ip


def start(self) -> None:
Expand All @@ -30,36 +34,36 @@ def start(self) -> None:


def start_as_thread(self):
dns_logging.info(f"DNSProxy server started. ")
dns_logging.info(f"Primary DNS server: {self.proxy_ip}")
logger.info(f"DNSProxy server started.")
logger.info(f"Primary DNS server: {self.proxy_ip}")
while True:
dns_query, client_address = self.proxy_socket.recvfrom(512)[:2]
dns_logging.debug(f"Received DNS query from {client_address}")
dns_query, client_address = self.proxy_socket.recvfrom(512)
logger.debug(f"Received DNS query from {client_address}")
self.handle_dns_query(dns_query, client_address)


def handle_dns_query(self, dns_query, client_address) -> None:
try:
request = dns.message.from_wire(dns_query)
# log all data in the request
dns_logging.debug(f"DNS query: {request.to_text()}")
logger.debug(f"DNS query: {request.to_text()}")
response = dns.query.udp(request, self.dns_ip)
dns_logging.debug(f"DNS response: {response.to_text()}")
logger.debug(f"DNS response: {response.to_text()}")

modified_response = self.modify_dns_response(response)
self.proxy_socket.sendto(modified_response.to_wire(), client_address)
except Exception as e:
dns_logging.error(f"Error handling DNS query: {e}")
logger.error(f"Error handling DNS query: {e}")


def modify_dns_response(self, response) -> dns.message.Message:
for answer in response.answer:
if answer.rdtype == dns.rdatatype.A:
for rd in answer:
dns_logging.debug(f"DNS returns IP {rd.address} for {answer.name}")
logger.debug(f"DNS returns IP {rd.address} for {answer.name}")
for domain in ["gamestats2.gs.nintendowifi.net"]: # "dls1.ilostmymind.xyz", eventually..
if answer.name == dns.name.from_text(domain):
dns_logging.debug(f"Changing IP for {answer.name} from {rd.address} to {self.proxy_ip}")
logger.debug(f"Changing IP for {answer.name} from {rd.address} to {self.proxy_ip}")
rd.address = self.proxy_ip
return response

Expand Down
16 changes: 16 additions & 0 deletions src/dns_server/dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM python:3.8.10

WORKDIR /dns_service
COPY . .

ENV PYTHONUNBUFFERED 1
ENV PYTHONPATH=/code
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8

EXPOSE 53/udp

RUN pip3 install --upgrade setuptools pip wheel
RUN pip3 install -r requirements.txt

CMD python3 main.py
13 changes: 13 additions & 0 deletions src/dns_server/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import os

from dns_server import DNSServer


def main():
ip = os.environ.get('IP')
dns_server = DNSServer(prox_ip=ip)
dns_server.start()


if __name__ == '__main__':
main()
5 changes: 5 additions & 0 deletions src/dns_server/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dnspython==1.16.0
Flask==3.0.2
Flask-Classful==0.16.0
colorlog==6.8.2
loguru==0.7.2
Empty file added src/http_server/__init__.py
Empty file.
3 changes: 2 additions & 1 deletion src/boxtoparty.py → src/http_server/boxtoparty.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from __future__ import division
from __future__ import absolute_import
from array import array
from src.pokemon import PokemonData

from pokemon import PokemonData

pkm = None

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
16 changes: 16 additions & 0 deletions src/http_server/dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM python:3.8.10

WORKDIR /http_server_service
COPY . .

ENV PYTHONUNBUFFERED 1
ENV PYTHONPATH=/code
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8

EXPOSE 80/tcp

RUN pip3 install --upgrade setuptools pip wheel
RUN pip3 install -r requirements.txt

CMD python3 main.py
72 changes: 52 additions & 20 deletions src/http_server.py → src/http_server/http_server.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
from flask import Flask, Response, request
import os
from datetime import datetime

from flask import Flask, Response, request, render_template
from flask_classful import FlaskView, route
from .pokemon import B64EncodedPokemon, Pokemon
from .loghandler import LogHandler
import os, logging
from loguru import logger

from pokemon import B64EncodedPokemon, Pokemon
from util import get_pkms

http_logging = LogHandler('http_server', 'network.log').get_logger()
gts_logging = LogHandler('gts_server', 'network.log').get_logger()
wc_logging = LogHandler('wc_server', 'network.log').get_logger()
werkzeug_logging = logging.getLogger('werkzeug')
werkzeug_logging.setLevel(logging.ERROR)
start_time = datetime.now()
logger.add(f"/logs/{start_time.strftime('%Y%m%d%H%M%S')}")
http_logging = logger.bind(resource='http_server')
gts_logging = logger.bind(resource='gts_server')
wc_logging = logger.bind(wc_server='wc_server')

selected_pkm_path = None
try:
selected_pkm_path = f'/Pokemon/{get_pkms()[0]}'
except Exception:
selected_pkm_path = None

class GTSResponse(Response):
def __init__(self, response=None, status=None, headers=None, content_type=None, **kwargs):
Expand Down Expand Up @@ -41,12 +51,37 @@ def handle_request():
return GTSResponse('c9KcX1Cry3QKS2Ai7yxL6QiQGeBGeQKR')

class GTSServer(FlaskView):
route_base = '/pokemondpds/worldexchange'
route_base = '/pokemondpds'

def __init__(self):
self.token = 'c9KcX1Cry3QKS2Ai7yxL6QiQGeBGeQKR'

@route('/info.asp', methods=['GET'])
@route('/worldexchange/selected', methods=['GET'])
def selected(self):
global selected_pkm_path
return str(selected_pkm_path), 200

@route('/worldexchange/list_options', methods=['GET'])
def list_options(self):
pkm_files = ['None']
pkm_files.extend(get_pkms())
return render_template('list_options.html', pkm_files=pkm_files)

@route('/worldexchange/select', methods=['POST'])
def select_option(self):
global selected_pkm_path
pkm_file = request.form.get('pkm')
if not pkm_file:
return f'no value provided', 400
if pkm_file == 'None':
selected_pkm_path = None
return f"hosting {selected_pkm_path}", 200
if pkm_file not in os.listdir('/Pokemon/'):
return f'{pkm_file} not found in /Pokemon', 400
selected_pkm_path = os.path.join('/Pokemon', pkm_file)
return f'hosting {pkm_file}', 200

@route('/worldexchange/info.asp', methods=['GET'])
def info(self):
gts_logging.info('Connection Established.')
return GTSResponse(b'\x01\x00')
Expand All @@ -55,7 +90,7 @@ def info(self):
def set_profile(self):
return GTSResponse(b'\x00' * 8)

@route('/post.asp', methods=['GET'])
@route('/worldexchange/post.asp', methods=['GET'])
def post(self):

gts_logging.info('Receiving Pokemon...')
Expand All @@ -64,19 +99,16 @@ def post(self):
pokemon.dump()
return GTSResponse(b'\x0c\x00')

@route('/search.asp', methods=['GET'])
@route('/worldexchange/search.asp', methods=['GET'])
def search(self):
return GTSResponse(b'')

@route('/result.asp', methods=['GET'])
@route('/worldexchange/result.asp', methods=['GET'])
def result(self):

print('Enter the path or drag the pkm file here')
print('Leave blank to not send a Pokémon')
path = input().strip()
global selected_pkm_path
path = selected_pkm_path

if path:
path = os.path.normpath(path).lower()
pokemon = Pokemon()
pokemon_data = pokemon.load(path)

Expand All @@ -96,7 +128,7 @@ def result(self):

return GTSResponse(b'\x05\x00')

@route('/delete.asp', methods=['GET'])
@route('/worldexchange/delete.asp', methods=['GET'])
def delete(self):
return GTSResponse(b'\x01\x00')

Expand Down
7 changes: 7 additions & 0 deletions src/http_server/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from http_server import app

def main():
app.run(host='0.0.0.0', port=80, debug=False)

if __name__ == '__main__':
main()
Loading