1use std::fs::{self, remove_dir_all, File, OpenOptions};
2use std::io::{self, BufRead, Seek, SeekFrom};
3use std::path::Path;
4pub(crate) mod utils;
5use crate::config::{Difficulty, Gamemode};
6use crate::{config, consts, gracefully_exit};
7use cactus_world::level::{create_nbt, LevelDat, VersionInfo};
8use colored::Colorize;
9use log::{error, info, warn};
10use serde::{Deserialize, Serialize};
11use std::io::Read;
12use std::io::Write;
13
14pub fn init() -> std::io::Result<()> {
16 eula()?;
17 create_server_properties()
18}
19
20fn eula() -> io::Result<()> {
22 let path = Path::new(consts::file_paths::EULA);
23 if !path.exists() {
24 create_eula()?;
25 let content = "Please agree to the 'eula.txt' and start the server again.";
26 warn!("{}", content.bright_red().bold());
27 gracefully_exit(crate::ExitCode::Failure);
28 } else {
29 let is_agreed_eula = check_eula()?;
30 if !is_agreed_eula {
31 let error_content = "Cannot start the server, please agree to the 'eula.txt'";
32 error!("{}", error_content.bright_red().bold().blink());
33 gracefully_exit(crate::ExitCode::Failure);
34 }
35 Ok(())
36 }
37}
38
39fn create_server_properties() -> io::Result<()> {
41 let path = Path::new(consts::file_paths::PROPERTIES);
42 let content = consts::file_contents::server_properties();
43
44 utils::create_file(path, Some(&content))
45}
46
47fn create_eula() -> io::Result<()> {
49 let path = Path::new(consts::file_paths::EULA);
50 let content = consts::file_contents::eula();
51
52 utils::create_file(path, Some(&content))
53}
54
55fn check_eula() -> io::Result<bool> {
57 let file = File::open(Path::new(consts::file_paths::EULA))?;
58 let reader = io::BufReader::new(file);
59
60 for line in reader.lines() {
61 let line = line?;
62 if line.starts_with("eula=") {
63 let eula_value = line.split('=').nth(1).unwrap_or("").to_lowercase();
64 return Ok(eula_value == "true");
65 }
66 }
67
68 Ok(false)
69}
70
71pub fn create_other_files() {
72 match utils::create_file(Path::new(consts::file_paths::BANNED_IP), None) {
73 Ok(_) => (),
74 Err(e) => error!(
75 "Failed to create the file {} as error:{}",
76 consts::file_paths::BANNED_IP,
77 e
78 ),
79 }
80 match utils::create_file(Path::new(consts::file_paths::BANNED_PLAYERS), None) {
81 Ok(_) => (),
82 Err(e) => error!(
83 "Failed to create the file {} as error:{}",
84 consts::file_paths::BANNED_PLAYERS,
85 e
86 ),
87 }
88 match utils::create_file(Path::new(consts::file_paths::OPERATORS), None) {
89 Ok(_) => (),
90 Err(e) => error!(
91 "Failed to create the file {} as error:{}",
92 consts::file_paths::OPERATORS,
93 e
94 ),
95 }
96 match utils::create_file(Path::new(consts::file_paths::SESSION), None) {
97 Ok(_) => (),
98 Err(e) => error!(
99 "Failed to create the file {} as error:{}",
100 consts::file_paths::SESSION,
101 e
102 ),
103 }
104 match utils::create_file(Path::new(consts::file_paths::USERCACHE), None) {
105 Ok(_) => (),
106 Err(e) => error!(
107 "Failed to create the file {} as error:{}",
108 consts::file_paths::USERCACHE,
109 e
110 ),
111 }
112 match utils::create_file(Path::new(consts::file_paths::WHITELIST), None) {
113 Ok(_) => (),
114 Err(e) => error!(
115 "Failed to create the file {} as error:{}",
116 consts::file_paths::WHITELIST,
117 e
118 ),
119 }
120 match create_level_file() {
121 Ok(_) => (),
122 Err(e) => error!(
123 "Failed to create the file at {} as error {}",
124 consts::file_paths::LEVEL,
125 e
126 ),
127 }
128}
129pub fn create_dirs() {
130 match utils::create_dir(Path::new(consts::directory_paths::LOGS)) {
131 Ok(_) => (),
132 Err(e) => info!(
133 "Failed to create dir{} as error: {}",
134 consts::directory_paths::LOGS,
135 e
136 ),
137 }
138
139 match utils::create_dir(Path::new(consts::directory_paths::WORLDS_DIRECTORY)) {
140 Ok(_) => (),
141 Err(e) => info!(
142 "Failed to create dir{} as error: {}",
143 consts::directory_paths::WORLDS_DIRECTORY,
144 e
145 ),
146 }
147
148 match utils::create_dir(Path::new(consts::directory_paths::OVERWORLD)) {
149 Ok(_) => (),
150 Err(e) => info!(
151 "Failed to create dir{} as error: {}",
152 consts::directory_paths::OVERWORLD,
153 e
154 ),
155 }
156
157 match utils::create_dir(Path::new(consts::directory_paths::THE_END)) {
158 Ok(_) => (),
159 Err(e) => info!(
160 "Failed to create dir{} as error: {}",
161 consts::directory_paths::THE_END,
162 e
163 ),
164 }
165
166 match utils::create_dir(Path::new(consts::directory_paths::NETHER)) {
167 Ok(_) => (),
168 Err(e) => info!(
169 "Failed to create dir{} as error: {}",
170 consts::directory_paths::NETHER,
171 e
172 ),
173 }
174}
175#[derive(Serialize, Deserialize)]
176struct Player {
177 uuid: String,
178 name: String,
179 level: u8,
180 bypassesPlayerLimit: bool,
181}
182
183pub fn write_ops_json(
184 filename: &str,
185 uuid: &str,
186 name: &str,
187 level: u8,
188 bypasses_player_limit: bool,
189) -> std::io::Result<()> {
190 let mut file = OpenOptions::new()
191 .read(true)
192 .write(true)
193 .create(true)
194 .open(filename)?;
195
196 let mut content = String::new();
197 file.read_to_string(&mut content)?;
198
199 if content.starts_with('\u{feff}') {
200 content = content.trim_start_matches('\u{feff}').to_string();
201 }
202
203 let mut players: Vec<Player> = if content.trim().is_empty() {
204 Vec::new()
205 } else {
206 serde_json::from_str(&content).unwrap_or_else(|_| Vec::new())
207 };
208
209 if !players.iter().any(|p| p.uuid == uuid) {
210 players.push(Player {
211 uuid: uuid.to_string(),
212 name: name.to_string(),
213 level,
214 bypassesPlayerLimit: bypasses_player_limit,
215 });
216 info!("Made {} a server operator", name.to_string())
217 } else {
218 warn!("Nothing changed. The player already is an operator")
219 }
220
221 file.set_len(0)?;
223 file.seek(SeekFrom::Start(0))?;
224 file.write_all(serde_json::to_string_pretty(&players)?.as_bytes())?;
225
226 Ok(())
227}
228pub fn clean_files() -> Result<(), std::io::Error> {
233 fn remove_file(file_path: &str) -> Result<(), std::io::Error> {
235 match fs::remove_file(file_path) {
236 Ok(_) => {
237 info!("File deleted: {}", file_path);
238 Ok(())
239 }
240 Err(e) => {
241 info!("Error when deleting file {}: {}", file_path, e);
242 Err(e)
243 }
244 }
245 }
246
247 fn remove_dir(dir_path: &str) -> Result<(), std::io::Error> {
249 match fs::remove_dir(dir_path) {
250 Ok(_) => {
251 info!("Directory deleted: {}", dir_path);
252 Ok(())
253 }
254 Err(e) => {
255 info!("Error when deleting directory {}: {}", dir_path, e);
256 Err(e)
257 }
258 }
259 }
260
261 let files = [
263 consts::file_paths::EULA,
264 consts::file_paths::PROPERTIES,
265 consts::file_paths::BANNED_IP,
266 consts::file_paths::BANNED_PLAYERS,
267 consts::file_paths::OPERATORS,
268 consts::file_paths::SESSION,
269 consts::file_paths::USERCACHE,
270 consts::file_paths::WHITELIST,
271 consts::file_paths::LEVEL,
272 ];
273
274 for file in &files {
276 remove_file(file)?;
277 }
278
279 let directories = [
281 consts::directory_paths::LOGS,
282 consts::directory_paths::NETHER,
283 consts::directory_paths::OVERWORLD,
284 consts::directory_paths::THE_END,
285 consts::directory_paths::WORLDS_DIRECTORY,
286 ];
287
288 for dir in &directories {
290 remove_dir_all(dir)?;
291 }
292 let info = "[Info]".green();
293 println!(
294 "{} Files cleaned successfully before starting the server.",
295 info
296 );
297 gracefully_exit(crate::ExitCode::Success);
298}
299fn create_level_file() -> Result<(), std::io::Error> {
300 if Path::new(consts::file_paths::LEVEL).exists() {
301 info!("level.dat file already exist, not altering it");
302 return Ok(());
303 }
304 let server_version = VersionInfo {
305 id: 4440,
306 snapshot: false,
307 series: "main".into(),
308 name: consts::minecraft::VERSION.into(),
309 };
310 let data = LevelDat {
311 version: server_version,
312 difficulty: match config::Settings::new().difficulty {
313 Difficulty::Easy => 0,
314 Difficulty::Normal => 1,
315 Difficulty::Hard => 2,
316 },
317 game_type: match config::Settings::new().gamemode {
318 Gamemode::Survival => 0,
319 Gamemode::Creative => 1,
320 Gamemode::Spectator => 3,
321 Gamemode::Adventure => 2,
322 },
323
324 hardcore: config::Settings::new().hardcore,
325 level_name: match config::Settings::new().level_name {
326 Some(words) => words,
327 _ => "Overworld".into(),
328 },
329
330 ..Default::default()
331 };
332
333 let result = create_nbt(&data, consts::file_paths::LEVEL);
334 if result.is_ok() {
335 info!("File created at {}", consts::file_paths::LEVEL)
336 }
337 result
338}