Skip to content

Commit 257dc55

Browse files
added server icon support
1 parent a47238f commit 257dc55

File tree

10 files changed

+112
-11
lines changed

10 files changed

+112
-11
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/target
22
/Cargo.lock
33
/out
4-
/.vscode/settings.json
4+
/.vscode/settings.json
5+
/server_icon.png

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ reqwest = "0.12.12"
2121
num-bigint = "0.4.6"
2222
tracing = "0.1.41"
2323
tracing-subscriber = "0.3.19"
24+
base64_light = "0.1.5"

config.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"players_online": 0,
66
"proto_ver": 0,
77
"server_ver": "1.21.4",
8-
"motd": "ГООООЛ",
9-
"code_life_time": 300
8+
"motd": "§6mc-oauth.andcool.ru",
9+
"code_life_time": 300,
10+
"icon": "server_icon.png"
1011
}

src/config/mod.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
pub mod server_icon;
12
mod types;
23

34
use anyhow::Result;
45
use std::sync::OnceLock;
56
use tokio::fs;
7+
use tracing::warn;
68

79
static CONFIG: OnceLock<types::Config> = OnceLock::new();
810

@@ -11,9 +13,15 @@ pub async fn load(path: &str) -> Result<()> {
1113
let file = fs::read_to_string(path)
1214
.await
1315
.expect("Couldn't load config file");
14-
CONFIG
15-
.set(serde_json::from_str(&file).expect("Couldn't parse config json"))
16-
.expect("Couldn't load config");
16+
17+
let mut config: types::Config =
18+
serde_json::from_str(&file).expect("Couldn't parse config json");
19+
match server_icon::load(&config.icon).await {
20+
Ok(base64) => config.image = Some(format!("data:image/png;base64,{}", base64)),
21+
Err(e) => warn!("Error loading server icon: {}", e)
22+
}
23+
24+
CONFIG.set(config).expect("Couldn't load config");
1725
Ok(())
1826
}
1927

src/config/server_icon.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use base64_light::base64_encode_bytes;
2+
use tokio::fs;
3+
4+
pub async fn load(file_path: &str) -> anyhow::Result<String> {
5+
let file_content = fs::read(file_path).await?;
6+
let encoded = base64_encode_bytes(&file_content);
7+
Ok(encoded)
8+
}

src/config/types.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,34 @@ use serde::{Deserialize, Serialize};
22

33
#[derive(Serialize, Deserialize, Debug)]
44
pub struct Config {
5+
/// Minecraft server port (Minecraft's default port is `25565`)
56
pub server_port: u16,
7+
8+
/// API port
69
pub api_port: u16,
10+
11+
/// Display count of max server players
712
pub players_max: usize,
13+
14+
/// Display count of max server players
815
pub players_online: usize,
16+
17+
/// Server protocol version (0 for auto)
918
pub proto_ver: usize,
19+
20+
/// Server version string (e.g. 1.20.5)
1021
pub server_ver: String,
22+
23+
/// Server description (you can use a `§` codes)
1124
pub motd: String,
25+
26+
/// Assigned 6-digit code life time in seconds
1227
pub code_life_time: u64,
28+
29+
/// Path to the server icon
30+
pub icon: String,
31+
32+
#[serde(skip)]
33+
/// Base 64 encoded server icon
34+
pub image: Option<String>,
1335
}

src/format/mod.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use serde_json::{json, Value};
2+
use std::collections::HashMap;
3+
4+
#[allow(dead_code)]
5+
/// JSON text format parser (unused)
6+
pub fn format_motd(motd: &str) -> Value {
7+
let color_map = HashMap::from([
8+
('0', "black"), ('1', "dark_blue"), ('2', "dark_green"), ('3', "dark_aqua"),
9+
('4', "dark_red"), ('5', "dark_purple"), ('6', "gold"), ('7', "gray"),
10+
('8', "dark_gray"), ('9', "blue"), ('a', "green"), ('b', "aqua"),
11+
('c', "red"), ('d', "light_purple"), ('e', "yellow"), ('f', "white")
12+
]);
13+
let style_map = HashMap::from([
14+
('k', "obfuscated"), ('l', "bold"), ('m', "strikethrough"),
15+
('n', "underlined"), ('o', "italic"), ('r', "reset")
16+
]);
17+
18+
let mut json_array = Vec::new();
19+
let mut current_text = String::new();
20+
let mut current_json = json!({});
21+
22+
let chars: Vec<char> = motd.chars().collect();
23+
let mut i = 0;
24+
25+
while i < chars.len() {
26+
if chars[i] == '§' && i + 1 < chars.len() {
27+
if !current_text.is_empty() {
28+
current_json["text"] = Value::String(current_text.clone());
29+
json_array.push(current_json);
30+
current_text.clear();
31+
current_json = json!({});
32+
}
33+
34+
let format_char = chars[i + 1];
35+
if let Some(color) = color_map.get(&format_char) {
36+
current_json["color"] = Value::String(color.to_string());
37+
} else if let Some(style) = style_map.get(&format_char) {
38+
current_json[style] = Value::Bool(true);
39+
}
40+
41+
i += 2;
42+
} else {
43+
current_text.push(chars[i]);
44+
i += 1;
45+
}
46+
}
47+
48+
if !current_text.is_empty() {
49+
current_json["text"] = Value::String(current_text);
50+
json_array.push(current_json);
51+
}
52+
53+
Value::Array(json_array)
54+
}

src/handlers/client_handler.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{sync::Arc, time::Duration};
33
use anyhow::Result;
44
use bytes::{BufMut, BytesMut};
55
use tokio::net::TcpStream;
6-
use tracing::info;
6+
use tracing::{debug, info};
77

88
use crate::{
99
byte_buf_utils::read_varint,
@@ -32,7 +32,7 @@ pub async fn handle(mut stream: TcpStream, keys: Arc<rsa::RsaPrivateKey>) -> Res
3232

3333
while packet_available(&mut buffer) {
3434
let packet_id = read_varint(&mut buffer)?;
35-
info!("Received packet: {}", packet_id);
35+
debug!("Received packet: {}", packet_id);
3636

3737
match packet_id {
3838
0x00 => match session.next_state {
@@ -58,7 +58,7 @@ pub async fn handle(mut stream: TcpStream, keys: Arc<rsa::RsaPrivateKey>) -> Res
5858
disconnect::send(
5959
&mut stream,
6060
session,
61-
"Failed to login: Invalid session (Try restarting your game and the launcher)".to_string()
61+
"§cFailed to login: Invalid session (Try restarting your game and the launcher)".to_string()
6262
).await?;
6363
break;
6464
}
@@ -78,7 +78,7 @@ pub async fn handle(mut stream: TcpStream, keys: Arc<rsa::RsaPrivateKey>) -> Res
7878
disconnect::send(
7979
&mut stream,
8080
session,
81-
format!("Hello, {}! Your code is: {}", player_data.name, code),
81+
format!("Hello, §6{}§r! Your code is: §a{}", player_data.name, code),
8282
)
8383
.await?;
8484

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod byte_buf_utils;
33
mod client_sessions;
44
mod config;
55
mod encryption;
6+
mod format;
67
mod generators;
78
mod handlers;
89
mod map;

src/responses/status.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@ pub async fn send(stream: &mut TcpStream, session: &mut Session) -> Result<()> {
1717
config.proto_ver
1818
};
1919

20+
let icon = match &config.image {
21+
Some(img) => img,
22+
None => &"".to_string()
23+
};
24+
2025
let data = StatusData {
2126
version_name: config.server_ver.clone(),
2227
version_protocol: proto_ver,
2328
players_max: config.players_max,
2429
players_online: config.players_online,
2530
description: json!({"text": config.motd.clone()}),
26-
favicon: "".to_string(),
31+
favicon: icon.to_string(),
2732
enforces_secure_chat: false,
2833
};
2934

0 commit comments

Comments
 (0)