diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3438ad9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+# Python stuff
+
+__pycache__/
+build/
+*.pyc
diff --git a/.install_files/application-x-mcpimod.png b/.install_files/application-x-mcpimod.png
new file mode 100644
index 0000000..79a9542
Binary files /dev/null and b/.install_files/application-x-mcpimod.png differ
diff --git a/install_files/icon.png b/.install_files/icon.png
similarity index 100%
rename from install_files/icon.png
rename to .install_files/icon.png
diff --git a/.install_files/mcpimod.xml b/.install_files/mcpimod.xml
new file mode 100644
index 0000000..fb00ca5
--- /dev/null
+++ b/.install_files/mcpimod.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ Minecraft Pi Mod
+
+
+
+
diff --git a/install_files/minecraft-pe b/.install_files/minecraft-pe
similarity index 100%
rename from install_files/minecraft-pe
rename to .install_files/minecraft-pe
diff --git a/install_files/minecraft-pe.bsdiff b/.install_files/minecraft-pe.bsdiff
similarity index 100%
rename from install_files/minecraft-pe.bsdiff
rename to .install_files/minecraft-pe.bsdiff
diff --git a/install_files/mcpil.desktop b/.install_files/tk.mcpi.mcpil.desktop
similarity index 57%
rename from install_files/mcpil.desktop
rename to .install_files/tk.mcpi.mcpil.desktop
index ffa4575..9a98a1b 100644
--- a/install_files/mcpil.desktop
+++ b/.install_files/tk.mcpi.mcpil.desktop
@@ -1,9 +1,11 @@
[Desktop Entry]
Name=MCPIL
Comment=Minecraft Pi Launcher
-Exec=$(EXECUTABLE_PATH)
-Icon=$(ICON_PATH)
+Exec=$(EXECUTABLE_PATH) %f
+Icon=mcpil
Terminal=false
Type=Application
Categories=Application;Game;
+MimeType=application/x-mcpimod
StartupNotify=true
+GenericName=Minecraft Pi Launcher
diff --git a/README.md b/README.md
index f4607b1..5e89357 100644
--- a/README.md
+++ b/README.md
@@ -7,25 +7,49 @@ A simple launcher for Minecraft: Pi Edition.
## Getting started
### Prerequisites
-To use MCPIL you need to have ``Python> = 3.8.x`` pre-installed and root privileges.
+To use MCPIL you need to have `Python >= 3.7.x` pre-installed and root privileges.
### Installation
To install MCPIL, download or clone the repository:
-``` shell
+```shell
git clone https://github.com/Alvarito050506/MCPIL.git
```
-and then run the file ``install.py``. It will create a desktop file that you can access in the "Games" category.
+and then run the `install.py` file. It will create a desktop file that you can access in the "Games" category.
## Features
+ Switch between Minecraft Pi and PE
+ Username change
+ Skin change
+ Mod load
++ Mod API
++ Mod compilation
+ World game mode and name change
-+ Coming soon: join non-local server
++ Join non-local servers
++ Coming soon: Pi Realms
## Usage
-Launch the MCPIL desktop file or run the ``mcpil.py`` file to see the magick! :wink:
+Launch the MCPIL desktop file or run the `mcpil.py` file to see the magick! :wink:
+
+## API
+There is an MCPIL API that you can use by importing the `mcpil` module into your Python mod. It exposes the following functions:
+
+### `def get_user_name()`
+Returns the user name of the player.
+
+### `def get_world_name()`
+Returns the name of the current world.
+
+### `def get_game_mode()`
+Returns the game mode of the current world as an interger:
+ + 0 = Survival
+ + 1 = Creative
+
+## Mod compilation
+To compile (compress) a Python mod, run the `mcpim.py` file passing the filename of the mod as the first argument. For example:
+```shell
+./mcpim.py example.py
+```
+will produce a `example.mcpi` mod file.
## Thanks
To [@Phirel](https://www.minecraftforum.net/members/Phirel) for his Pi2PE (a.k.a. "survival") patch.
diff --git a/api/mcpil/__init__.py b/api/mcpil/__init__.py
new file mode 100644
index 0000000..c73823e
--- /dev/null
+++ b/api/mcpil/__init__.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# __init__.py
+#
+# Copyright 2020 Alvarito050506
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+#
+#
+
+import sys
+import psutil
+from os import environ
+
+def get_user_name():
+ return environ.get("MCPIL_USERNAME");
+
+def get_world_name():
+ mcpi_process = psutil.Process(int(environ.get("MCPIL_PID")));
+ return mcpi_process.open_files()[-1].path.split("/")[-2];
+
+def get_game_mode():
+ world_name = get_world_name();
+ world_file = open("/root/.minecraft/games/com.mojang/minecraftWorlds/" + world_name + "/level.dat", "rb");
+ world_file.seek(0x16);
+ game_mode = int.from_bytes(world_file.read(1), "little");
+ world_file.close();
+ return game_mode;
diff --git a/api/setup.py b/api/setup.py
new file mode 100644
index 0000000..6a5a804
--- /dev/null
+++ b/api/setup.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# setup.py
+#
+# Copyright 2020 Alvarito050506
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+#
+#
+
+from distutils.core import setup
+from os import chdir, path
+
+chdir(path.dirname(__file__));
+
+setup(
+ name="mcpil",
+ version="v0.1.1",
+ description="MCPI API extensions",
+ author="Alvarito050506",
+ packages=["mcpil"],
+ classifiers=[
+ "Development Status :: 3 - Alpha",
+ "Environment :: Console",
+ "Operating System :: POSIX :: Linux",
+ "Programming Language :: Python :: 3 :: Only",
+ "License :: OSI Approved :: GNU General Public License v2 (GPLv2)"
+ ]
+);
diff --git a/mods/example.py b/example.py
similarity index 58%
rename from mods/example.py
rename to example.py
index 472c26e..43294e6 100755
--- a/mods/example.py
+++ b/example.py
@@ -3,6 +3,7 @@
import sys
import time
from mcpi import *
+from mcpil import *
mc = minecraft.Minecraft.create();
@@ -10,7 +11,12 @@ def main(args):
mc.setting("world_immutable", True);
mc.camera.setFollow();
mc.saveCheckpoint();
- mc.postToChat("Welcome to Minecraft Pi.");
+ mc.postToChat("Welcome to Minecraft Pi, " + get_user_name() + ".");
+ mc.postToChat("The name of this awesome world is \"" + get_world_name() + "\".");
+ if get_game_mode() == 0:
+ mc.postToChat("You are in Survival mode.");
+ else:
+ mc.postToChat("You are in Creative mode.");
time.sleep(5);
mc.setting("world_immutable", False);
mc.setting("nametags_visible", True);
diff --git a/install.py b/install.py
old mode 100644
new mode 100755
index 74eba95..50d892a
--- a/install.py
+++ b/install.py
@@ -1,5 +1,25 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
+#
+# install.py
+#
+# Copyright 2020 Alvarito050506
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+#
+#
import subprocess
import sys
@@ -14,9 +34,11 @@ def main():
if os.geteuid() != 0:
sys.stdout.write("Error: You need to have root privileges to run this installer.\n");
+ return 1;
sys.stdout.write("Installing dependencies... ");
subprocess.call(["sudo", "apt-get", "install", "python3-tk", "wget", "bspatch", "-qq"]);
+ subprocess.call(["sudo", "pip3", "install", "psutil", "-qq"]);
sys.stdout.write("OK.\n");
sys.stdout.write("Downloading Minecraft... ");
@@ -24,21 +46,30 @@ def main():
sys.stdout.write("OK.\n");
sys.stdout.write("Installing Minecraft... ");
- subprocess.call(["bspatch", "/opt/minecraft-pi/minecraft-pi", "/opt/minecraft-pi/minecraft-pe", "install_files/minecraft-pe.bsdiff"]);
+ subprocess.call(["bspatch", "/opt/minecraft-pi/minecraft-pi", "/opt/minecraft-pi/minecraft-pe", "./.install_files/minecraft-pe.bsdiff"]);
subprocess.call(["chmod", "a+x", "/opt/minecraft-pi/minecraft-pe"]);
sys.stdout.write("OK.\n");
+ sys.stdout.write("Installing API... ");
+ subprocess.call(["python3", "./api/setup.py", "-q", "install"]);
+ sys.stdout.write("OK.\n");
+
sys.stdout.write("Configuring... ");
- desktop_template = open("install_files/mcpil.desktop", "r");
- desktop_file = open("/usr/share/applications/mcpil.desktop", "w");
- desktop_file.write(desktop_template.read().replace("$(EXECUTABLE_PATH)", os.getcwd() + "/mcpil.py").replace("$(ICON_PATH)", os.getcwd() + "/install_files/icon.png"));
+ desktop_template = open("./.install_files/tk.mcpi.mcpil.desktop", "r");
+ desktop_file = open("/usr/share/applications/tk.mcpi.mcpil.desktop", "w");
+ desktop_file.write(desktop_template.read().replace("$(EXECUTABLE_PATH)", os.getcwd() + "/mcpil.py").replace("$(ICON_PATH)", os.getcwd() + "/.install_files/icon.png"));
desktop_template.close();
desktop_file.close();
-
- shutil.copy2("./install_files/minecraft-pe", "/usr/bin/minecraft-pe");
+
+ shutil.copy2("./.install_files/minecraft-pe", "/usr/bin/minecraft-pe");
subprocess.call(["chmod", "a+x", "/usr/bin/minecraft-pe"]);
subprocess.call(["chmod", "a+x", "mcpil.py"]);
+
+ shutil.copy2("./.install_files/icon.png", "/usr/share/pixmaps/mcpil.png");
+ shutil.copy2("./.install_files/application-x-mcpimod.png", "/usr/share/pixmaps/application-x-mcpimod.png");
+ shutil.copy2("./.install_files/mcpimod.xml", "/usr/share/mime/packages/application-x-mcpimod.xml");
+ subprocess.call(["update-mime-database", "/usr/share/mime"]);
sys.stdout.write("OK.\n");
return 0;
diff --git a/mcpil.py b/mcpil.py
index 8f9cdb1..f341e67 100755
--- a/mcpil.py
+++ b/mcpil.py
@@ -1,12 +1,34 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
+#
+# mcpil.py
+#
+# Copyright 2020 Alvarito050506
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+#
+#
import sys
import subprocess
import atexit
import signal
import webbrowser
-from os import walk, remove, path, chdir, kill, rename
+import psutil
+import time
+from os import walk, remove, path, chdir, kill, rename, environ, uname, geteuid
from tkinter import *
from tkinter import ttk
from tkinter.filedialog import askopenfilename
@@ -22,7 +44,11 @@ def on_select_versions(event):
return 0;
def launch():
- subprocess.Popen([binaries[current_selection]]);
+ global mcpi_pid;
+ mcpi_process = subprocess.Popen([binaries[current_selection]]);
+ time.sleep(2);
+ mcpi_pid = mcpi_process.pid + 2;
+ start_mods();
return 0;
def save_settings():
@@ -55,8 +81,9 @@ def on_select_mods(event):
delete_button["state"] = DISABLED;
return 0;
-def install_mod():
- mod_file = askopenfilename(filetypes=[("Python scripts", "*.py")]);
+def install_mod(mod_file=None):
+ if mod_file is None:
+ mod_file = askopenfilename(filetypes=[("Minecraft Pi Mods", "*.mcpi *.py")]);
copy2(mod_file, "./mods/" + path.basename(mod_file));
update_mods();
delete_button["state"] = DISABLED;
@@ -78,13 +105,19 @@ def update_mods():
mods.delete(0, END);
while i < len(mod_files):
- mods.insert(i, mod_files[i]);
+ mods.insert(i, mod_files[i].replace(".py", "").replace(".mcpi", ""));
i += 1;
return 0;
def start_mods():
global mods_process;
- mods_process = subprocess.Popen(["python3", "mcpim.py"]);
+ mods_env = environ.copy();
+ mcpi_file = open("/opt/minecraft-pi/minecraft-pi", "rb");
+ mcpi_file.seek(0xfa8ca);
+ mods_env["MCPIL_USERNAME"] = mcpi_file.read(7).decode("utf-8");
+ mcpi_file.close();
+ mods_env["MCPIL_PID"] = str(mcpi_pid);
+ mods_process = subprocess.Popen(["./mcpim.py"], env=mods_env);
return 0;
def kill_mods():
@@ -93,6 +126,7 @@ def kill_mods():
def change_skin():
skin_file = askopenfilename(filetypes=[("Portable Network Graphics", "*.png")]);
+ copy2("/opt/minecraft-pi/data/images/mob/char.png", "/opt/minecraft-pi/data/images/mob/char_original.png");
copy2(skin_file, "/opt/minecraft-pi/data/images/mob/char.png");
return 0;
@@ -101,16 +135,16 @@ def web_open(event):
return 0;
def save_world():
- old_worldname = old_worldname_entry.get();
- new_worldname = new_worldname_entry.get();
- world_file = open("/root/.minecraft/games/com.mojang/minecraftWorlds/" + old_worldname + "/level.dat", "rb+");
- new_world = world_file.read().replace(bytes([len(old_worldname)]) + bytes([0]) + bytes(old_worldname.encode()), bytes([len(new_worldname)]) + bytes([0]) + bytes(new_worldname.encode()));
+ old_world_name = old_worldname_entry.get();
+ new_world_name = new_worldname_entry.get();
+ world_file = open("/root/.minecraft/games/com.mojang/minecraftWorlds/" + old_world_name + "/level.dat", "rb+");
+ new_world = world_file.read().replace(bytes([len(old_world_name)]) + bytes([0]) + bytes(old_world_name.encode()), bytes([len(new_world_name)]) + bytes([0]) + bytes(new_world_name.encode()));
world_file.seek(0);
world_file.write(new_world);
world_file.seek(0x16);
world_file.write(bytes([game_mode.get()]));
world_file.close();
- rename("/root/.minecraft/games/com.mojang/minecraftWorlds/" + old_worldname, "/root/.minecraft/games/com.mojang/minecraftWorlds/" + new_worldname);
+ rename("/root/.minecraft/games/com.mojang/minecraftWorlds/" + old_world_name, "/root/.minecraft/games/com.mojang/minecraftWorlds/" + new_world_name);
return 0;
def set_default_worldname(event):
@@ -118,6 +152,20 @@ def set_default_worldname(event):
new_worldname_entry.insert(0, old_worldname_entry.get());
return True;
+def add_server():
+ global proxy_process;
+ server_addr = server_addr_entry.get();
+ server_port = server_port_entry.get();
+ proxy_process = subprocess.Popen(["./mcpip.py", server_addr, server_port]);
+ return 0;
+
+def kill_proxy():
+ try:
+ kill(proxy_process.pid, signal.SIGTERM);
+ except NameError:
+ pass;
+ return 0;
+
def play_tab(parent):
global description_text;
@@ -241,6 +289,37 @@ def worlds_tab(parent):
buttons_frame.pack(fill=BOTH, expand=True);
return tab;
+def servers_tab(parent):
+ global server_addr_entry;
+ global server_port_entry;
+ tab = Frame(parent);
+
+ title = Label(tab, text="Servers");
+ title.config(font=("", 24));
+ title.pack();
+
+ server_addr_frame = Frame(tab);
+ server_addr_text = Label(server_addr_frame, text="Server addres:");
+ server_addr_text.pack(side=LEFT, anchor=N, pady=16, padx=8);
+
+ server_addr_entry = Entry(server_addr_frame, width=32);
+ server_addr_entry.pack(side=RIGHT, anchor=N, pady=16, padx=8);
+ server_addr_frame.pack(fill=X);
+
+ server_port_frame = Frame(tab);
+ server_port_text = Label(server_port_frame, text="Server port:");
+ server_port_text.pack(side=LEFT, anchor=N, padx=8);
+
+ server_port_entry = Entry(server_port_frame, width=32);
+ server_port_entry.pack(side=RIGHT, anchor=N, padx=8);
+ server_port_frame.pack(fill=X);
+
+ buttons_frame = Frame(tab);
+ start_button = Button(buttons_frame, text="Add server", command=add_server);
+ start_button.pack(side=RIGHT, anchor=S);
+ buttons_frame.pack(fill=BOTH, expand=True);
+ return tab;
+
def about_tab(parent):
tab = Frame(parent);
@@ -248,7 +327,7 @@ def about_tab(parent):
title.config(font=("", 24));
title.pack();
- version = Label(tab, text="v0.1.1");
+ version = Label(tab, text="v0.2.0");
version.config(font=("", 10));
version.pack();
@@ -264,25 +343,36 @@ def about_tab(parent):
return tab;
def main(args):
+ if "arm" not in uname()[4] and "aarch" not in uname()[4]:
+ sys.stdout.write("Error: Minecraft Pi Launcher must run on a Raspberry Pi.\n");
+ return 1;
+
+ if geteuid() != 0:
+ sys.stdout.write("Error: You need to have root privileges to run this program.\n");
+ return 1;
+
global mods_process;
chdir(path.dirname(__file__));
window = Tk();
window.title("MCPI Laucher");
window.geometry("480x348");
window.resizable(False, False);
- window.iconphoto(True, PhotoImage(file="install_files/icon.png"))
+ window.iconphoto(True, PhotoImage(file="./.install_files/icon.png"))
tabs = ttk.Notebook(window);
tabs.add(play_tab(tabs), text="Play");
tabs.add(settings_tab(tabs), text="Settings");
tabs.add(mods_tab(tabs), text="Mods");
tabs.add(worlds_tab(tabs), text="Worlds");
+ tabs.add(servers_tab(tabs), text="Servers");
tabs.add(about_tab(tabs), text="About");
tabs.pack(fill=BOTH, expand=True);
- copy2("/opt/minecraft-pi/data/images/mob/char.png", "/opt/minecraft-pi/data/images/mob/char_original.png");
- start_mods();
+ if len(args) > 1:
+ install_mod(args[1]);
+
atexit.register(kill_mods);
+ atexit.register(kill_proxy);
window.mainloop();
return 0;
diff --git a/mcpim.py b/mcpim.py
old mode 100644
new mode 100755
index ce819b2..ea87e65
--- a/mcpim.py
+++ b/mcpim.py
@@ -1,11 +1,34 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
+#
+# mcpim.py
+#
+# Copyright 2020 Alvarito050506
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+#
+#
import sys
import subprocess
import signal
import atexit
-from os import walk, kill
+import zlib
+import shutil
+import time
+from os import walk, kill, environ, remove, path, mkdir
from mcpi import *
def kill_mods():
@@ -15,7 +38,16 @@ def kill_mods():
i += 1;
return 0;
-def main():
+def main(args):
+ if len(args) > 1:
+ mod_file = open(args[1], "rb");
+ mod_code = zlib.compress(mod_file.read());
+ mod_file.close();
+ mod_file = open(args[1].replace(".py", ".mcpi"), "wb");
+ mod_file.write(mod_code);
+ mod_file.close();
+ return 0;
+
global mods_processes;
mods_processes = [];
mod_files = [];
@@ -28,13 +60,25 @@ def main():
for (_, _, files) in walk("mods"):
mod_files.extend(files);
while i < len(mod_files):
- subprocess.Popen(["python3", "mods/" + mod_files[i]]);
+ if mod_files[i][-5:] == ".mcpi":
+ mod_file = open(mod_files[i], "rb");
+ mod_code = zlib.decompress(mod_file.read()).decode("utf-8");
+ mod_file.close();
+ mod_name = "./mods/." + mod_files[i].replace(".mcpi", ".py");
+ mod_file = open(mod_name, "w");
+ mod_file.write(mod_code);
+ mod_file.close();
+ subprocess.Popen(["python3", mod_name], env=environ);
+ time.sleep(5);
+ remove(mod_name);
+ else:
+ subprocess.Popen(["python3", "mods/" + mod_files[i]], env=environ);
i += 1;
atexit.register(kill_mods);
j = 1;
- except:
+ except ConnectionRefusedError:
pass;
return 0;
if __name__ == '__main__':
- sys.exit(main());
+ sys.exit(main(sys.argv));
diff --git a/mcpip.py b/mcpip.py
new file mode 100755
index 0000000..77ab906
--- /dev/null
+++ b/mcpip.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# mcpip.py
+#
+# Copyright 2020 Alvarito050506
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+#
+#
+
+
+import sys
+import socket
+
+def main(args):
+ ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP);
+ sc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP);
+ server = (args[1], int(args[2]));
+ client = ("0.0.0.0", 19134);
+ sc.setblocking(0);
+ ss.setblocking(0);
+ sc.bind(client);
+ client_addr = None;
+
+ while True:
+ try:
+ data, addr = ss.recvfrom(16384);
+ sc.sendto(data, client_addr);
+ except BlockingIOError:
+ pass;
+ try:
+ data, addr = sc.recvfrom(16384);
+ client_addr = addr;
+ ss.sendto(data, server);
+ except BlockingIOError:
+ pass;
+ return 0;
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv));
diff --git a/mods/example.mcpi b/mods/example.mcpi
new file mode 100644
index 0000000..6a6f838
Binary files /dev/null and b/mods/example.mcpi differ
diff --git a/screenshot.png b/screenshot.png
index 8f6f285..e3f2f98 100644
Binary files a/screenshot.png and b/screenshot.png differ