├── images ├── configpage.png ├── serverlist.png ├── serverlist-failed.png └── serverlist-refreshing.png ├── Config.h ├── LICENSE ├── OTA.ino ├── ProxyServer.h ├── Config.ino ├── Readme.md ├── ESP32_MinecraftProxy.ino ├── MQTT.ino ├── WiFi.ino ├── ProxyServer.ino └── Webserver.ino /images/configpage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3gg0/ESP32_MinecraftProxy/master/images/configpage.png -------------------------------------------------------------------------------- /images/serverlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3gg0/ESP32_MinecraftProxy/master/images/serverlist.png -------------------------------------------------------------------------------- /images/serverlist-failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3gg0/ESP32_MinecraftProxy/master/images/serverlist-failed.png -------------------------------------------------------------------------------- /images/serverlist-refreshing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3gg0/ESP32_MinecraftProxy/master/images/serverlist-refreshing.png -------------------------------------------------------------------------------- /Config.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H__ 2 | #define __CONFIG_H__ 3 | 4 | #define CONFIG_MAGIC 0xE1AAFF00 5 | #define CONFIG_SERVER_COUNT 2 6 | 7 | #define CONFIG_SOFTAPNAME "esp32-minecraft" 8 | #define CONFIG_OTANAME "MinecraftProxy" 9 | 10 | 11 | typedef struct 12 | { 13 | uint32_t magic; 14 | uint32_t verbose; 15 | uint32_t mqtt_publish; 16 | uint32_t current_entry; 17 | char hostname[32]; 18 | char wifi_ssid[32]; 19 | char wifi_password[32]; 20 | char remote_name[CONFIG_SERVER_COUNT][64]; 21 | char remote_server[CONFIG_SERVER_COUNT][64]; 22 | int remote_port[CONFIG_SERVER_COUNT]; 23 | int local_port; 24 | char mqtt_server[32]; 25 | int mqtt_port; 26 | char mqtt_user[32]; 27 | char mqtt_password[32]; 28 | char mqtt_client[32]; 29 | } t_cfg; 30 | 31 | 32 | extern t_cfg current_config; 33 | 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 g3gg0.de 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OTA.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | bool ota_active = false; 5 | bool ota_setup_done = false; 6 | long ota_offtime = 0; 7 | 8 | void ota_setup() 9 | { 10 | if(ota_setup_done) 11 | { 12 | ota_enable(); 13 | return; 14 | } 15 | ArduinoOTA.setHostname(CONFIG_OTANAME); 16 | 17 | ArduinoOTA.onStart([]() { 18 | String type; 19 | ota_active = true; 20 | ota_offtime = millis() + 600000; 21 | }) 22 | .onEnd([]() { 23 | ota_active = false; 24 | }) 25 | .onProgress([](unsigned int progress, unsigned int total) { 26 | }) 27 | .onError([](ota_error_t error) { 28 | Serial.printf("Error[%u]: ", error); 29 | if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); 30 | else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); 31 | else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); 32 | else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); 33 | else if (error == OTA_END_ERROR) Serial.println("End Failed"); 34 | }); 35 | 36 | ArduinoOTA.begin(); 37 | 38 | Serial.printf("[OTA] Setup finished\n"); 39 | ota_setup_done = true; 40 | ota_enable(); 41 | } 42 | 43 | void ota_enable() 44 | { 45 | Serial.printf("[OTA] Enabled\n"); 46 | ota_offtime = millis() + 600000; 47 | } 48 | 49 | bool ota_enabled() 50 | { 51 | return (ota_offtime > millis() || ota_active); 52 | } 53 | 54 | bool ota_loop() 55 | { 56 | if(ota_enabled()) 57 | { 58 | ArduinoOTA.handle(); 59 | } 60 | 61 | return ota_active; 62 | } 63 | -------------------------------------------------------------------------------- /ProxyServer.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROXY_SERVER__ 2 | #define __PROXY_SERVER__ 3 | 4 | #define PROT_UNCONN_PING 0x01 5 | #define PROT_UNCONN_PONG 0x1C 6 | #define STR_FIELD_SIZE 64 7 | #define STR_FIELD_COUNT 12 8 | 9 | /* this type has STR_FIELD_COUNT uniform entries for simpler string parsing */ 10 | typedef struct { 11 | char edition[STR_FIELD_SIZE]; 12 | char motd[STR_FIELD_SIZE]; 13 | char prot_ver[STR_FIELD_SIZE]; 14 | char game_ver[STR_FIELD_SIZE]; 15 | char players[STR_FIELD_SIZE]; 16 | char max_players[STR_FIELD_SIZE]; 17 | char server_id[STR_FIELD_SIZE]; 18 | char sub_motd[STR_FIELD_SIZE]; 19 | char game_type[STR_FIELD_SIZE]; 20 | char extra[STR_FIELD_SIZE]; 21 | char port_ipv4[STR_FIELD_SIZE]; 22 | char port_ipv6[STR_FIELD_SIZE]; 23 | } t_server_string; 24 | 25 | typedef struct { 26 | uint64_t server_time; 27 | uint64_t guid; 28 | uint8_t magic[16]; 29 | uint16_t server_len; 30 | uint8_t server_string; 31 | } t_pong_packet; 32 | 33 | class ProxyServer 34 | { 35 | public: 36 | ProxyServer (char *, char *, int, int); 37 | void ClientReceived(AsyncUDPPacket&); 38 | void ServerReceived(AsyncUDPPacket&); 39 | void Loop(); 40 | char *ServerName; 41 | char *ServerAddress; 42 | int ServerPort; 43 | int LocalPort; 44 | 45 | int ClientPort; 46 | IPAddress ClientAddress; 47 | 48 | private: 49 | AsyncUDP client_udp; 50 | AsyncUDP server_udp; 51 | IPAddress proxy_client_addr; 52 | IPAddress proxy_server_addr; 53 | int proxy_out_port; 54 | uint64_t last_server_packet = 0; 55 | char *server_name; 56 | 57 | int AssembleString(t_server_string *server_str, uint8_t *buf); 58 | t_server_string *ParseString(uint8_t *string, int length); 59 | void SendPacket(IPAddress dst, uint16_t port); 60 | }; 61 | 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /Config.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "Config.h" 6 | 7 | t_cfg current_config; 8 | 9 | void cfg_save() 10 | { 11 | File file = SPIFFS.open("/config.dat", "w"); 12 | if (!file || file.isDirectory()) 13 | { 14 | return; 15 | } 16 | 17 | if (strlen(current_config.hostname) < 2) 18 | { 19 | strcpy(current_config.hostname, "MinecraftProxy"); 20 | } 21 | 22 | file.write((uint8_t *)¤t_config, sizeof(current_config)); 23 | file.close(); 24 | } 25 | 26 | void cfg_reset() 27 | { 28 | memset(¤t_config, 0x00, sizeof(current_config)); 29 | 30 | current_config.magic = CONFIG_MAGIC; 31 | strcpy(current_config.hostname, "MinecraftProxy"); 32 | 33 | strcpy(current_config.mqtt_server, "(not set)"); 34 | current_config.mqtt_port = 11883; 35 | strcpy(current_config.mqtt_user, "(not set)"); 36 | strcpy(current_config.mqtt_password, "(not set)"); 37 | strcpy(current_config.mqtt_client, "MinecraftProxy"); 38 | 39 | strcpy(current_config.wifi_ssid, "(not set)"); 40 | strcpy(current_config.wifi_password, "(not set)"); 41 | 42 | strcpy(current_config.remote_name[0], "Creative"); 43 | strcpy(current_config.remote_server[0], ""); 44 | current_config.remote_port[0] = 19132; 45 | 46 | strcpy(current_config.remote_name[1], "Survival"); 47 | strcpy(current_config.remote_server[1], ""); 48 | current_config.remote_port[1] = 19134; 49 | 50 | current_config.local_port = 19132; 51 | 52 | current_config.current_entry = 0; 53 | 54 | current_config.verbose = 0; 55 | current_config.mqtt_publish = 0; 56 | 57 | cfg_save(); 58 | } 59 | 60 | void cfg_read() 61 | { 62 | File file = SPIFFS.open("/config.dat", "r"); 63 | 64 | if (!file || file.isDirectory()) 65 | { 66 | cfg_reset(); 67 | } 68 | else 69 | { 70 | file.read((uint8_t *)¤t_config, sizeof(current_config)); 71 | file.close(); 72 | 73 | if (current_config.magic != CONFIG_MAGIC) 74 | { 75 | cfg_reset(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # MinecraftProxy 2 | 3 | This is a ESP32 compatible LAN-to-dedicated server proxy, similar to [proxy phantom](https://github.com/jhead/phantom). 4 | It listens on the local network for minecraft's discovery messages and replies them, while also forwarding to the configured server. 5 | When the server responds, the packets get forwarded to the requesting host. 6 | 7 | Using this proxy you can play with your Playstation 4 or Xbox on dedicated bedrock servers. 8 | It shows up as a local LAN server and acts like one, just by forwarding all packets to the internet server you configured. 9 | 10 | The current implementation only supports one client connection and will probably fail if you try so. 11 | 12 | ## Server announcement 13 | When starting up, the sketch toggles between your configured servers, switching after every restart. 14 | So you can set up two different servers and choose which one you want to connect by resetting your ESP32. 15 | 16 | There is an extra server entry shown for a status information. 17 | Within the first few seconds, you might see this status message: 18 | 19 | ![Server refreshing](images/serverlist-refreshing.png "Servers shown when the ping was sent but nothing received yet") 20 | 21 | If the response times out, an error message is shown. 22 | 23 | ![Server failed](images/serverlist-failed.png "Servers shown when the ping timed out") 24 | 25 | However, if everything goes well you get your deidcated server listed with a "via proxy" prefix. 26 | Now you can connect to it as if the server was in your LAN. 27 | 28 | ![Server responded](images/serverlist.png "Servers shown when we are ready to connect") 29 | 30 | ## Configuration 31 | Before connecting, you have to configure your WiFi and server settings. 32 | You can either hardcode these settings in Config.ino or use the AP mode of this sketch. 33 | 34 | Whenever the WiFi connection fails, the sketch creates an access point named "esp32-minecraft", you have to connect to. 35 | In theory your mobile should open the captive portal, I tried to implement. However that doesn't seem to work although I answer all DNS queries with a 302 to http://192.168.4.1/ 36 | 37 | So until someone figures out how to fix this, you have to follow http://192.168.4.1/ manually and enter your WiFi credentials there. 38 | 39 | ![Config page](images/configpage.png "Configuration page") -------------------------------------------------------------------------------- /ESP32_MinecraftProxy.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "Config.h" 11 | #include "ProxyServer.h" 12 | 13 | #define MIN(a,b) (((a)<(b))?(a):(b)) 14 | #define MAX(a,b) (((a)>(b))?(a):(b)) 15 | 16 | bool connected = false; 17 | uint64_t bytes_transferred = 0; 18 | 19 | void setup() 20 | { 21 | Serial.begin(115200); 22 | Serial.printf("\n\n\n"); 23 | 24 | Serial.printf("[i] SDK: '%s'\n", ESP.getSdkVersion()); 25 | Serial.printf("[i] CPU Speed: %d MHz\n", ESP.getCpuFreqMHz()); 26 | Serial.printf("[i] Chip Id: %06llX\n", ESP.getEfuseMac()); 27 | Serial.printf("[i] Flash Mode: %08X\n", ESP.getFlashChipMode()); 28 | Serial.printf("[i] Flash Size: %08X\n", ESP.getFlashChipSize()); 29 | Serial.printf("[i] Flash Speed: %d MHz\n", ESP.getFlashChipSpeed() / 1000000); 30 | Serial.printf("[i] Heap %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize()); 31 | Serial.printf("[i] SPIRam %d/%d\n", ESP.getFreePsram(), ESP.getPsramSize()); 32 | Serial.printf("\n"); 33 | Serial.printf("[i] Starting\n"); 34 | Serial.printf("[i] Setup SPIFFS\n"); 35 | if (!SPIFFS.begin(true)) 36 | { 37 | Serial.println("[E] SPIFFS Mount Failed"); 38 | } 39 | 40 | cfg_read(); 41 | current_config.current_entry++;; 42 | current_config.current_entry %= CONFIG_SERVER_COUNT; 43 | cfg_save(); 44 | 45 | Serial.printf("[i] Setup WiFi\n"); 46 | wifi_setup(); 47 | Serial.printf("[i] Setup Webserver\n"); 48 | www_setup(); 49 | Serial.printf("[i] Setup MQTT\n"); 50 | mqtt_setup(); 51 | } 52 | 53 | ProxyServer *CurrentProxy; 54 | 55 | void loop() 56 | { 57 | wifi_loop(); 58 | www_loop(); 59 | ota_loop(); 60 | mqtt_loop(); 61 | 62 | if (WiFi.status() == WL_CONNECTED) 63 | { 64 | if(!connected) 65 | { 66 | connected = true; 67 | CurrentProxy = new ProxyServer(current_config.remote_name[current_config.current_entry], current_config.remote_server[current_config.current_entry], current_config.remote_port[current_config.current_entry], current_config.local_port); 68 | } 69 | CurrentProxy->Loop(); 70 | } 71 | else 72 | { 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /MQTT.ino: -------------------------------------------------------------------------------- 1 | #define MQTT_DEBUG 2 | 3 | #include 4 | #include 5 | 6 | 7 | #define COMMAND_TOPIC "tele/mc_proxy/command" 8 | #define RESPONSE_TOPIC "tele/mc_proxy/response" 9 | 10 | WiFiClient client; 11 | PubSubClient mqtt(client); 12 | 13 | extern uint64_t bytes_transferred; 14 | 15 | int mqtt_last_publish_time = 0; 16 | int mqtt_lastConnect = 0; 17 | int mqtt_retries = 0; 18 | bool mqtt_fail = false; 19 | 20 | void callback(char *topic, byte *payload, unsigned int length) 21 | { 22 | Serial.print("Message arrived ["); 23 | Serial.print(topic); 24 | Serial.print("] "); 25 | Serial.print("'"); 26 | for (int i = 0; i < length; i++) 27 | { 28 | Serial.print((char)payload[i]); 29 | } 30 | Serial.print("'"); 31 | Serial.println(); 32 | 33 | payload[length] = 0; 34 | 35 | if(!strcmp(topic, COMMAND_TOPIC)) 36 | { 37 | char *command = (char *)payload; 38 | 39 | if(!strncmp(command, "http", 4)) 40 | { 41 | char buf[1024]; 42 | sprintf(buf, "updating from: '%s'", command); 43 | Serial.printf("%s\n", buf); 44 | mqtt.publish(RESPONSE_TOPIC, buf); 45 | t_httpUpdate_return ret = ESPhttpUpdate.update(command); 46 | 47 | sprintf(buf, "update failed"); 48 | switch(ret) 49 | { 50 | case HTTP_UPDATE_FAILED: 51 | sprintf(buf, "HTTP_UPDATE_FAILD Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); 52 | break; 53 | 54 | case HTTP_UPDATE_NO_UPDATES: 55 | sprintf(buf, "HTTP_UPDATE_NO_UPDATES"); 56 | break; 57 | 58 | case HTTP_UPDATE_OK: 59 | sprintf(buf, "HTTP_UPDATE_OK"); 60 | break; 61 | } 62 | mqtt.publish(RESPONSE_TOPIC, buf); 63 | Serial.printf("%s\n", buf); 64 | } 65 | else 66 | { 67 | Serial.printf("unknown command: '%s'", command); 68 | } 69 | } 70 | } 71 | 72 | void mqtt_setup() 73 | { 74 | mqtt.setServer(current_config.mqtt_server, current_config.mqtt_port); 75 | mqtt.setCallback(callback); 76 | } 77 | 78 | void mqtt_publish_float(char *name, float value) 79 | { 80 | char buffer[32]; 81 | 82 | sprintf(buffer, "%0.2f", value); 83 | if(false) 84 | { 85 | if (!mqtt.publish(name, buffer)) 86 | { 87 | mqtt_fail = true; 88 | } 89 | } 90 | Serial.printf("Published %s : %s\n", name, buffer); 91 | } 92 | 93 | void mqtt_publish_int(char *name, uint32_t value) 94 | { 95 | char buffer[32]; 96 | 97 | if (value == 0x7FFFFFFF) 98 | { 99 | return; 100 | } 101 | sprintf(buffer, "%d", value); 102 | if (!mqtt.publish(name, buffer)) 103 | { 104 | mqtt_fail = true; 105 | } 106 | Serial.printf("Published %s : %s\n", name, buffer); 107 | } 108 | 109 | bool mqtt_loop() 110 | { 111 | uint32_t time = millis(); 112 | static int nextTime = 0; 113 | 114 | if (mqtt_fail) 115 | { 116 | mqtt_fail = false; 117 | mqtt.disconnect(); 118 | } 119 | 120 | MQTT_connect(); 121 | 122 | if (!mqtt.connected()) 123 | { 124 | return false; 125 | } 126 | 127 | mqtt.loop(); 128 | 129 | if (time >= nextTime) 130 | { 131 | bool do_publish = false; 132 | 133 | if ((time - mqtt_last_publish_time) > 10000) 134 | { 135 | do_publish = true; 136 | } 137 | 138 | if (do_publish) 139 | { 140 | mqtt_last_publish_time = time; 141 | 142 | mqtt_publish_int((char*)"feeds/integer/mc_proxy/bytes_transferred", bytes_transferred); 143 | mqtt_publish_int((char*)"feeds/integer/mc_proxy/rssi", WiFi.RSSI()); 144 | } 145 | nextTime = time + 1000; 146 | } 147 | 148 | return false; 149 | } 150 | 151 | void MQTT_connect() 152 | { 153 | int curTime = millis(); 154 | int8_t ret; 155 | 156 | if (WiFi.status() != WL_CONNECTED) 157 | { 158 | return; 159 | } 160 | 161 | if (mqtt.connected()) 162 | { 163 | return; 164 | } 165 | 166 | if ((mqtt_lastConnect != 0) && (curTime - mqtt_lastConnect < (1000 << mqtt_retries))) 167 | { 168 | return; 169 | } 170 | 171 | mqtt_lastConnect = curTime; 172 | 173 | Serial.print("MQTT: Connecting to MQTT... "); 174 | ret = mqtt.connect(current_config.mqtt_client, current_config.mqtt_user, current_config.mqtt_password); 175 | 176 | if (ret == 0) 177 | { 178 | mqtt_retries++; 179 | if (mqtt_retries > 8) 180 | { 181 | mqtt_retries = 8; 182 | } 183 | Serial.printf("MQTT: (%d) ", mqtt.state()); 184 | Serial.println("MQTT: Retrying MQTT connection"); 185 | mqtt.disconnect(); 186 | } 187 | else 188 | { 189 | Serial.println("MQTT Connected!"); 190 | mqtt.subscribe(COMMAND_TOPIC); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /WiFi.ino: -------------------------------------------------------------------------------- 1 | #include 2 | DNSServer dnsServer; 3 | 4 | bool connecting = false; 5 | bool wifi_captive = false; 6 | char wifi_error[64]; 7 | 8 | void wifi_setup() 9 | { 10 | Serial.printf("[WiFi] Connecting to '%s', password '%s'...\n", current_config.wifi_ssid, current_config.wifi_password); 11 | sprintf(wifi_error, ""); 12 | WiFi.begin(current_config.wifi_ssid, current_config.wifi_password); 13 | connecting = true; 14 | 15 | pinMode(2, OUTPUT); 16 | digitalWrite(2, LOW); 17 | } 18 | 19 | void wifi_off() 20 | { 21 | connecting = false; 22 | WiFi.disconnect(); 23 | WiFi.mode(WIFI_OFF); 24 | } 25 | 26 | bool wifi_loop(void) 27 | { 28 | int status = WiFi.status(); 29 | int curTime = millis(); 30 | static int nextTime = 0; 31 | static int stateCounter = 0; 32 | 33 | if(wifi_captive) 34 | { 35 | dnsServer.processNextRequest(); 36 | digitalWrite(2, ((millis() % 250) > 125) ? LOW : HIGH); 37 | return true; 38 | } 39 | 40 | if (nextTime > curTime) 41 | { 42 | return false; 43 | } 44 | 45 | /* standard refresh time */ 46 | nextTime = curTime + 500; 47 | 48 | 49 | /* when stuck at a state, disconnect */ 50 | if (++stateCounter > 20) 51 | { 52 | Serial.printf("[WiFi] Timeout connecting\n"); 53 | sprintf(wifi_error, "Timeout - incorrect password?"); 54 | wifi_off(); 55 | } 56 | 57 | if(strcmp(wifi_error, "")) 58 | { 59 | Serial.printf("[WiFi] Entering captive mode. Reason: '%s'\n", wifi_error); 60 | wifi_off(); 61 | WiFi.softAP(CONFIG_SOFTAPNAME); 62 | dnsServer.start(53, "*", WiFi.softAPIP()); 63 | Serial.printf("[WiFi] Local IP: %s\n", WiFi.softAPIP().toString().c_str()); 64 | 65 | wifi_captive = true; 66 | 67 | stateCounter = 0; 68 | return false; 69 | } 70 | 71 | 72 | switch (status) 73 | { 74 | case WL_CONNECTED: 75 | if (connecting) 76 | { 77 | connecting = false; 78 | Serial.print("[WiFi] Connected, IP address: "); 79 | Serial.println(WiFi.localIP()); 80 | stateCounter = 0; 81 | sprintf(wifi_error, ""); 82 | digitalWrite(2, HIGH); 83 | } 84 | else 85 | { 86 | static int last_rssi = -1; 87 | int rssi = WiFi.RSSI(); 88 | 89 | if (last_rssi != rssi) 90 | { 91 | float maxRssi = -40; 92 | float minRssi = -90; 93 | float strRatio = (rssi - minRssi) / (maxRssi - minRssi); 94 | float strength = MIN(1, MAX(0, strRatio)); 95 | float brightness = 0.08f; 96 | int r = brightness * 255.0f * (1.0f - strength); 97 | int g = brightness * 255.0f * strength; 98 | 99 | last_rssi = rssi; 100 | } 101 | 102 | /* happy with this state, reset counter */ 103 | stateCounter = 0; 104 | } 105 | break; 106 | 107 | case WL_CONNECTION_LOST: 108 | Serial.printf("[WiFi] Connection lost\n"); 109 | sprintf(wifi_error, "Network found, but connection lost"); 110 | wifi_off(); 111 | break; 112 | 113 | case WL_CONNECT_FAILED: 114 | Serial.printf("[WiFi] Connection failed\n"); 115 | sprintf(wifi_error, "Network found, but connection failed"); 116 | wifi_off(); 117 | break; 118 | 119 | case WL_NO_SSID_AVAIL: 120 | Serial.printf("[WiFi] No SSID with that name\n"); 121 | sprintf(wifi_error, "Network not found"); 122 | wifi_off(); 123 | break; 124 | 125 | case WL_SCAN_COMPLETED: 126 | Serial.printf("[WiFi] Scan completed\n"); 127 | wifi_off(); 128 | break; 129 | 130 | case WL_DISCONNECTED: 131 | if(!connecting) 132 | { 133 | Serial.printf("[WiFi] Disconnected\n"); 134 | wifi_off(); 135 | } 136 | break; 137 | 138 | case WL_IDLE_STATUS: 139 | if (!connecting) 140 | { 141 | connecting = true; 142 | Serial.printf("[WiFi] Idle, connect to '%s'\n", current_config.wifi_ssid); 143 | WiFi.mode(WIFI_STA); 144 | WiFi.begin(current_config.wifi_ssid, current_config.wifi_password); 145 | } 146 | else 147 | { 148 | Serial.printf("[WiFi] Idle, connecting...\n"); 149 | } 150 | break; 151 | 152 | case WL_NO_SHIELD: 153 | if (!connecting) 154 | { 155 | connecting = true; 156 | Serial.printf("[WiFi] Disabled (%d), connecting to '%s'\n", status, current_config.wifi_ssid); 157 | WiFi.mode(WIFI_STA); 158 | WiFi.begin(current_config.wifi_ssid, current_config.wifi_password); 159 | } 160 | break; 161 | 162 | default: 163 | Serial.printf("[WiFi] unknown (%d), disable\n", status); 164 | wifi_off(); 165 | break; 166 | } 167 | 168 | return false; 169 | } 170 | -------------------------------------------------------------------------------- /ProxyServer.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "lwip/inet.h" 3 | #include "lwip/ip4_addr.h" 4 | #include "lwip/dns.h" 5 | 6 | #include "ProxyServer.h" 7 | #include "Config.h" 8 | 9 | char packet_string[] = "MCPE;status;-1;1.0;0;0;0;(c) g3gg0.de;Creative;1;55555;"; 10 | uint8_t packet_magic[] = {0x00, 0xff, 0xff, 0x00, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, 0x56, 0x78}; 11 | 12 | void proxy_client_received(void *arg, AsyncUDPPacket& packet) 13 | { 14 | ProxyServer *srv = (ProxyServer *)arg; 15 | srv->ClientReceived(packet); 16 | } 17 | void proxy_server_received(void *arg, AsyncUDPPacket& packet) 18 | { 19 | ProxyServer *srv = (ProxyServer *)arg; 20 | srv->ServerReceived(packet); 21 | } 22 | 23 | ProxyServer::ProxyServer (char *server_name, char *server_address, int server_port, int local_port) 24 | { 25 | ServerName = strdup(server_name); 26 | ServerAddress = strdup(server_address); 27 | ServerPort = server_port; 28 | LocalPort = local_port; 29 | 30 | ClientPort = -1; 31 | proxy_out_port = local_port + 16384; 32 | 33 | Serial.printf("[Proxy] Set up '%s'\n", server_name); 34 | Serial.printf("[Proxy] Listening on %s:%d\n", WiFi.localIP().toString().c_str(), local_port); 35 | Serial.printf("[Proxy] Forwarding to %s:%d\n", server_address, server_port); 36 | 37 | if(!client_udp.listen(local_port)) 38 | { 39 | Serial.printf("[Proxy] Failed to bind to %s:%d\n", WiFi.localIP(), local_port); 40 | return; 41 | } 42 | if(!server_udp.listen(proxy_out_port)) 43 | { 44 | Serial.printf("[Proxy] Failed to bind to %s:%d\n", WiFi.localIP(), proxy_out_port); 45 | return; 46 | } 47 | 48 | proxy_server_addr.fromString(server_address); 49 | 50 | client_udp.onPacket(proxy_client_received, this); 51 | server_udp.onPacket(proxy_server_received, this); 52 | } 53 | 54 | void ProxyServer::Loop() 55 | { 56 | static long nextChange = 0; 57 | 58 | if(millis() > nextChange) 59 | { 60 | nextChange = millis() + 1000; 61 | } 62 | } 63 | 64 | int ProxyServer::AssembleString(t_server_string *server_str, uint8_t *buf) 65 | { 66 | uint8_t *str = (uint8_t *) server_str; 67 | uint16_t pos = 0; 68 | int field_num = 0; 69 | int field_pos = 0; 70 | 71 | while(field_num < STR_FIELD_COUNT) 72 | { 73 | if(str[field_num * STR_FIELD_SIZE + field_pos] == 0) 74 | { 75 | field_num++; 76 | field_pos = 0; 77 | if(field_num < STR_FIELD_COUNT) 78 | { 79 | buf[pos++] = ';'; 80 | } 81 | } 82 | else 83 | { 84 | buf[pos++] = str[field_num * STR_FIELD_SIZE + field_pos]; 85 | field_pos++; 86 | } 87 | } 88 | 89 | return pos; 90 | } 91 | 92 | t_server_string *ProxyServer::ParseString(uint8_t *string, int length) 93 | { 94 | uint8_t *str = (uint8_t *)malloc(sizeof(t_server_string)); 95 | int pos = 0; 96 | int field_num = 0; 97 | int field_pos = 0; 98 | 99 | memset(str, 0x00, sizeof(t_server_string)); 100 | 101 | while(pos < length && field_num < STR_FIELD_COUNT) 102 | { 103 | if(string[pos] == ';') 104 | { 105 | field_num++; 106 | field_pos = 0; 107 | if(field_num < STR_FIELD_COUNT) 108 | { 109 | str[field_num * STR_FIELD_SIZE + field_pos] = 0; 110 | } 111 | } 112 | else 113 | { 114 | if(field_pos < STR_FIELD_SIZE - 1) 115 | { 116 | str[field_num * STR_FIELD_SIZE + field_pos] = string[pos]; 117 | field_pos++; 118 | str[field_num * STR_FIELD_SIZE + field_pos] = 0; 119 | } 120 | } 121 | pos++; 122 | } 123 | 124 | return (t_server_string *) str; 125 | } 126 | 127 | void ProxyServer::SendPacket(IPAddress dst, uint16_t port) 128 | { 129 | /* assume a certain server string length */ 130 | int string_len = 1024; 131 | uint32_t pong_len = sizeof(t_pong_packet) - 1 + string_len; 132 | t_pong_packet *pong_packet = (t_pong_packet *)malloc(pong_len); 133 | pong_packet->server_time = millis(); 134 | pong_packet->guid = 0; 135 | memcpy(&pong_packet->magic, packet_magic, sizeof(packet_magic)); 136 | 137 | t_server_string *str = ParseString((uint8_t *)packet_string, strlen(packet_string)); 138 | 139 | strcpy(str->port_ipv6, ""); 140 | if(millis() < last_server_packet + 10000) 141 | { 142 | if(last_server_packet == 0) 143 | { 144 | sprintf(str->motd, "§oStatus: §6'%s:%d' §kcheck", ServerAddress, ServerPort); 145 | } 146 | else 147 | { 148 | sprintf(str->motd, "§oStatus: §2'%s:%d' online", ServerAddress, ServerPort); 149 | } 150 | } 151 | else 152 | { 153 | sprintf(str->motd, "§oStatus: §c'%s:%d' offline", ServerAddress, ServerPort); 154 | } 155 | char status[32]; 156 | 157 | sprintf(status, ", RSSI: %i", WiFi.RSSI()); 158 | strcat(str->sub_motd, status); 159 | 160 | /* now calc real server string length */ 161 | string_len = AssembleString(str, &pong_packet->server_string); 162 | pong_packet->server_len = htons(string_len); 163 | 164 | int udp_length = 1 + sizeof(t_pong_packet) - 1 + string_len; 165 | uint8_t *udp_buffer = (uint8_t *)malloc(udp_length); 166 | 167 | udp_buffer[0] = PROT_UNCONN_PONG; 168 | memcpy(&udp_buffer[1], pong_packet, udp_length - 1); 169 | 170 | server_udp.writeTo((uint8_t*)udp_buffer, udp_length, dst, port); 171 | 172 | free(str); 173 | free(udp_buffer); 174 | free(pong_packet); 175 | } 176 | 177 | 178 | void ProxyServer::ClientReceived(AsyncUDPPacket& packet) 179 | { 180 | uint8_t *payload = packet.data(); 181 | size_t length = packet.length(); 182 | 183 | ClientAddress = packet.remoteIP(); 184 | ClientPort = packet.remotePort(); 185 | 186 | if(payload[0] == PROT_UNCONN_PING) 187 | { 188 | if(current_config.verbose & 1) 189 | { 190 | Serial.printf("[Proxy] Received ping from client\n"); 191 | } 192 | 193 | /* always also respond with the generic info entry */ 194 | SendPacket(ClientAddress, ClientPort); 195 | } 196 | else if(payload[0] == PROT_UNCONN_PONG) 197 | { 198 | if(current_config.verbose & 1) 199 | { 200 | Serial.printf("[Proxy] Received pong from client\n"); 201 | } 202 | } 203 | else 204 | { 205 | if(current_config.verbose & 1) 206 | { 207 | Serial.printf("[Proxy] Received packet from client\n"); 208 | } 209 | } 210 | 211 | server_udp.writeTo(payload, length, proxy_server_addr, ServerPort); 212 | bytes_transferred += length; 213 | } 214 | 215 | void ProxyServer::ServerReceived(AsyncUDPPacket& packet) 216 | { 217 | uint8_t *payload = packet.data(); 218 | size_t length = packet.length(); 219 | 220 | last_server_packet = millis(); 221 | 222 | if(payload[0] == PROT_UNCONN_PING) 223 | { 224 | if(current_config.verbose & 1) 225 | { 226 | Serial.printf("[Proxy] Received ping from server - "); 227 | } 228 | } 229 | else if(payload[0] == PROT_UNCONN_PONG) 230 | { 231 | if(current_config.verbose & 1) 232 | { 233 | Serial.printf("[Proxy] Received pong from server\n"); 234 | } 235 | t_pong_packet *pkt = (t_pong_packet *)malloc(length - 1 + 1024); 236 | memcpy(pkt, &payload[1], length - 1); 237 | t_server_string *str = ParseString(&pkt->server_string, ntohs(pkt->server_len)); 238 | if(current_config.verbose & 1) 239 | { 240 | Serial.printf(" edition %s, game %s, motd %s, sub_motd %s, id %s, port4 %s, port6 %s\n", str->edition, str->game_type, str->motd, str->sub_motd, str->server_id, str->port_ipv4, str->port_ipv6); 241 | } 242 | 243 | sprintf(str->port_ipv4, "%d", LocalPort); 244 | strcpy(str->port_ipv6, ""); 245 | strcpy(str->motd, str->sub_motd); 246 | strcat(str->motd, "§o§3 via proxy"); 247 | strcpy(str->sub_motd, ServerName); 248 | 249 | int newlen = AssembleString(str, &pkt->server_string); 250 | pkt->server_len = htons(newlen); 251 | length = 1 + sizeof(t_pong_packet) - 1 + newlen; 252 | 253 | uint8_t *new_buffer = (uint8_t *)malloc(length); 254 | 255 | new_buffer[0] = PROT_UNCONN_PONG; 256 | memcpy(&new_buffer[1], pkt, length - 1); 257 | 258 | client_udp.writeTo(new_buffer, length, ClientAddress, ClientPort); 259 | 260 | free(str); 261 | free(pkt); 262 | free(new_buffer); 263 | return; 264 | } 265 | else 266 | { 267 | if(current_config.verbose & 1) 268 | { 269 | Serial.printf("[Proxy] Received packet from server\n"); 270 | } 271 | } 272 | 273 | client_udp.writeTo(payload, length, ClientAddress, ClientPort); 274 | bytes_transferred += length; 275 | } 276 | -------------------------------------------------------------------------------- /Webserver.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include "Config.h" 5 | 6 | WebServer webserver(80); 7 | extern char wifi_error[]; 8 | extern bool wifi_captive; 9 | 10 | int www_wifi_scanned = -1; 11 | 12 | 13 | void www_setup() 14 | { 15 | webserver.on("/", handle_root); 16 | webserver.on("/index.html", handle_index); 17 | webserver.on("/set_parm", handle_set_parm); 18 | webserver.on("/ota", handle_ota); 19 | webserver.on("/reset", handle_reset); 20 | webserver.on("/test", handle_test); 21 | webserver.onNotFound(handle_404); 22 | 23 | webserver.begin(); 24 | Serial.println("[WWW] Webserver started"); 25 | 26 | if (!MDNS.begin(current_config.hostname)) 27 | { 28 | Serial.println("Error setting up MDNS responder!"); 29 | } 30 | MDNS.addService("http", "tcp", 80); 31 | } 32 | 33 | bool www_loop() 34 | { 35 | webserver.handleClient(); 36 | return false; 37 | } 38 | 39 | void handle_root() 40 | { 41 | webserver.send(200, "text/html", SendHTML()); 42 | } 43 | 44 | void handle_404() 45 | { 46 | if(wifi_captive) 47 | { 48 | char buf[128]; 49 | sprintf(buf, "HTTP/1.1 302 Found\r\nContent-Type: text/html\r\nContent-length: 0\r\nLocation: http://%s/\r\n\r\n", WiFi.softAPIP().toString().c_str()); 50 | webserver.sendContent(buf); 51 | Serial.printf("[WWW] 302 - http://%s%s/ -> http://%s/\n", webserver.hostHeader().c_str(), webserver.uri().c_str(), WiFi.softAPIP().toString().c_str()); 52 | } 53 | else 54 | { 55 | webserver.send(404, "text/plain", "So empty here"); 56 | Serial.printf("[WWW] 404 - http://%s%s/\n", webserver.hostHeader().c_str(), webserver.uri().c_str()); 57 | } 58 | } 59 | 60 | void handle_index() 61 | { 62 | webserver.send(200, "text/html", SendHTML()); 63 | } 64 | 65 | void handle_ota() 66 | { 67 | ota_setup(); 68 | webserver.send(200, "text/html", SendHTML()); 69 | } 70 | 71 | void handle_reset() 72 | { 73 | webserver.send(200, "text/html", SendHTML()); 74 | ESP.restart(); 75 | } 76 | 77 | void handle_test() 78 | { 79 | Serial.printf("Test\n"); 80 | webserver.send(200, "text/html", SendHTML()); 81 | } 82 | 83 | void handle_set_parm() 84 | { 85 | current_config.mqtt_publish = MAX(0, MIN(65535, webserver.arg("mqtt_publish").toInt())); 86 | current_config.local_port = MAX(0, MIN(65535, webserver.arg("local_port").toInt())); 87 | 88 | current_config.verbose = 0; 89 | current_config.verbose |= (webserver.arg("verbose_c0") != "") ? 1 : 0; 90 | current_config.verbose |= (webserver.arg("verbose_c1") != "") ? 2 : 0; 91 | current_config.verbose |= (webserver.arg("verbose_c2") != "") ? 4 : 0; 92 | current_config.verbose |= (webserver.arg("verbose_c3") != "") ? 8 : 0; 93 | 94 | strncpy(current_config.hostname, webserver.arg("hostname").c_str(), sizeof(current_config.hostname)); 95 | strncpy(current_config.wifi_ssid, webserver.arg("wifi_ssid").c_str(), sizeof(current_config.wifi_ssid)); 96 | strncpy(current_config.wifi_password, webserver.arg("wifi_password").c_str(), sizeof(current_config.wifi_password)); 97 | 98 | for(int num = 0; num < CONFIG_SERVER_COUNT; num++) 99 | { 100 | char tmp[32]; 101 | sprintf(tmp, "remote_name_%d", num); 102 | strncpy(current_config.remote_name[num], webserver.arg(tmp).c_str(), sizeof(current_config.remote_name[num])); 103 | sprintf(tmp, "remote_server_%d", num); 104 | strncpy(current_config.remote_server[num], webserver.arg(tmp).c_str(), sizeof(current_config.remote_server[num])); 105 | sprintf(tmp, "remote_port_%d", num); 106 | current_config.remote_port[num] = MAX(0, MIN(65535, webserver.arg(tmp).toInt())); 107 | } 108 | 109 | cfg_save(); 110 | 111 | if (current_config.verbose) 112 | { 113 | Serial.printf("Config:\n"); 114 | Serial.printf(" mqtt_publish: %d %%\n", current_config.mqtt_publish); 115 | Serial.printf(" verbose: %d\n", current_config.verbose); 116 | } 117 | 118 | if(webserver.arg("http_update") != "") 119 | { 120 | webserver.send(200, "text/text", "Updating from " + webserver.arg("http_update")); 121 | 122 | Serial.println("updating from: " + webserver.arg("http_update")); 123 | t_httpUpdate_return ret = ESPhttpUpdate.update(webserver.arg("http_update")); 124 | 125 | switch(ret) 126 | { 127 | case HTTP_UPDATE_FAILED: 128 | Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); 129 | break; 130 | 131 | case HTTP_UPDATE_NO_UPDATES: 132 | Serial.println("HTTP_UPDATE_NO_UPDATES"); 133 | break; 134 | 135 | case HTTP_UPDATE_OK: 136 | Serial.println("HTTP_UPDATE_OK"); 137 | break; 138 | } 139 | return; 140 | } 141 | 142 | if(webserver.arg("reboot") == "true") 143 | { 144 | webserver.send(200, "text/html", "

Saved. Rebooting...

(will refresh page in 5 seconds)"); 145 | delay(500); 146 | ESP.restart(); 147 | return; 148 | } 149 | 150 | if(webserver.arg("scan") == "true") 151 | { 152 | www_wifi_scanned = WiFi.scanNetworks(); 153 | } 154 | webserver.send(200, "text/html", SendHTML()); 155 | www_wifi_scanned = -1; 156 | } 157 | 158 | String SendHTML() 159 | { 160 | char buf[1024]; 161 | 162 | String ptr = " \n"; 163 | ptr += "\n"; 164 | 165 | sprintf(buf, "MinecraftProxy Control\n"); 166 | 167 | ptr += buf; 168 | ptr += "\n"; 192 | ptr += "\n"; 193 | ptr += "\n"; 194 | 195 | sprintf(buf, "

MinecraftProxy

\n"); 196 | 197 | if(strlen(wifi_error) != 0) 198 | { 199 | sprintf(buf, "

WiFi Error: %s

\n", wifi_error); 200 | } 201 | 202 | ptr += buf; 203 | if (!ota_enabled()) 204 | { 205 | ptr += "[Enable OTA] "; 206 | } 207 | ptr += "

\n"; 208 | 209 | ptr += "
\n"; 210 | ptr += ""; 211 | 212 | #define ADD_CONFIG(name, value, fmt, desc) \ 213 | do \ 214 | { \ 215 | ptr += ""; \ 216 | sprintf(buf, "\n", value); \ 217 | ptr += buf; \ 218 | } while (0) 219 | 220 | #define ADD_CONFIG_CHECK4(name, value, fmt, desc, text0, text1, text2, text3) \ 221 | do \ 222 | { \ 223 | ptr += "\n"); \ 241 | ptr += buf; \ 242 | } while (0) 243 | 244 | #define ADD_CONFIG_COLOR(name, value, fmt, desc) \ 245 | do \ 246 | { \ 247 | ptr += ""; \ 248 | sprintf(buf, "\n", value); \ 249 | ptr += buf; \ 250 | } while (0) 251 | 252 | ADD_CONFIG("hostname", current_config.hostname, "%s", "Hostname"); 253 | 254 | ADD_CONFIG("wifi_ssid", current_config.wifi_ssid, "%s", "WiFi SSID"); 255 | ADD_CONFIG("wifi_password", current_config.wifi_password, "%s", "WiFi Password"); 256 | 257 | 258 | ptr += ""; 289 | 290 | 291 | ADD_CONFIG("local_port", current_config.local_port, "%d", "Local UDP port"); 292 | for(int num = 0; num < CONFIG_SERVER_COUNT; num++) 293 | { 294 | char tmp[64]; 295 | sprintf(tmp, "", num, num+1); 296 | ptr += tmp; 297 | sprintf(buf, "\n", num, num, current_config.remote_name[num]); 298 | ptr += buf; 299 | 300 | sprintf(tmp, "", num, num+1); 301 | ptr += tmp; 302 | sprintf(buf, "\n", num, num, current_config.remote_server[num]); 303 | ptr += buf; 304 | 305 | sprintf(tmp, "", num, num+1); 306 | ptr += tmp; 307 | sprintf(buf, "\n", num, num, current_config.remote_port[num]); 308 | ptr += buf; 309 | } 310 | 311 | //ADD_CONFIG_CHECK4("verbose", current_config.verbose, "%d", "Verbosity", "Serial", "_", "_", "_"); 312 | //ADD_CONFIG_CHECK4("mqtt_publish", current_config.mqtt_publish, "%d", "MQTT publishes", "RSSI", "Debug", "_", "_"); 313 | 314 | 315 | ADD_CONFIG("http_update", "", "%s", "Update URL"); 316 | 317 | ptr += "
" desc ":
"; \ 224 | sprintf(buf, "\n", (value&1)?"checked":""); \ 225 | ptr += buf; \ 226 | sprintf(buf, "\n"); \ 227 | ptr += buf; \ 228 | sprintf(buf, "\n", (value&2)?"checked":""); \ 229 | ptr += buf; \ 230 | sprintf(buf, "\n"); \ 231 | ptr += buf; \ 232 | sprintf(buf, "\n", (value&4)?"checked":""); \ 233 | ptr += buf; \ 234 | sprintf(buf, "\n"); \ 235 | ptr += buf; \ 236 | sprintf(buf, "\n", (value&8)?"checked":""); \ 237 | ptr += buf; \ 238 | sprintf(buf, "\n"); \ 239 | ptr += buf; \ 240 | sprintf(buf, "
WiFi networks:"; 259 | 260 | if (www_wifi_scanned == -1) 261 | { 262 | ptr += ""; 263 | } 264 | else if (www_wifi_scanned == 0) 265 | { 266 | ptr += "No networks found, "; 267 | } 268 | else 269 | { 270 | ptr += ""; 271 | ptr += ""; 272 | for (int i = 0; i < www_wifi_scanned; ++i) 273 | { 274 | if(WiFi.SSID(i) != "") 275 | { 276 | ptr += ""; 283 | } 284 | } 285 | ptr += "
"; 279 | ptr += WiFi.SSID(i); 280 | ptr += ""; 281 | ptr += WiFi.RSSI(i); 282 | ptr += " dBm
"; 286 | } 287 | 288 | ptr += "
\n"; 318 | 319 | ptr += "\n"; 320 | ptr += "\n"; 321 | return ptr; 322 | } 323 | --------------------------------------------------------------------------------