├── .github └── workflows │ ├── release.yml │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── config.toml ├── crypt ├── Cargo.toml └── src │ ├── lib.rs │ └── rc4.rs └── server ├── Cargo.toml └── src ├── main.rs ├── prelude.rs ├── server ├── login │ ├── component.rs │ ├── conn.rs │ ├── entity │ │ ├── game.rs │ │ ├── mod.rs │ │ ├── room.rs │ │ ├── session.rs │ │ └── timer.rs │ ├── handler │ │ ├── after_login.rs │ │ ├── before_login.rs │ │ ├── mod.rs │ │ ├── on_game.rs │ │ ├── on_lobby.rs │ │ ├── on_room.rs │ │ └── packet.rs │ ├── mod.rs │ └── timer.rs ├── mod.rs └── status │ └── mod.rs └── util ├── config.rs ├── db.rs ├── hash.rs ├── mod.rs ├── object.rs ├── preader.rs └── pwriter.rs /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | 6 | name: Upload Release Asset 7 | 8 | env: 9 | NAME: RustyDO 10 | 11 | jobs: 12 | create_release: 13 | permissions: write-all 14 | name: Create Release 15 | runs-on: ubuntu-latest 16 | outputs: 17 | upload_url: ${{ steps.create_release.outputs.upload_url }} 18 | steps: 19 | - name: Get Version from Tag 20 | id: tag_name 21 | run: | 22 | echo ::set-output name=current_version::${GITHUB_REF#refs/tags/v} 23 | shell: bash 24 | 25 | - name: Checkout 26 | uses: actions/checkout@v2 27 | 28 | - name: Get Changelog 29 | id: changelog_reader 30 | uses: mindsers/changelog-reader-action@v2 31 | with: 32 | version: ${{ steps.tag_name.outputs.current_version }} 33 | path: ./CHANGELOG.md 34 | 35 | - name: Create Release 36 | id: create_release 37 | uses: actions/create-release@latest 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | with: 41 | tag_name: ${{ github.ref }} 42 | release_name: ${{env.BUILD_CONFIGURATION}} ${{ github.ref }} 43 | body: ${{ steps.changelog_reader.outputs.changes }} 44 | draft: false 45 | prerelease: false 46 | 47 | build: 48 | permissions: write-all 49 | name: Upload Release Asset 50 | runs-on: ubuntu-latest 51 | needs: create_release 52 | strategy: 53 | fail-fast: false 54 | matrix: 55 | include: 56 | - target: x86_64-pc-windows-gnu 57 | name: win-x86_64 58 | ext: .exe 59 | - target: x86_64-unknown-linux-musl 60 | name: linux-x86_64 61 | ext: 62 | - target: aarch64-unknown-linux-musl 63 | name: linux-arm64 64 | ext: 65 | steps: 66 | - name: Checkout 67 | uses: actions/checkout@v2 68 | - uses: actions-rs/toolchain@v1 69 | with: 70 | toolchain: stable 71 | target: ${{ matrix.target }} 72 | override: true 73 | - name: Get Executable Name 74 | id: extname 75 | shell: bash 76 | run: | 77 | echo "name=$PROJ_NAME$CURR_EXT" >> $GITHUB_OUTPUT 78 | env: 79 | PROJ_NAME: ${{env.NAME}} 80 | CURR_EXT: ${{matrix.ext}} 81 | - uses: actions-rs/cargo@v1 82 | with: 83 | use-cross: true 84 | command: build 85 | args: --release --target=${{ matrix.target }} --target-dir ./build 86 | - name: Zip 87 | run: | 88 | zip -j ${{env.NAME}}-${{matrix.name}}.zip ./build/${{matrix.target}}/release/${{steps.extname.outputs.name}} README.md CHANGELOG.md config.toml 89 | - name: Upload Asset 90 | id: upload-release-asset 91 | uses: actions/upload-release-asset@v1 92 | env: 93 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 94 | with: 95 | upload_url: ${{ needs.create_release.outputs.upload_url }} 96 | asset_path: ./${{env.NAME}}-${{matrix.name}}.zip 97 | asset_name: ${{env.NAME}}-${{matrix.name}}.zip 98 | asset_content_type: application/zip -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "server", 5 | "crypt", 6 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RustyDO 2 | ================================= 3 | 4 | A server emulator for Digimon Online v1.5. 5 | 6 | Homepage: https://jidoc01.github.io/RustyDO 7 | 8 | ## Credits 9 | 10 | Digimon Online v1.5 was created by CCR. The rights of the game and its IP belong 11 | to Move Interactive and BANDAI, respectively. 12 | 13 | This repository is to emulate the server and does not copyright the game itself 14 | in any form or use it for profit. The user is solely responsible for the 15 | consequences of using this. 16 | 17 | This repository is maintained by Jung Hyun Kim (@jidoc01). 18 | 19 | 디지몬 온라인 v1.5는 CCR이 제작한 온라인 게임입니다. 20 | 게임의 저작권, 그리고 게임이 사용하는 캐릭터 IP는 각각 Move Interactive, 그리고 BANDAI에게 있습니다. 21 | 22 | 이 프로젝트는 서비스 종료된 디지몬 온라인 v1.5 게임의 서버 에뮬레이터를 구현하는 것을 목표로 하고 있습니다. 23 | 서버 로직만을 구현할 뿐, 게임과 관련한 그 어떤 저작권도 침해하지 않습니다. 또한 이 프로젝트를 이용한 서버 운영은 게임산업법에 의거하여 엄격히 금지됩니다. 24 | 프로젝트 사용에 따른 법적 책임은 개인에게 귀속됩니다. 25 | 26 | ## Announcements 27 | 28 | ### RC4 Algorithm 29 | 30 | Now, RustyDO uses its self-contained RC4 algorithm, which does not require 31 | Windows APIs. At the same time, it won't need Windows OS for its environment. 32 | 33 | 34 | ## Objective 35 | 36 | This project is an open-source version of a toy server that I used to make in my 37 | free time. The goal is to recreate the game server of the time. Due to the lack 38 | of information, difficulties are expected, but we can try to imitate it as much 39 | as we can. 40 | 41 | 42 | ## Videos 43 | 44 | [![Video Label](http://img.youtube.com/vi/qFHj128fxyM/0.jpg)](https://youtu.be/qFHj128fxyMI) 45 | 46 | 47 | ## Screenshots 48 | 49 | | ![lobby](https://user-images.githubusercontent.com/12146267/183245660-494904c7-a072-4f31-839e-db5fffd7d04d.png) | 50 | |:--:| 51 | | *In a lobby* | 52 | 53 | | ![room](https://user-images.githubusercontent.com/12146267/183245665-af69dc61-a110-4577-a1e3-a4baa2fc7247.png) | 54 | |:--:| 55 | | *In a room* | 56 | 57 | | ![game](https://user-images.githubusercontent.com/12146267/183245667-5524b15f-648a-4d35-aaf6-b6dbb95eb018.png) | 58 | |:--:| 59 | | *In a game* | 60 | 61 | 62 | ## References 63 | 64 | + Wiki: 65 | + https://namu.wiki/w/%EB%94%94%EC%A7%80%EB%AA%AC%20%EC%98%A8%EB%9D%BC%EC%9D%B8 (Korean) 66 | + https://digimon.fandom.com/wiki/Digimon_Battle_Server 67 | 68 | + Client: https://archive.org/details/digimonbattleserver 69 | 70 | 71 | 72 | 73 | ## To those who want to contribute 74 | 75 | This project only implements basic functions. The parts that need to be 76 | implemented are marked as TODO on the source code. 77 | 78 | If you want to implement TODO, introduce new features (ex. processing a new 79 | packet, introducing new implementation, etc.), or improve inefficient parts of 80 | its existing implementation, feel free to issue. Especially, it is written in 81 | Rust language, but I am not very fluent in the language. And I am not aware of 82 | server architectures. Feel free to suggest any form of improvement in its design. 83 | 84 | 85 | ## TODOs 86 | 87 | There are features not implemented (or finished) yet. You can search `TODO` in the source code to check what features are not implemented yet. 88 | 89 | ### Board 90 | - [x] Writing a new post. 91 | - [x] Reading a post. 92 | - [ ] Modification/deletion/reply of posts. 93 | 94 | ### Shop 95 | - [x] Entering/Leaving a shop. 96 | - [ ] Checking validity of purchasing. 97 | 98 | ### Room 99 | - [x] Changing team/character/map. 100 | - [x] Kicking a player. 101 | 102 | ### In-game 103 | - [x] Basic movements. 104 | - [x] Using items. 105 | - [ ] Expressing emotions. 106 | - [ ] Kurumon generation algorithm. 107 | + It could be impossible to recover the exact algorithms which its original server used. 108 | + But I hope we imitate it as much as we can. 109 | - [ ] Item generation algorithm. 110 | - [ ] Red crack generation algorithm. 111 | - [ ] Improving priority calculation algorithm. 112 | - [ ] Leaving a game while playing it. 113 | - [ ] Gaining experiences after game. 114 | 115 | ### Messenger 116 | - [ ] Packet analysis (both in TCP/UDP). 117 | 118 | ### Ranking 119 | - [ ] Packet analysis. 120 | 121 | ### Etc. 122 | - [x] Changing nickname. 123 | - [x] Changing game settings. 124 | - [x] Chatting. 125 | - [x] Whispering. 126 | - [ ] Server migration. 127 | - [ ] Scheduling priority in events. 128 | 129 | ## License 130 | 131 | [GNU AGPL v3+](https://www.gnu.org/licenses/agpl-3.0.en.html) 132 | 133 | Copyright 2022. Jung Hyun Kim (jidoc01). -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | [server] 2 | # A password which is used when servers communicate with each other. 3 | # If it is empty, then it is filled with a random string to prevent attack. 4 | password = "" 5 | use_auto_account= true 6 | # TODO 7 | max_users = 1000 8 | # TODO 9 | tick_per_second = 20 10 | 11 | 12 | [user] 13 | initial_level = 1 14 | initial_money = 999999999 15 | 16 | 17 | [message] 18 | notice = ''' 19 | [디지몬 온라인 v1.5 프리서버 CBT] 20 | 21 | 안녕하세요, 테이머 여러분. 22 | 즐거운 CBT를 위해 몇 가지 안내드릴 사항이 있어, 아래와 같이 공지드립니다. 23 | 24 | · 게임머니는 충분히 많이 지급되오니 아이템을 적극 사용해 주세요. 25 | · 일부 기능들은 미구현입니다: 게시판, 랭킹, 사용자 간 DM. 26 | 해당 기능들은 인게임과 무관한 관계로, 차후 개발 예정입니다. 27 | · 인게임에서의 일부 구현 (동글몬 및 아이템 드롭)은, 당시의 정보가 부족한 관계로 28 | 아직 완전히 재현되지 않았습니다. 충분한 정보가 모이는 대로 재현될 것입니다. 29 | · 10분 간 아무런 행동을 취하지 않으면 접속 종료됩니다. 30 | 31 | 감사합니다. 32 | 33 | 지덕. 34 | ''' 35 | 36 | 37 | [ticker] 38 | initial = ''' 39 | 접속하고 싶은 채널을 선택해라 동~글. 40 | ''' 41 | board = ''' 42 | 읽고 싶은 글을 선택해라 동~글. 43 | ''' 44 | ranking = ''' 45 | 테이머들의 랭킹을 볼 수 있다 동~글. 46 | ''' 47 | selection = ''' 48 | 디지몬 온라인의 세계에 잘 왔다 동~글. 49 | ''' -------------------------------------------------------------------------------- /crypt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crypt" 3 | version = "0.0.1" 4 | authors = ["JungHyun Kim "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | once_cell = "1.12.0" 9 | anyhow = { version = "1.0" } -------------------------------------------------------------------------------- /crypt/src/lib.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | mod rc4; 19 | 20 | use once_cell::sync::Lazy; 21 | 22 | const HEADER_LENGTH: usize = 9; 23 | const SALT_LENGTH: usize = 16 - 5; 24 | 25 | static FEED: Lazy> = Lazy::new(|| { 26 | let mut key = b"\xf6\xef\x8b\xa1\x5c".to_vec(); 27 | let mut salt = b"\x00".repeat(SALT_LENGTH); 28 | key.append(&mut salt); 29 | key 30 | }); 31 | 32 | // TODO: Do not create a new buffer. 33 | pub fn transfer(data: &[u8]) -> Vec { 34 | let mut out = vec![0u8; data.len()]; 35 | rc4::transfer(data, &mut out, &FEED); 36 | out 37 | } 38 | 39 | #[inline] 40 | pub fn decode(data: &[u8]) -> anyhow::Result> { 41 | let len = data.len(); 42 | anyhow::ensure!(len == HEADER_LENGTH); 43 | let key = data[len - 1]; 44 | anyhow::ensure!(key < 8); 45 | let out = 46 | (0 .. len - 1) // Except the last element. 47 | .map(|i| (data[i] << key) | (data[(i + 1) % 8] >> (8 - key))) 48 | .collect(); 49 | Ok(out) 50 | } 51 | 52 | /// Encode 9 (8 + 1) bytes header. 53 | /// Returns 9 bytes including its seed value. 54 | #[inline] 55 | pub fn encode(data: &[u8]) -> anyhow::Result> { 56 | let len = data.len(); 57 | anyhow::ensure!(len == HEADER_LENGTH); 58 | let key = data[len - 1]; 59 | anyhow::ensure!(key < 8); 60 | let mut out: Vec = 61 | (0 .. len - 1) 62 | .map(|i| (data[i] >> key) | (data[(i + 8 - 1) % 8] << (8 - key))) 63 | .collect(); 64 | out.push(key); // Append its seed. 65 | Ok(out) 66 | } 67 | -------------------------------------------------------------------------------- /crypt/src/rc4.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | pub fn transfer(src: &[u8], dst: &mut [u8], key: &[u8]) { 19 | let mut s = generate_state(key); 20 | do_transfer(src, dst, &mut s); 21 | } 22 | 23 | fn generate_state(key: &[u8]) -> Vec { 24 | let mut s: Vec = (0..256).map(|i| i).collect(); 25 | (0..256).fold(0, |j, i| { 26 | let k = key[i % key.len()] as usize; 27 | let j = (j + k + s[i]) % 256; 28 | (s[i], s[j]) = (s[j], s[i]); 29 | j 30 | }); 31 | s 32 | } 33 | 34 | fn do_transfer(src: &[u8], dst: &mut [u8], s: &mut [usize]) { 35 | let len = src.len(); 36 | (0..len).fold((0, 0), |(i, j), k| { 37 | let i = (i + 1) % 256; 38 | let j = (j + s[i]) % 256; 39 | (s[i], s[j]) = (s[j], s[i]); 40 | let v = s[(s[i] + s[j]) % 256] as u8; 41 | dst[k] = src[k] ^ v; 42 | (i, j) 43 | }); 44 | } -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "RustyDO" 3 | version = "0.1.0" 4 | authors = ["JungHyun Kim "] 5 | readme = "README.md" 6 | repository = "https://github.com/jidoc01/RustyDO" 7 | edition = "2021" 8 | 9 | [dependencies] 10 | crypt = { path = "../crypt" } 11 | 12 | encoding = { version = "0.2" } 13 | tokio = { version = "1.19.1", features = ["full"] } 14 | once_cell = { version = "1.12.0" } 15 | byteorder = { version = "1.2" } 16 | rand = { version = "0.8.5" } 17 | anyhow = { version = "1.0" } 18 | async-trait = { version = "0.1.56" } 19 | #legion = { version = "0.4.0" } 20 | #parking_lot = { version = "0.4.0" } 21 | #rocksdb = { version = "0.18.0" } 22 | #confy = { version = "0.4.0" } 23 | config = { version = "0.13.1" } 24 | serde = { version = "1.0", features = ["derive"] } 25 | serde_json = { version = "1.0" } 26 | sha3 = { version = "0.10.1" } 27 | #diesel = { version = "1.4.8", features = [ "sqlite" ] } 28 | #hotpot-db = "0.0.2" 29 | rusqlite = { version = "0.21.0", features = ["bundled"] } 30 | nosqlite = { git = "https://github.com/HiruNya/nosqlite.git" } 31 | #ejdb = { version = "0.4" } 32 | hex = "*" 33 | dotenv = "*" 34 | chrono = "0.4.0" -------------------------------------------------------------------------------- /server/src/main.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | mod server; 19 | mod util; 20 | mod prelude; 21 | 22 | extern crate anyhow; 23 | 24 | use crate::prelude::*; 25 | 26 | const VERSION: &str = env!("CARGO_PKG_VERSION"); 27 | const AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); 28 | const REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY"); 29 | const LOGO: &str = r" 30 | _____ _ _____ ____ 31 | | __ \ | | | __ \ / __ \ 32 | | |__) | _ ___| |_ _ _| | | | | | | 33 | | _ / | | / __| __| | | | | | | | | | 34 | | | \ \ |_| \__ \ |_| |_| | |__| | |__| | 35 | |_| \_\__,_|___/\__|\__, |_____/ \____/ 36 | __/ | 37 | |___/ "; 38 | 39 | #[tokio::main] 40 | async fn main() -> Result<()> { 41 | println!("{LOGO}"); 42 | println!("RustyDO v{VERSION}"); 43 | println!("Repository: {REPOSITORY}"); 44 | println!("Contact: {AUTHORS}"); 45 | let config = Config::open(CONFIG_PATH)?; 46 | let db = Connection::open(DB_PATH)?; 47 | db.table(USER_TBL)?; 48 | db.table(POST_TBL)?; 49 | let mut login = login::Server::new(config.clone(), db); 50 | let login_tx = login.get_server_tx(); 51 | status::run(config, login_tx).await; 52 | login.run().await; // Note that it will block the thread. 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /server/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | /* 19 | Import frequently-used modules. 20 | */ 21 | pub use crate::{*, util::*, server::*}; 22 | pub use std::sync::{Arc}; // To prevent the confliction between std and tokio, we selectively use modules from std. 23 | pub use std::cell::RefCell; 24 | pub use tokio::sync::*; 25 | pub use async_trait::*; // For async traits. 26 | pub use anyhow::*; 27 | pub use std::result::Result::{Ok, Err}; 28 | 29 | /* 30 | Constants. 31 | */ 32 | pub const DB_PATH: &str = "./db.nosqlite"; 33 | pub const CONFIG_PATH: &str = "./config.toml"; 34 | 35 | pub const HEADER_SIZE: usize = 9; 36 | pub const TAIL_SIZE: usize = 3; 37 | 38 | pub const MAX_USERS: usize = 1000; 39 | 40 | pub const ITEM_COUNT: usize = 4; 41 | pub const EXP_COUNT: usize = 8; 42 | pub const MACRO_COUNT: usize = 8; 43 | 44 | pub const MIN_CLIENT_UID: ClientId = 1; 45 | pub const MAX_CLIENT_UID: ClientId = MAX_USERS as ClientId; 46 | 47 | /* 48 | Types. 49 | */ 50 | pub type ClientId = u16; 51 | 52 | // pub type Result = std::result::Result>; 53 | 54 | /* 55 | APIs. 56 | */ 57 | pub fn read_u16(vec: &[u8], off: usize) -> u16 { 58 | ((vec[off+0] as u16) << 0) | ((vec[off+1] as u16) << 8) 59 | } 60 | 61 | pub fn read_u32(vec: &[u8], off: usize) -> u32 { 62 | ((vec[off+0] as u32) << 0) 63 | | ((vec[off+1] as u32) << 8) 64 | | ((vec[off+2] as u32) << 16) 65 | | ((vec[off+3] as u32) << 24) 66 | } 67 | 68 | pub fn concat_list_of_vec(list: &[&Vec]) -> Vec { 69 | let mut out = vec!(); 70 | for bytes in list { 71 | out.extend_from_slice(bytes); 72 | } 73 | out 74 | } 75 | 76 | #[macro_export] 77 | macro_rules! run { 78 | ($x: expr) => { 79 | { 80 | tokio::spawn($x); 81 | } 82 | }; 83 | } 84 | 85 | #[inline] 86 | pub fn if_else(cond: bool, t: T, f: T) -> T { 87 | if cond { 88 | t 89 | } else { 90 | f 91 | } 92 | } 93 | 94 | #[allow(unused_macros)] 95 | 96 | #[cfg(debug_assertions)] 97 | #[macro_export] 98 | macro_rules! log { 99 | ($( $args:expr ),*) => { println!( $( $args ),* ); } 100 | } 101 | 102 | #[cfg(not(debug_assertions))] 103 | #[macro_export] 104 | macro_rules! log { 105 | ($( $args:expr ),*) => {()} 106 | } 107 | 108 | -------------------------------------------------------------------------------- /server/src/server/login/component.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | //use legion::Entity; 19 | 20 | use crate::prelude::*; 21 | 22 | pub type RoomId = u8; 23 | pub type MapKind = u8; 24 | 25 | pub const MAX_CHAR_ID: u8 = 8; 26 | pub const MAX_MAP_ID: u8 = 10; 27 | 28 | pub const CHAR_RANDOM_ID: u8 = MAX_CHAR_ID; 29 | pub const MAP_RANDOM_ID: u8 = MAX_MAP_ID; 30 | 31 | pub const MAX_ROOMS: usize = 200; 32 | pub const MIN_ROOM_ID: RoomId = 0; 33 | pub const MAX_ROOM_ID: RoomId = (MAX_ROOMS - 1) as RoomId; 34 | 35 | #[derive(Clone, Copy, Debug, PartialEq)] 36 | pub enum ClientState { 37 | BeforeLogin, 38 | AfterLogin, 39 | OnLobby, 40 | OnRoom, 41 | OnGame, 42 | } 43 | 44 | #[derive(Clone, Copy, Debug, PartialEq)] 45 | pub enum RoomState { 46 | Waiting = 1, 47 | Full = 2, 48 | Playing = 3, // Playing = not (1 or 2) 49 | } 50 | 51 | #[derive(Clone, Debug, PartialEq)] 52 | pub struct AfterLoginInfo { 53 | pub client_uid: ClientId, 54 | pub user_schema: UserSchema, 55 | } 56 | 57 | impl AfterLoginInfo { 58 | pub fn new( 59 | client_uid: ClientId, 60 | user_schema: UserSchema 61 | ) -> Self { 62 | Self { 63 | client_uid: client_uid, 64 | user_schema: user_schema, 65 | } 66 | } 67 | } 68 | 69 | #[derive(Clone, Debug, PartialEq)] 70 | pub enum StateInRoom { 71 | NotReady = 1, 72 | Shopping = 2, 73 | Ready = 3 74 | } 75 | 76 | #[derive(Clone, Debug, PartialEq)] 77 | pub struct OnRoomInfo { 78 | pub room_entity_id: EntityId, 79 | pub team: u8, 80 | pub character: u8, 81 | pub state_in_room: StateInRoom, 82 | } 83 | 84 | impl OnRoomInfo { 85 | pub fn new(room_entity_id: EntityId) -> Self { 86 | Self { 87 | room_entity_id: room_entity_id, 88 | team: 0, 89 | character: CHAR_RANDOM_ID, // TODO: Remember his recent played character. 90 | state_in_room: StateInRoom::NotReady 91 | } 92 | } 93 | } 94 | 95 | // For checking ping-pong. 96 | #[derive(Clone, Debug, PartialEq)] 97 | pub struct PongInfo(pub bool); 98 | 99 | #[derive(Clone, Debug, PartialEq)] 100 | pub struct RoomInfo { 101 | pub room_uid: RoomId, 102 | pub master_index: usize, 103 | pub members: [Option; 8], 104 | pub is_playing: bool, 105 | 106 | pub name: String, 107 | pub password: String, 108 | pub map: MapKind, 109 | pub allows_item: bool, 110 | pub allows_evol: bool, 111 | pub max_clients: u8, 112 | pub level_condition: u8, 113 | pub level_base: u8, 114 | } 115 | 116 | impl RoomInfo { 117 | pub fn new( 118 | room_uid: RoomId, 119 | name: &str, 120 | password: &str, 121 | master_entity_id: EntityId, 122 | allows_item: bool, 123 | allows_evol: bool, 124 | max_clients: u8, 125 | level_condition: u8, 126 | level_base: u8 127 | ) -> Self { 128 | let mut members = [None; 8]; 129 | members[0] = Some(master_entity_id); 130 | Self { 131 | room_uid: room_uid, 132 | is_playing: false, 133 | name: name.to_string(), 134 | password: password.to_string(), 135 | master_index: 0, 136 | map: MAP_RANDOM_ID, 137 | members: members, 138 | allows_item: allows_item, 139 | allows_evol: allows_evol, 140 | max_clients: max_clients, 141 | level_condition: level_condition, 142 | level_base: level_base 143 | } 144 | } 145 | } 146 | 147 | #[derive(Clone, Copy, Debug, PartialEq)] 148 | pub struct PlayerInfo { 149 | pub delay: u16, 150 | pub hp: u16, 151 | pub x: u16, 152 | pub y: u16, 153 | } 154 | 155 | impl PlayerInfo { 156 | pub fn default() -> Self { 157 | Self { 158 | delay: 0, 159 | hp: 0, 160 | x: 0, 161 | y: 0 162 | } 163 | } 164 | } 165 | 166 | #[derive(Clone, Debug, PartialEq)] 167 | // Note that a Room entity can have this GameInfo. 168 | pub struct GameInfo { 169 | pub room_eid: EntityId, 170 | 171 | /// It is used for checking either (1) game loading and (2) turn end. 172 | pub load_table: [bool; 8], 173 | 174 | pub turn_table: Vec, 175 | 176 | pub turn_index: usize, 177 | 178 | pub player_infos: [PlayerInfo; 8], // Represent states of in-game characters. 179 | } 180 | -------------------------------------------------------------------------------- /server/src/server/login/conn.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use crate::prelude::*; 19 | use tokio::{sync::mpsc::{UnboundedSender, unbounded_channel, UnboundedReceiver}, net::{TcpStream, tcp::{OwnedReadHalf, OwnedWriteHalf}}, io::AsyncReadExt}; 20 | use super::MsgToServer; 21 | 22 | pub enum MsgToConn { 23 | Shutdown, 24 | SendPacket(Vec), 25 | } 26 | 27 | pub type MsgToConnSender = UnboundedSender; 28 | 29 | enum RecvState { 30 | WaitForHeader, 31 | WaitForBody, 32 | } 33 | 34 | pub struct Conn { 35 | entity_id: EntityId, 36 | writer: RefCell, 37 | reader: RefCell, 38 | server_tx: UnboundedSender, 39 | conn_tx: RefCell>, 40 | conn_rx: RefCell>, 41 | is_running: bool, 42 | } 43 | 44 | impl Conn { 45 | pub fn new(entity: EntityId, stream: TcpStream, tx: UnboundedSender) -> Self { 46 | let (reader, writer) = stream.into_split(); 47 | let (conn_tx, conn_rx) = unbounded_channel(); 48 | Self { 49 | entity_id: entity, 50 | writer: RefCell::new(writer), 51 | reader: RefCell::new(reader), 52 | server_tx: tx, 53 | conn_tx: RefCell::new(conn_tx), 54 | conn_rx: RefCell::new(conn_rx), 55 | is_running: true, 56 | } 57 | } 58 | 59 | /// Explicitly drop itself. 60 | fn release(self) { 61 | drop(self); 62 | } 63 | 64 | /* 65 | async fn recv_packet(read: &OwnedReadHalf) 66 | -> anyhow::Result> { 67 | let header = Self::recv_exact(HEADER_SIZE, read.clone()).await?; 68 | let header = crypt::decode_header(&header); 69 | if header.is_none() { // Invalid packet. Should be noticed. 70 | bail!("Could not decode the header"); 71 | } 72 | let header = header.unwrap(); 73 | let body_size = read_u16(&header, 0) as usize; 74 | let to_read = body_size + TAIL_SIZE; 75 | let body = Self::recv_exact(to_read, cancel_rx.clone(), read.clone()).await?; 76 | let body = crypt::decrypt(&body); 77 | Ok(body) 78 | } 79 | */ 80 | 81 | /// It's used to disconnect itself when abnormal behavior was detected internally. 82 | fn disconnect(&mut self) { 83 | let _ = self.conn_tx.get_mut().send(MsgToConn::Shutdown); 84 | } 85 | 86 | fn handle_message(&mut self, msg: &MsgToConn) { 87 | match msg { 88 | MsgToConn::Shutdown => { 89 | self.is_running = false; 90 | }, 91 | MsgToConn::SendPacket(data) => { 92 | let disconnect = match self.writer.borrow_mut().try_write(&data) { 93 | Ok(n) => { 94 | if n != data.len() { 95 | todo!("TODO"); 96 | } 97 | false 98 | }, 99 | Err(err) => { 100 | println!("{}", err); 101 | true 102 | } 103 | }; 104 | if disconnect { 105 | self.disconnect(); 106 | } 107 | } 108 | } 109 | } 110 | 111 | fn handle_packet_chunk(&mut self, state: &mut RecvState, buf: &mut Vec) -> Result<()> { 112 | match state { 113 | RecvState::WaitForHeader => { 114 | let decoded = crypt::decode(&buf)?; 115 | let body_size = read_u16(&decoded, 0) as usize; 116 | let total_size = body_size + TAIL_SIZE; 117 | *buf = vec![0u8; total_size]; 118 | *state = RecvState::WaitForBody; 119 | }, 120 | RecvState::WaitForBody => { 121 | let decrypted = crypt::transfer(&buf); 122 | let pr = PacketReader::new(&decrypted); 123 | let entity = self.entity_id; 124 | self.server_tx.send(MsgToServer::OnPacketReceived(entity, pr))?; 125 | *buf = vec![0u8; HEADER_SIZE]; 126 | *state = RecvState::WaitForHeader; 127 | }, 128 | } 129 | Ok(()) 130 | } 131 | 132 | async fn handle(mut self) -> Result<()> { 133 | let mut state = RecvState::WaitForHeader; 134 | let mut buf: Vec = vec![0u8; HEADER_SIZE]; // Its size varies depending on its state. 135 | while self.is_running { 136 | tokio::select! { 137 | // Receive messages. 138 | msg = self.conn_rx.get_mut().recv() => { 139 | if let Some(msg) = msg { 140 | self.handle_message(&msg); 141 | } else { 142 | bail!("Invalid message on Conn"); 143 | } 144 | }, 145 | // Receive packets. 146 | n = self.reader.get_mut().read_exact(&mut buf) => { 147 | if let Ok(_n) = n { 148 | if let Err(err) = self.handle_packet_chunk(&mut state, &mut buf) { 149 | println!("Abnormal behavior: {}", err); 150 | self.disconnect(); 151 | } 152 | } else { // It can occur when the remote shut down its connection. 153 | self.disconnect(); 154 | } 155 | } 156 | } 157 | } 158 | // Let the server know that it has been disconnected. 159 | self.server_tx.send(MsgToServer::OnDisconnected(self.entity_id))?; 160 | self.release(); 161 | Ok(()) 162 | } 163 | 164 | pub fn start(self) { 165 | tokio::spawn(async move { 166 | if let Err(err) = self.handle().await { 167 | panic!("{}", err); 168 | } 169 | }); 170 | } 171 | 172 | pub fn get_conn_tx(&self) -> UnboundedSender { 173 | self.conn_tx.borrow().clone() 174 | } 175 | } -------------------------------------------------------------------------------- /server/src/server/login/entity/game.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use std::cmp::Ordering; 19 | 20 | use super::*; 21 | 22 | pub fn create(world: &mut World, info: GameInfo) -> &mut Entity { 23 | let id = world.push(); 24 | let entity = world.get_mut(&id).unwrap(); 25 | entity.push(EntityKind::Game); 26 | entity.push(info); 27 | entity 28 | } 29 | -------------------------------------------------------------------------------- /server/src/server/login/entity/mod.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | pub mod room; 19 | pub mod timer; 20 | pub mod session; 21 | 22 | use std::net::SocketAddr; 23 | 24 | use super::{component::*, conn::{MsgToConnSender, MsgToConn}}; 25 | use crate::prelude::*; 26 | 27 | #[derive(PartialEq)] 28 | pub enum EntityKind { 29 | Client, 30 | Room, 31 | Timer, 32 | } -------------------------------------------------------------------------------- /server/src/server/login/entity/room.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use std::cmp::Ordering; 19 | 20 | use super::*; 21 | 22 | pub fn create(world: &mut World, info: RoomInfo) -> &mut Entity { 23 | let id = world.push(); 24 | let entity = world.get_mut(&id).unwrap(); 25 | entity.push(EntityKind::Room); 26 | entity.push(info); 27 | entity 28 | } 29 | 30 | pub fn get_room_state(room: &Entity) -> Option { 31 | if let Ok(info) = room.get::() { 32 | if info.is_playing { 33 | Some(RoomState::Playing) 34 | } else { 35 | let count = 36 | info 37 | .members 38 | .iter() 39 | .fold(0, |acc, &x| acc + if_else(x.is_some(), 1, 0)); 40 | if count < info.max_clients { 41 | Some(RoomState::Waiting) 42 | } else { 43 | Some(RoomState::Full) 44 | } 45 | } 46 | } else { 47 | None 48 | } 49 | } 50 | 51 | pub enum TryAcceptClientResult { 52 | Ok, 53 | FullOfClients, 54 | InGame, 55 | } 56 | 57 | /// It tries to insert a client into a room. 58 | pub fn try_accept_client(room: &mut Entity, entity_id: EntityId) -> TryAcceptClientResult { 59 | let state = get_room_state(room).unwrap(); 60 | if state == RoomState::Full { 61 | return TryAcceptClientResult::FullOfClients; 62 | } else if state == RoomState::Playing { 63 | return TryAcceptClientResult::InGame; 64 | } 65 | 66 | let room_info = room 67 | .get_mut::() 68 | .unwrap(); 69 | let empty_index = room_info 70 | .members 71 | .iter() 72 | .enumerate() 73 | .find(|v| v.1.is_none()) 74 | .unwrap() 75 | .0; 76 | 77 | // Insert an user into a room. 78 | room_info.members[empty_index] = Some(entity_id); 79 | 80 | TryAcceptClientResult::Ok 81 | } 82 | 83 | pub fn get_room_list(world: &World, is_waiting_only: bool, mode: u8, base_room_id: RoomId) -> Vec<&Entity> { 84 | let mut rooms = { 85 | let mut rooms = world 86 | .select(|entity| { 87 | if let Some(state) = get_room_state(entity) { 88 | if is_waiting_only { // Waiting room only. 89 | state == RoomState::Waiting 90 | } else { 91 | true 92 | } 93 | } else { 94 | false 95 | } 96 | }); 97 | 98 | rooms.sort_by(|&room1, &room2| { 99 | if let (Ok(info1), Ok(info2)) = (room1.get::(), room2.get::()) { 100 | if_else(info1.room_uid < info2.room_uid, Ordering::Less, Ordering::Greater) 101 | } else { 102 | panic!("A room id should exist"); 103 | } 104 | }); 105 | 106 | rooms 107 | }; 108 | 109 | match mode { 110 | 1 => { // to the right 111 | let mut collected_rooms = vec!(); 112 | for &room in rooms.iter() { 113 | let id = room.get::().unwrap().room_uid; 114 | if id >= base_room_id { 115 | collected_rooms.push(room); 116 | 117 | if collected_rooms.len() == 8 { 118 | break; 119 | } 120 | } 121 | } 122 | 123 | if collected_rooms.len() < 8 { 124 | for &room in rooms.iter().rev() { 125 | let id = room.get::().unwrap().room_uid; 126 | if id < base_room_id { 127 | collected_rooms.insert(0, room); 128 | 129 | if collected_rooms.len() == 8 { 130 | break; 131 | } 132 | } 133 | } 134 | } 135 | 136 | rooms = collected_rooms; 137 | }, 138 | 2 => { // to the left 139 | let mut collected_rooms = vec!(); 140 | for &room in rooms.iter().rev() { 141 | let id = room.get::().unwrap().room_uid; 142 | if id <= base_room_id { 143 | collected_rooms.insert(0, room); 144 | 145 | if collected_rooms.len() == 8 { 146 | break; 147 | } 148 | } 149 | } 150 | 151 | if collected_rooms.len() < 8 { 152 | for &room in rooms.iter() { 153 | let id = room.get::().unwrap().room_uid; 154 | if id > base_room_id { 155 | collected_rooms.push(room); 156 | 157 | if collected_rooms.len() == 8 { 158 | break; 159 | } 160 | } 161 | } 162 | } 163 | 164 | rooms = collected_rooms; 165 | }, 166 | _ => { 167 | if rooms.len() > 8 { 168 | rooms = rooms[..8].to_vec(); 169 | } 170 | } 171 | } 172 | 173 | rooms 174 | } 175 | 176 | pub fn get_user_entity_ids(room: &Entity) -> Vec { 177 | let mut ret = vec!(); 178 | let info= room.get::().unwrap(); 179 | 180 | for place in info.members { 181 | if let Some(id) = place { 182 | ret.push(id); 183 | } 184 | } 185 | 186 | ret 187 | } 188 | 189 | pub fn get_index_in_room(room: &Entity, entity_id: &EntityId) -> Option { 190 | let info = room.get::().unwrap(); 191 | 192 | for (i, place) in info.members.iter().enumerate() { 193 | if let Some(id) = place { 194 | if *id == *entity_id { 195 | return Some(i); 196 | } 197 | } 198 | } 199 | 200 | return None; 201 | } 202 | 203 | pub fn send_to_all(world: &World, room: &Entity, pkt: &Vec) { 204 | let info = room.get::().unwrap(); 205 | 206 | for place in info.members.iter() { 207 | if let Some(id) = place { 208 | let entity = world 209 | .get(id) 210 | .unwrap(); 211 | session::send(entity, pkt.clone()); 212 | } 213 | } 214 | } 215 | 216 | pub fn send_to_all_except(world: &World, room: &Entity, pkt: &Vec, except_id: EntityId) { 217 | let info = room.get::().unwrap(); 218 | 219 | for place in info.members.iter() { 220 | match place { 221 | Some(id) if *id != except_id => { 222 | let entity = world 223 | .get(id) 224 | .unwrap(); 225 | session::send(entity, pkt.clone()); 226 | }, 227 | _ => {} 228 | } 229 | } 230 | } -------------------------------------------------------------------------------- /server/src/server/login/entity/session.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use super::*; 19 | 20 | pub fn create(world: &mut World, addr: SocketAddr) -> &mut Entity { 21 | let id = world.push(); 22 | let entity = world.get_mut(&id).unwrap(); 23 | entity.push(EntityKind::Client); 24 | entity.push(addr); 25 | entity.push(ClientState::BeforeLogin); 26 | entity 27 | } 28 | 29 | pub fn send(entity: &Entity, pkt: Vec) { 30 | let _ = entity 31 | .get::() 32 | .unwrap() 33 | .send(MsgToConn::SendPacket(pkt)); 34 | } 35 | 36 | pub fn send_by_eid(world: &World, eid: &EntityId, pkt: Vec) { 37 | let entity = world 38 | .get(eid) 39 | .unwrap(); 40 | send(entity, pkt); 41 | } -------------------------------------------------------------------------------- /server/src/server/login/entity/timer.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use std::{time::{Duration, Instant}, rc::Rc}; 19 | use crate::server::login::Server; 20 | use super::*; 21 | 22 | // pub type TimerCallback = fn(&mut Server, timer_entity_id: EntityId); 23 | pub type TimerCallback = dyn FnMut(&mut Server, &EntityId) -> bool; 24 | 25 | // TODO: Move it to component.rs? 26 | /// It is immutable. 27 | struct TimerInfo { 28 | pub callback: Rc>, 29 | pub duration: Duration, 30 | } 31 | 32 | #[macro_export] 33 | macro_rules! register_timer { 34 | ($world: expr, $duration: expr, $f: expr) => { 35 | { 36 | entity::timer::create($world, $duration, Rc::new(RefCell::new($f))); 37 | } 38 | }; 39 | } 40 | 41 | #[macro_export] 42 | /// It is used when a loop is implemented. 43 | macro_rules! register_loop { 44 | ($world: expr, $f: expr) => { 45 | { 46 | register_timer!($world, Duration::from_millis(100), $f); 47 | } 48 | }; 49 | } 50 | 51 | pub fn create(world: &mut World, duration: Duration, callback: Rc>) -> &mut Entity { 52 | let id = world.push(); 53 | let entity = world.get_mut(&id).unwrap(); 54 | let instant = std::time::Instant::now(); 55 | let timer_info = TimerInfo { 56 | callback: callback, 57 | duration: duration, 58 | }; 59 | entity.push(EntityKind::Timer); 60 | entity.push(timer_info); 61 | entity.push(instant); 62 | entity 63 | } 64 | 65 | /// Make its instant sync to the current time. 66 | fn proceed(world: &mut World, entity_id: &EntityId) { 67 | let entity = world.get_mut(entity_id).unwrap(); 68 | let instant = Instant::now(); 69 | entity.push(instant); 70 | } 71 | 72 | fn is_runnable(world: &World, timer_entity_id: &EntityId) -> bool { 73 | let timer = world 74 | .get(timer_entity_id) 75 | .unwrap(); 76 | let timer_info = timer 77 | .get::() 78 | .unwrap(); 79 | let instant = timer 80 | .get::() 81 | .unwrap(); 82 | let duration = timer_info.duration; 83 | let elapsed = instant.elapsed(); 84 | if elapsed.ge(&duration) { 85 | true 86 | } else { 87 | false 88 | } 89 | } 90 | 91 | fn get_callback(world: &World, timer_eid: &EntityId) -> Rc> { 92 | let timer = world 93 | .get(timer_eid) 94 | .unwrap(); 95 | let timer_info = timer 96 | .get::() 97 | .unwrap(); 98 | timer_info.callback.clone() 99 | } 100 | 101 | /// If it is a runnable timer, then run it. 102 | pub fn tick(server: &mut Server, timer_eid: &EntityId) { 103 | if !is_runnable(&server.world, timer_eid) { 104 | return; 105 | } 106 | 107 | let callback_ptr = get_callback(&server.world, timer_eid); 108 | let mut callback = callback_ptr.borrow_mut(); 109 | 110 | if callback(server, timer_eid) { 111 | proceed(&mut server.world, timer_eid); 112 | } else { 113 | server.world.remove(timer_eid); 114 | } 115 | } -------------------------------------------------------------------------------- /server/src/server/login/handler/after_login.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use std::borrow::Borrow; 19 | 20 | use rusqlite::{params, NO_PARAMS}; 21 | 22 | use crate::prelude::*; 23 | use crate::util::config::ConfigTicker; 24 | use super::*; 25 | 26 | const PACKET_FIND_NAME: u8 = 7; 27 | const PACKET_REQUEST_NOTICE: u8 = 16; 28 | const PACKET_REQUEST_TICKER: u8 = 20; 29 | // 30 | const PACKET_ENTER_BOARD: u8 = 26; 31 | const PACKET_READ_ARTICLE: u8 = 28; 32 | const PACKET_WRITE_ARTICLE: u8 = 30; 33 | // 34 | const PACKET_UPDATE_SETTINGS: u8 = 41; 35 | const PACKET_REGISTER_NAME: u8 = 44; 36 | const PACKET_ENTER_LOBBY: u8 = 57; 37 | const PACKET_CHANGE_NICKNAME: u8 = 123; 38 | 39 | fn get_notice_msg(server: &Server) -> String { 40 | server 41 | .config 42 | .message 43 | .notice 44 | .replace("\n", "\r\n\r\n") // Add carriage returns in front of the line breaks. 45 | } 46 | 47 | fn get_ticker_msg(server: &Server) -> ConfigTicker { 48 | server 49 | .config 50 | .ticker 51 | .clone() 52 | } 53 | 54 | fn send_notice(server: &mut Server, entity_id: &EntityId, pr: PacketReader) -> Result<()> { 55 | let notice_msg = get_notice_msg(server); 56 | let pkt = PacketWriter::new(PACKET_REQUEST_NOTICE + 1) 57 | .string_with_null(¬ice_msg) 58 | .as_vec(); 59 | let entity = server.world.get(entity_id).unwrap(); 60 | entity::session::send(entity, pkt); 61 | Ok(()) 62 | } 63 | 64 | fn send_ticker(server: &mut Server, entity_id: &EntityId, _pr: PacketReader) -> Result<()> { 65 | let ticker_msg = get_ticker_msg(server); 66 | let pkt = PacketWriter::new(PACKET_REQUEST_TICKER + 1) 67 | .string(&ticker_msg.initial, 101) 68 | .string(&ticker_msg.board, 101) 69 | .string(&ticker_msg.ranking, 101) 70 | .string(&ticker_msg.selection, 101) 71 | .as_vec(); 72 | entity::session::send_by_eid(&server.world, entity_id, pkt); 73 | Ok(()) 74 | } 75 | 76 | const POST_MAX_VISILITY: usize = 11; 77 | 78 | fn get_board_info_pkt(server: &Server, board_idx: usize) -> Vec { 79 | // TODO: Clean up. 80 | let max_post_id = { 81 | let db = &server.db; 82 | let query = format!("SELECT MAX(JSON_EXTRACT(data, '$.post_id')) from {POST_TBL}"); 83 | let mut stmt = db 84 | .as_ref() 85 | .prepare(&query) 86 | .unwrap(); 87 | let mut ret = stmt 88 | .query(NO_PARAMS) 89 | .unwrap(); 90 | match ret 91 | .next() 92 | .unwrap() { 93 | Some(row) => match row.get::<_, u32>(0) { 94 | Ok(max_post_id) => max_post_id, 95 | _ => 1 // It can happen when its type is Null. 96 | } 97 | None => 1, 98 | } 99 | }; 100 | // Note that post id starts from 1. 101 | // To allow negative numbers, we use isize. 102 | let post_id_ub = (max_post_id as isize) - ((board_idx * POST_MAX_VISILITY) as isize); 103 | let post_id_lb = post_id_ub - (POST_MAX_VISILITY as isize) + 1; 104 | let posts = { 105 | let db = &server.db; 106 | let posts: Vec = db 107 | .table(POST_TBL) 108 | .unwrap() 109 | .iter() 110 | .filter(field("post_id").gte(post_id_lb).and(field("post_id").lte(post_id_ub))) 111 | .take(POST_MAX_VISILITY as u32) 112 | .data(db) 113 | .unwrap(); 114 | posts 115 | }; 116 | 117 | let mut pw = PacketWriter::new(PACKET_ENTER_BOARD + 1); 118 | 119 | pw 120 | .u8(posts.len() as u8) // maximum: 11 (will cause overflow if it exceeds 11) 121 | .pad(3); 122 | for post in posts.iter().rev() { 123 | let name = { 124 | let db = &server.db; 125 | db 126 | .table(USER_TBL) 127 | .unwrap() 128 | .iter() 129 | .filter(field("id").eq(&post.writer_id)) 130 | .field::("name", db) 131 | .unwrap() 132 | .first() 133 | .map_or("알 수 없음".into(), |name| name.to_owned()) 134 | }; 135 | let y = post.datetime.year as u32; 136 | let m = post.datetime.month; 137 | let d = post.datetime.day; 138 | let h = post.datetime.hour; 139 | let min = post.datetime.min; 140 | pw 141 | .u8(if_else(post.is_notice, 1, 0)) // 0: is_admin (server notice) 142 | .pad(3) 143 | .u32(post.post_id) // 4: article unique id 144 | .u32(post.post_id) // 8: article number (to be shown in the list) 145 | .string(if_else(post.is_deleted, "삭제된 글입니다.", &post.title), 48) // 12: title 146 | .pad(1) // 60: null terminator 147 | .string(&name, 12) // 61: writter 148 | .pad(76 - 73) // 73 149 | .u32((y << 16) | (m << 8) | (d << 0)) // 76: y(2)/m(1)/d(1) (in a word) 150 | .u32((h << 16) | (min << 8)) // 80: hour(2)/min(1)/dummy(1) (in a word) 151 | .u16(post.views) // 84: 조회수 152 | .u8(0) // 86: n-th reply (0: not a reply) 153 | .pad(1); // an unit is 88 bytes. 154 | } 155 | 156 | pw.as_vec() 157 | } 158 | 159 | /// It is called when either (1) enter a board or (2) request the list of articles. 160 | fn enter_board(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 161 | //let pkt = packet::error(ErrorKind::BoardNotReady); 162 | let board_idx = pr.u32() as usize; 163 | let pkt = get_board_info_pkt(server, board_idx); 164 | entity::session::send_by_eid(&server.world, entity_id, pkt); 165 | Ok(()) 166 | } 167 | 168 | const POST_MAX_TEXT_LEN: usize = 2048; 169 | 170 | fn get_post_text_from_post_id(db: &Connection, post_id: u32) -> Option { 171 | db 172 | .table(POST_TBL) 173 | .unwrap() 174 | .iter() 175 | .filter(field("post_id").eq(post_id)) 176 | .field::("text", db) 177 | .unwrap() 178 | .first() 179 | .and_then(|text| Some(text.to_owned())) 180 | } 181 | 182 | fn read_article(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 183 | let post_id = pr.u32(); 184 | 185 | let db = &server.db; 186 | 187 | let is_deleted = db 188 | .table(POST_TBL)? 189 | .iter() 190 | .filter(field("post_id").eq(post_id)) 191 | .field::("is_deleted", db)? 192 | .first() 193 | .unwrap() 194 | .to_owned(); 195 | if is_deleted { 196 | // TODO: Use adequate packet. 197 | let pkt = packet::error(ErrorKind::BoardNotReady); 198 | entity::session::send_by_eid(&server.world, entity_id, pkt); 199 | return Ok(()); 200 | } 201 | 202 | let text = match get_post_text_from_post_id(db, post_id) { 203 | Some(text) => text, 204 | None => { 205 | // TODO: Use adequate packet. 206 | let pkt = packet::error(ErrorKind::BoardNotReady); 207 | entity::session::send_by_eid(&server.world, entity_id, pkt); 208 | return Ok(()); 209 | } 210 | }; 211 | // Increase its views. 212 | // TODO: Optimization with json_set query. 213 | { 214 | let views = db 215 | .table(POST_TBL)? 216 | .iter() 217 | .filter(field("post_id").eq(post_id)) 218 | .field::("views", db)? 219 | .first() 220 | .map_or(0, |views| views.to_owned()); 221 | db 222 | .table(POST_TBL)? 223 | .iter() 224 | .filter(field("post_id").eq(post_id)) 225 | .set("views", views + 1, db)?; 226 | } 227 | 228 | let pkt = PacketWriter::new(PACKET_READ_ARTICLE + 1) 229 | .string(&text, POST_MAX_TEXT_LEN) 230 | .as_vec(); 231 | let entity = server.world.get(entity_id).unwrap(); 232 | entity::session::send(entity, pkt); 233 | Ok(()) 234 | } 235 | 236 | fn assign_new_post_id(db: &Connection) -> u32 { 237 | let query = format!("SELECT MAX(JSON_EXTRACT(data, '$.post_id')) from {POST_TBL}"); 238 | let mut stmt = db 239 | .as_ref() 240 | .prepare(&query) 241 | .unwrap(); 242 | let mut ret = stmt 243 | .query(NO_PARAMS) 244 | .unwrap(); 245 | match ret 246 | .next() 247 | .unwrap() { 248 | Some(row) => match row.get::<_, u32>(0) { 249 | Ok(max_post_id) => max_post_id + 1, 250 | _ => 1 // It can happen when its type is Null. 251 | } 252 | None => 1, 253 | } 254 | } 255 | 256 | /// [Opcode] 257 | /// 0: TODO 258 | /// 1: Erase the list 259 | /// 2: TODO 260 | /// 3: TODO 261 | fn make_packet_for_write_article(err_kind: Option, opcode: u8) -> Vec { 262 | let err_code = match err_kind { 263 | Some(v) => v as u32, 264 | None => 0 265 | }; 266 | PacketWriter::new(PACKET_WRITE_ARTICLE + 1) 267 | .u32(err_code) // Error code can be sent. 268 | .u8(opcode) // TODO: opcode 269 | // 1: erase the list 270 | .as_vec() 271 | } 272 | 273 | fn write_article(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 274 | let write_mode = pr.u8(); // write mode 275 | // 0: new post 276 | // 1: modify 277 | // 2: delete 278 | // 3: reply 279 | pr.seek(3); 280 | let post_id = pr.u32(); // 0 when it is a new post. 281 | let title = pr.string(49); 282 | let text = pr.string(2051); 283 | 284 | let db = &server.db; 285 | 286 | match write_mode { 287 | 0 => { // new post 288 | if post_id != 0 { 289 | // TODO: Implement 290 | let pkt = make_packet_for_write_article(Some(ErrorKind::BoardNotReady), 0); 291 | entity::session::send_by_eid(&server.world, entity_id, pkt); 292 | return Ok(()); 293 | } 294 | 295 | let id = server 296 | .world 297 | .get(entity_id)? 298 | .get::()? 299 | .user_schema 300 | .id 301 | .borrow() as &String; 302 | 303 | let post_id = assign_new_post_id(db); 304 | 305 | let post_entity = PostSchema { 306 | post_id: post_id, 307 | parent_post_id: if_else(post_id == 0, None, Some(post_id)), 308 | writer_id: (*id).clone(), 309 | title: title, 310 | text: text, 311 | datetime: DateTimeSchema::now(), 312 | is_deleted: false, 313 | is_notice: false, 314 | views: 0, 315 | }; 316 | 317 | db 318 | .table(POST_TBL)? 319 | .insert(post_entity, &db)?; 320 | 321 | }, 322 | 1 => { // modify 323 | // TODO: Check if the post exists. 324 | // Check its writer. 325 | // TODO 326 | 327 | }, 328 | 2 => { // delete 329 | // TODO: Check if the post exists. 330 | /* 331 | db 332 | .table(POST_TBL)? 333 | .iter() 334 | .filter(field("post_id").eq(post_id)) 335 | .set("is_deleted", true, db)?; 336 | */ 337 | }, 338 | 3 => { // reply 339 | // TODO: Check if the post exists. 340 | 341 | 342 | }, 343 | _ => { 344 | bail!("Invalid opcode for write_article") 345 | } 346 | } 347 | 348 | let pkt_remove_list = make_packet_for_write_article(None, 1); 349 | let pkt_board_info = get_board_info_pkt(server, 0); 350 | entity::session::send_by_eid(&server.world, entity_id, pkt_remove_list); 351 | entity::session::send_by_eid(&server.world, entity_id, pkt_board_info); 352 | Ok(()) 353 | } 354 | 355 | fn update_settings(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 356 | let key_binding = pr.u8(); 357 | let macros = (0..8) 358 | .map(|_| pr.string(70)) 359 | .collect(); 360 | pr.seek(3); 361 | let bgm = pr.u32(); 362 | let sound = pr.u32(); 363 | 364 | let settings = SettingSchema { 365 | bgm_volume: (bgm & 0xff) as u8, 366 | bgm_mute: if_else((bgm >> 8) & (1 << 0) != 0, false, true), 367 | bgm_echo: if_else((bgm >> 8) & (1 << 1) != 0, true, false), 368 | sound_volume: (sound & 0xff) as u8, 369 | sound_mute: if_else((sound >> 8) & (1 << 0) != 0, false, true), 370 | key_binding: key_binding, 371 | macros: macros 372 | }; 373 | 374 | server 375 | .world 376 | .get_mut(entity_id)? 377 | .get_mut::()? 378 | .user_schema 379 | .setting = settings.clone(); 380 | 381 | let id = &server 382 | .world 383 | .get(entity_id)? 384 | .get::()? 385 | .user_schema 386 | .id; 387 | 388 | { 389 | let db = &server.db; 390 | let tbl = db.table(USER_TBL)?; 391 | 392 | tbl 393 | .iter() 394 | .filter(field("id").eq(id)) 395 | .patch(json!({ 396 | "setting": settings 397 | }), db)?; 398 | } 399 | 400 | Ok(()) 401 | } 402 | 403 | /// Send the client a list of rooms. 404 | fn enter_lobby(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 405 | let world = &mut server.world; 406 | 407 | let is_waiting_only = if_else(pr.u8() == 1, true, false); 408 | let mode = pr.u8(); // 0: From the beginning, 1: >= (forward), 2: <= (backward) 409 | let base_room_id = pr.u8(); 410 | 411 | { 412 | let entity = world.get_mut(entity_id).unwrap(); 413 | // Move into the lobby. 414 | entity.push(ClientState::OnLobby); 415 | } 416 | 417 | let rooms = entity::room::get_room_list(world, is_waiting_only, mode, base_room_id); 418 | 419 | { 420 | let entity = world.get(entity_id).unwrap(); 421 | let pkt = packet::room_list(world, &rooms); 422 | entity::session::send(&entity, pkt); 423 | } 424 | 425 | Ok(()) 426 | } 427 | 428 | fn make_packet_for_change_nickname(err_kind: Option) -> Vec { 429 | let err_code = match err_kind { 430 | Some(err_kind) => err_kind as u32, 431 | None => 0 432 | }; 433 | PacketWriter::new(PACKET_CHANGE_NICKNAME + 1) 434 | .u32(err_code) 435 | .as_vec() 436 | } 437 | 438 | fn change_nickname(server: &mut Server, eid: &EntityId, mut pr: PacketReader) -> Result<()> { 439 | let world = &mut server.world; 440 | let db = &server.db; 441 | 442 | let name = pr.string(12+1); 443 | let prev_name = world 444 | .get(eid)? 445 | .get::()? 446 | .user_schema 447 | .name 448 | .clone(); 449 | 450 | if name == prev_name { 451 | let pkt = make_packet_for_change_nickname(Some(ErrorKind::SameNickname)); 452 | entity::session::send_by_eid(world, eid, pkt); 453 | return Ok(()); 454 | } 455 | 456 | // TODO: Check validity of the name. 457 | match db 458 | .as_ref() 459 | .query_row(&format!("SELECT COUNT(*) from {USER_TBL} WHERE name = ?"), params![&name], |row| row.get(0)) { 460 | Ok(0) | Err(_) => {}, 461 | _ => { 462 | let pkt = make_packet_for_change_nickname(Some(ErrorKind::DupNickname)); 463 | entity::session::send_by_eid(world, eid, pkt); 464 | return Ok(()); 465 | } 466 | } 467 | 468 | world 469 | .get_mut(eid)? 470 | .get_mut::()? 471 | .user_schema 472 | .name = name.clone(); 473 | 474 | db 475 | .table(USER_TBL)? 476 | .iter() 477 | .filter(field("name").eq(prev_name)) // It is okay since uniqueness of names are ensured. 478 | .set("name", &name, db)?; 479 | 480 | let pkt = make_packet_for_change_nickname(None); 481 | entity::session::send_by_eid(world, eid, pkt); 482 | 483 | Ok(()) 484 | } 485 | 486 | 487 | pub fn handle(server: &mut Server, entity_id: &EntityId, pr: PacketReader) -> Result<()> { 488 | match pr.opcode() { 489 | PACKET_REQUEST_NOTICE => { 490 | send_notice(server, entity_id, pr) 491 | }, 492 | PACKET_REQUEST_TICKER => { 493 | send_ticker(server, entity_id, pr) 494 | }, 495 | PACKET_ENTER_BOARD => { 496 | enter_board(server, entity_id, pr) 497 | }, 498 | PACKET_READ_ARTICLE => { 499 | read_article(server, entity_id, pr) 500 | }, 501 | PACKET_WRITE_ARTICLE => { 502 | write_article(server, entity_id, pr) 503 | }, 504 | PACKET_UPDATE_SETTINGS => { 505 | update_settings(server, entity_id, pr) 506 | }, 507 | PACKET_ENTER_LOBBY => { 508 | enter_lobby(server, entity_id, pr) 509 | }, 510 | PACKET_CHANGE_NICKNAME => { 511 | change_nickname(server, entity_id, pr) 512 | }, 513 | opcode => { 514 | println!("Unknown packet, AfterLogin: {opcode}"); 515 | Ok(()) 516 | } 517 | } 518 | } -------------------------------------------------------------------------------- /server/src/server/login/handler/before_login.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use crate::prelude::*; 19 | use super::*; 20 | 21 | const PACKET_LOGIN: u8 = 3; 22 | const PACKET_REENTER_SERVER: u8 = 20; 23 | 24 | fn try_get_user_schema(db: &Connection, id: &String, pw: &String) -> (Option, Option) { 25 | let hashed_pw = hex::encode(hash::from_str(pw)); 26 | let tbl = db.table(USER_TBL).unwrap(); 27 | let found_user_schemas: Vec = tbl 28 | .iter() 29 | .filter(field("id").eq(id)) 30 | .data(&db) 31 | .unwrap(); 32 | match found_user_schemas.first() { 33 | Some(user_schema) => { 34 | // Check if its pw is correct. 35 | let actual_pw = &user_schema.pw; 36 | if *actual_pw != hashed_pw { // Invalid password. 37 | (None, Some (ErrorKind::InvalidAccInfo)) 38 | } 39 | // Check if it is banned. 40 | else if user_schema.is_banned { 41 | (None, Some (ErrorKind::Banned)) 42 | } else { 43 | (Some((*user_schema).clone()), None) 44 | } 45 | }, 46 | _ => (None, Some(ErrorKind::InvalidId)) // No id. 47 | } 48 | } 49 | 50 | /// Try to register a new client uid. 51 | /// It costs O(NlogN). 52 | fn try_register_client_uid(world: &World) -> Option { 53 | let id_set = { 54 | let mut id_set = HashSet::new(); 55 | world 56 | .values() 57 | .iter() 58 | .for_each(|&entity| { 59 | match entity.get::() { 60 | Ok(info) => { id_set.insert(info.client_uid); }, 61 | _ => {} 62 | } 63 | }); 64 | id_set 65 | }; 66 | 67 | for candidate in MIN_CLIENT_UID..=MAX_CLIENT_UID { 68 | if !id_set.contains(&candidate) { 69 | return Some(candidate); 70 | } 71 | } 72 | return None; 73 | } 74 | 75 | fn make_login_packet(info: &AfterLoginInfo) -> Vec { 76 | let schema = &info.user_schema; 77 | let setting = &info.user_schema.setting; 78 | let bgm_setting: u32 = 79 | if_else(!setting.bgm_mute, 1u32 << 8, 0u32) 80 | | if_else(setting.bgm_echo, 1u32 << 9, 0u32) 81 | | setting.bgm_volume as u32; 82 | let sound_setting: u32 = 83 | if_else(!setting.sound_mute, 1u32 << 8, 0) 84 | | setting.sound_volume as u32; 85 | let allow_nickname_mod = true; 86 | 87 | let mut pw = PacketWriter::new(PACKET_LOGIN + 1); 88 | pw 89 | .u16(info.client_uid) // Unique number among clients. 90 | .u8(0) // Unknown: (1 << 1) or (1 << 3) or both or none. 91 | .u8(if_else(schema.is_muted, 1, 0)) // Chat forbidden. 92 | .pad_to(16) 93 | .u8(0) 94 | .u8(schema.level) // Level. 95 | .u32(0) 96 | .u32(0) 97 | .u8(0) 98 | .u8(0) 99 | .string(&schema.name, 16) // User name. 100 | .u8(setting.key_binding); // 0: None, 1~4: Type A~D. 101 | setting.macros.iter().for_each(|msg| { 102 | pw 103 | .string(&msg, 55) 104 | .pad(15); 105 | }); 106 | pw 107 | .pad_to(608) 108 | .u32(bgm_setting) // bgm setting 109 | .u32(sound_setting) // sound setting 110 | .u32(schema.money); 111 | schema.exps.iter().for_each(|exp| { 112 | pw.u32(*exp); 113 | }); 114 | schema.items.iter().for_each(|item_no| { 115 | pw.u8(*item_no); 116 | }); 117 | /* TODO: Messanger infos */ 118 | let friend_count = 10; 119 | pw 120 | .u8(friend_count) 121 | .pad_to(660); 122 | for i in 0..friend_count { 123 | pw 124 | .u8(0) 125 | .u8(1) 126 | .u32(0) 127 | .u32(0) 128 | .u8(0) 129 | .u8(0) 130 | .string(&format!("User {i}"), 16); 131 | } 132 | pw.pad_to(1220); 133 | for i in 0..20 { // Unknown 134 | pw.string(&format!("Test {i}"), 13); 135 | } 136 | pw 137 | .u32(if_else(allow_nickname_mod, 1, 0)) 138 | .u32(0) 139 | .u8(25) // 25d. 140 | .u8(7) // 7m. 141 | .u16(2004) // 2004y. 142 | .as_vec() 143 | } 144 | 145 | fn login(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 146 | let id = pr.string(13); 147 | let password = pr.string(13); 148 | 149 | log!("[{}] Login request received.", entity_id); 150 | 151 | let use_auto_account = server 152 | .config 153 | .server 154 | .use_auto_account; 155 | // Check if the account information is valid. 156 | let user_schema = match try_get_user_schema(&server.db, &id, &password) { 157 | (Some(v), None) => v, 158 | (None, Some(ErrorKind::InvalidId)) if use_auto_account => { 159 | // Automatically generate an account. 160 | let name = id.clone(); 161 | match server.add_new_account(id.clone(), name, password.clone()).as_str() { 162 | "id_dup" => todo!(), 163 | "name_dup" => { 164 | let pkt = packet::error(ErrorKind::DupNickname); 165 | let entity = server.world.get(entity_id).unwrap(); 166 | entity::session::send(entity, pkt); 167 | return Ok(()); 168 | }, 169 | _ => { 170 | // Re-try. 171 | match try_get_user_schema(&server.db, &id, &password) { 172 | (Some(v), None) => v, 173 | _ => todo!() // Impossible. 174 | } 175 | } 176 | } 177 | }, 178 | (None, Some(err_kind)) => { 179 | let pkt = packet::error(err_kind); 180 | let entity = server.world.get(entity_id).unwrap(); 181 | entity::session::send(entity, pkt); 182 | return Ok(()); 183 | }, 184 | _ => todo!() 185 | }; 186 | 187 | // Check if it's online. 188 | let is_offline = server 189 | .world 190 | .select(|entity| { 191 | if let Ok(info) = entity.get::() { 192 | info.user_schema.id == id 193 | } else { 194 | false 195 | } 196 | }) 197 | .is_empty(); 198 | 199 | if !is_offline { 200 | let pkt = packet::error(packet::ErrorKind::Online); 201 | let entity = server.world.get(entity_id).unwrap(); 202 | entity::session::send(entity, pkt); 203 | return Ok(()); 204 | } 205 | 206 | // Try to get an unique id which will be used on the client-side. 207 | // Note that it's for distinguishing other clients from a client on 208 | // the client-side. 209 | let client_id = try_register_client_uid(&server.world); 210 | if client_id.is_none() { // Full of clients. 211 | let pkt = packet::error(packet::ErrorKind::FullOfClients); 212 | let entity = server.world.get(entity_id).unwrap(); 213 | entity::session::send(entity, pkt); 214 | return Ok(()); 215 | } 216 | let client_id = client_id.unwrap(); 217 | 218 | // Add components. 219 | let info = component::AfterLoginInfo::new( 220 | client_id, 221 | user_schema 222 | ); 223 | let entity = server.world.get_mut(entity_id).unwrap(); 224 | entity.push(ClientState::AfterLogin); 225 | entity.push(info.clone()); 226 | 227 | // Send a packet including various information of the user. 228 | let pkt = make_login_packet(&info); 229 | entity::session::send(&entity, pkt); 230 | 231 | log!("[{}] Login accepted.", entity_id); 232 | 233 | Ok(()) 234 | } 235 | 236 | fn reenter_server(server: &mut Server, entity_id: &EntityId, _pr: PacketReader) -> Result<()> { 237 | let entity = 238 | server 239 | .world 240 | .get_mut(entity_id)?; 241 | // Check if it is authenticated. 242 | if entity.get::().is_err() { 243 | bail!("Invalid access: it tried to re-enter the server, but was not authenticated"); 244 | } 245 | entity.push(ClientState::AfterLogin); 246 | 247 | Ok(()) 248 | } 249 | 250 | pub fn handle(server: &mut Server, entity_id: &EntityId, pr: PacketReader) -> Result<()> { 251 | match pr.opcode() { 252 | PACKET_LOGIN => { // Login request. 253 | login(server, entity_id, pr) 254 | }, 255 | PACKET_REENTER_SERVER => { 256 | reenter_server(server, entity_id, pr) 257 | }, 258 | opcode => { 259 | println!("Unknown packet, BeforeLogin: {opcode}"); 260 | Ok(()) 261 | } 262 | } 263 | } -------------------------------------------------------------------------------- /server/src/server/login/handler/mod.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | pub mod before_login; 19 | pub mod after_login; 20 | pub mod on_lobby; 21 | pub mod on_room; 22 | pub mod on_game; 23 | 24 | pub mod packet; 25 | 26 | use super::*; 27 | 28 | pub fn handle(server: &mut Server, entity_id: EntityId, pr: PacketReader) -> Result<()> { 29 | let state = server 30 | .world 31 | .get(&entity_id)? 32 | .get::()?; 33 | log!("[{}] Packet received: {}, {}", entity_id, pr.opcode(), pr.to_str()); 34 | match state { 35 | ClientState::BeforeLogin => { 36 | before_login::handle(server, &entity_id, pr) 37 | }, 38 | ClientState::AfterLogin => { 39 | after_login::handle(server, &entity_id, pr) 40 | }, 41 | ClientState::OnLobby => { 42 | on_lobby::handle(server, &entity_id, pr) 43 | }, 44 | ClientState::OnRoom => { 45 | on_room::handle(server, &entity_id, pr) 46 | }, 47 | ClientState::OnGame => { 48 | on_game::handle(server, &entity_id, pr) 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /server/src/server/login/handler/on_game.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use std::{cmp::Ordering, rc::Rc}; 19 | 20 | use crate::prelude::*; 21 | use super::*; 22 | 23 | const PACKET_ON_FRAME: u8 = 32; 24 | const PACKET_CHAT: u8 = 34; 25 | const PACKET_END_LOADING: u8 = 83; 26 | const PACKET_USE_ITEM: u8 = 85; 27 | const PACKET_END_TURN: u8 = 86; 28 | const PACKET_ON_DEATH: u8 = 88; // 88 - 128; On death. 29 | const PACKET_GO_TO_LOBBY: u8 = 95; 30 | const PACKET_EXIT_GAME: u8 = 128; // 128 - 95 - 57; 31 | 32 | pub fn handle_exit(server: &mut Server, eid: &EntityId) { 33 | // Make the player die in the game. 34 | // TODO: It is not a safe way. We need to assure that this player is not 35 | // referenced from other spots in game logics. 36 | let _ = handle_on_death(server, eid); 37 | } 38 | 39 | fn on_chat(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 40 | let world = &mut server.world; 41 | 42 | let unknown = pr.u8(); 43 | let chat_kind = pr.u8(); // 0: team chat, 1: whisper, 2: all 44 | let text = pr.string(193); 45 | 46 | let name = world 47 | .get(entity_id)? 48 | .get::()? 49 | .user_schema 50 | .name 51 | .clone(); 52 | let room_eid = get_room_eid(world, entity_id); 53 | let room = world.get(&room_eid)?; 54 | let index_in_room = entity::room::get_index_in_room(room, entity_id).unwrap(); 55 | 56 | // TODO: Emoticon. 57 | let pkt = packet::chat_in_game( 58 | unknown, chat_kind, &name, &text, 59 | None, Some(index_in_room as u8) 60 | ); 61 | 62 | let users = match chat_kind { 63 | 0 => { // To all 64 | let entity = world.get(entity_id)?; 65 | let on_room_info = entity.get::()?; 66 | let room_entity_id = on_room_info.room_entity_id; 67 | let room_entity = world.get(&room_entity_id)?; 68 | let room_info = room_entity.get::()?; 69 | let mut users = vec!(); 70 | room_info 71 | .members 72 | .iter() 73 | .for_each(|member| { 74 | if let Some(member_entity_id) = member { 75 | if let Ok(member_entity) = world.get(member_entity_id) { 76 | users.push(member_entity); 77 | } 78 | } 79 | }); 80 | users 81 | }, 82 | 1 => { // Whisper 83 | // TODO 84 | vec!() 85 | }, 86 | 2 => { // Team chat 87 | let entity = world.get(entity_id)?; 88 | let on_room_info = entity.get::()?; 89 | let team_no = on_room_info.team; 90 | let room_entity_id = on_room_info.room_entity_id; 91 | let room_entity = world.get(&room_entity_id)?; 92 | let room_info = room_entity.get::()?; 93 | let mut users = vec!(); 94 | room_info 95 | .members 96 | .iter() 97 | .for_each(|member| { 98 | if let Some(member_entity_id) = member { 99 | if let Ok(member_entity) = world.get(member_entity_id) { 100 | let on_room_info = member_entity 101 | .get::() 102 | .unwrap(); 103 | let curr_team_no = on_room_info.team; 104 | if team_no == curr_team_no { 105 | users.push(member_entity); 106 | } 107 | } 108 | } 109 | }); 110 | users 111 | }, 112 | _ => bail!("Invalid chat kind: {chat_kind}") 113 | }; 114 | 115 | for user in users { 116 | entity::session::send(user, pkt.clone()); 117 | } 118 | 119 | Ok(()) 120 | } 121 | 122 | fn make_turn_change_pkt( 123 | index_in_room: u8, 124 | wind_velocity: i8, 125 | item: Option<(u8, u16)>, 126 | kurumon: Option, 127 | delay_list: &Vec, 128 | hp_list: &Vec 129 | ) -> Vec { 130 | let (item_no, item_pos) = item.unwrap_or((0, 0)); 131 | 132 | let mut pw = PacketWriter::new(PACKET_END_TURN + 1); 133 | // 8 134 | pw 135 | .i32(wind_velocity as i32) // -30 ~ 30 (wind velocity) 136 | // 12 unknown 137 | .u8(1) // 1 or 0 138 | .u8(0) // if [12] == 0 139 | // 14~16 kurumon 140 | .u8(if_else(kurumon.is_some(), 0, 1)) // 1 or 0 141 | .pad(1) 142 | .u16(kurumon.unwrap_or(0)) // if [14] == 0 then kurumon position 143 | // 18 unknown 144 | .u8(0x00) // if [18] & 0x10 != 0 then [18] & 0x0F (low bits) 145 | // 19~20 item 146 | .u8(item_no) // item (0: no item) 147 | .u16(item_pos) // item position 148 | // 22 149 | .u8(1) // visible 150 | // 23 151 | .u8(index_in_room as u8); // whose turn 152 | // 24 153 | for i in 0..8 { 154 | pw 155 | .u16(0) // TODO: unknown 156 | .pad(2) 157 | .u32(hp_list[i]) // hp 158 | .pad(4); 159 | } 160 | // 120 unk 161 | pw 162 | .u16(0) 163 | // 122 unk 164 | .u16(0); 165 | pw.as_vec() 166 | } 167 | 168 | fn send_to_all(world: &World, room_eid: &EntityId, pkt: &Vec) { 169 | let room = world 170 | .get(&room_eid) 171 | .unwrap(); 172 | entity::room::send_to_all(world, room, pkt); 173 | } 174 | 175 | fn send_to_all_except(world: &World, room_eid: &EntityId, pkt: &Vec, except_id: EntityId) { 176 | let room = world 177 | .get(&room_eid) 178 | .unwrap(); 179 | entity::room::send_to_all_except(world, room, pkt, except_id); 180 | } 181 | 182 | fn get_index_in_room_in_turn(world: &World, room_eid: &EntityId) -> usize { 183 | let room = world 184 | .get(&room_eid) 185 | .unwrap(); 186 | let game_info = room 187 | .get::() 188 | .unwrap(); 189 | let turn_index = game_info.turn_index; 190 | let index_in_room = game_info.turn_table[turn_index]; 191 | index_in_room 192 | } 193 | 194 | fn get_room_eid(world: &World, eid: &EntityId) -> EntityId { 195 | let entity = world 196 | .get(eid) 197 | .unwrap(); 198 | let on_room_info = entity 199 | .get::() 200 | .unwrap(); 201 | on_room_info.room_entity_id 202 | } 203 | 204 | fn get_delay_list(world: &World, room_eid: &EntityId) -> Vec { 205 | let room = world 206 | .get(room_eid) 207 | .unwrap(); 208 | let game_info = room 209 | .get::() 210 | .unwrap(); 211 | 212 | (0..8) 213 | .map(|i| game_info.player_infos[i].delay as u16) 214 | .collect() 215 | } 216 | 217 | fn get_hp_list(world: &World, room_eid: &EntityId) -> Vec { 218 | let room = world 219 | .get(room_eid) 220 | .unwrap(); 221 | let game_info = room 222 | .get::() 223 | .unwrap(); 224 | 225 | (0..8) 226 | .map(|i| game_info.player_infos[i].hp as u32) 227 | .collect() 228 | } 229 | 230 | const MAX_WIND_VELOCITY: i8 = 30; 231 | 232 | fn change_turn(world: &mut World, room_eid: &EntityId, is_starting_game: bool) { 233 | // Move to the next turn. 234 | { 235 | let room = world 236 | .get_mut(&room_eid) 237 | .unwrap(); 238 | if !is_starting_game { 239 | // Try to find the next player who has not played yet and is still 240 | // alive. 241 | let game_info = room 242 | .get_mut::() 243 | .unwrap(); 244 | let turn_table_len = game_info.turn_table.len(); 245 | let mut idx = game_info.turn_index + 1; 246 | while idx < turn_table_len { 247 | let i = game_info.turn_table[idx]; 248 | let hp = game_info.player_infos[i].hp; 249 | if hp != 0 { 250 | break; 251 | } 252 | idx += 1; 253 | } 254 | game_info.turn_index = idx; 255 | } 256 | } 257 | 258 | // Check if a cycle is finished. 259 | let room = world 260 | .get(room_eid) 261 | .unwrap(); 262 | let room_info = room 263 | .get::() 264 | .unwrap(); 265 | let game_info = room 266 | .get::() 267 | .unwrap(); 268 | let turn_index = game_info.turn_index; 269 | let turn_table_len = game_info.turn_table.len(); 270 | if turn_index >= turn_table_len { // it has finished a cycle. 271 | // Set a new cycle. 272 | // The order is decided by delays of the previous cycle. 273 | let mut players_with_delay = vec!(); 274 | for i in 0..8 { 275 | if room_info 276 | .members[i] 277 | .is_some() { 278 | let delay = game_info.player_infos[i].delay; 279 | let hp = game_info.player_infos[i].hp; 280 | if hp != 0 { // Assure that his hp is not zero. 281 | players_with_delay.push((delay, i)); 282 | } 283 | } 284 | } 285 | 286 | // Reorder it. 287 | players_with_delay.sort_by(|(delay1, _), (delay2, _)| { 288 | if *delay1 < *delay2 { 289 | Ordering::Less 290 | } else if *delay1 > *delay2 { 291 | Ordering::Greater 292 | } else { 293 | // Grant its priority randomly. 294 | match rand::random::() % 3 { 295 | 0 => Ordering::Less, 296 | 1 => Ordering::Equal, 297 | 2 => Ordering::Greater, 298 | _ => panic!("Modulo 3 can not be more than 2") 299 | } 300 | } 301 | }); 302 | 303 | { 304 | let room = world 305 | .get_mut(room_eid) 306 | .unwrap(); 307 | let game_info = room 308 | .get_mut::() 309 | .unwrap(); 310 | 311 | game_info.turn_table = players_with_delay 312 | .iter() 313 | .map(|(_, idx)| *idx) 314 | .collect(); 315 | 316 | game_info.turn_index = 0; 317 | } 318 | } 319 | 320 | // Unset load table. 321 | { 322 | let room = world 323 | .get_mut(room_eid) 324 | .unwrap(); 325 | let game_info = room 326 | .get_mut::() 327 | .unwrap(); 328 | 329 | for i in 0..8 { 330 | game_info.load_table[i] = false; 331 | } 332 | } 333 | 334 | // send to all that a turn has been changed. 335 | { 336 | let index_in_room = get_index_in_room_in_turn(world, room_eid); 337 | let delay_list = get_delay_list(world, room_eid); 338 | let hp_list = get_hp_list(world, room_eid); 339 | let wind_velocity = rand::random::() % (MAX_WIND_VELOCITY + 1); // -30 ~ 30 340 | let pkt = make_turn_change_pkt(index_in_room as u8, wind_velocity, None, None, &delay_list, &hp_list); 341 | send_to_all(world, room_eid, &pkt); 342 | } 343 | } 344 | 345 | fn check_if_his_turn(world: &World, eid: &EntityId) -> bool { 346 | let room_eid = get_room_eid(world, eid); 347 | let room = world 348 | .get(&room_eid) 349 | .unwrap(); 350 | let index_in_room = entity::room::get_index_in_room(room, eid).unwrap(); 351 | let index_in_room_in_turn = get_index_in_room_in_turn(world, &room_eid); 352 | index_in_room == index_in_room_in_turn 353 | } 354 | 355 | fn check_if_load_finished(world: &World, room_eid: &EntityId) -> bool { 356 | let room = world 357 | .get(&room_eid) 358 | .unwrap(); 359 | let game_info = room 360 | .get::() 361 | .unwrap(); 362 | let room_info = room 363 | .get::() 364 | .unwrap(); 365 | 366 | (0..8) 367 | .all(|i| { 368 | let user_exists = room_info 369 | .members[i] 370 | .is_some(); 371 | let is_loaded = game_info.load_table[i]; 372 | !user_exists || is_loaded 373 | }) 374 | } 375 | 376 | /// Return Some(team) where team is an id of the team which has won the game. 377 | fn check_if_game_finished(world: &World, room_eid: &EntityId) -> Option { 378 | let mut survivors_by_team = [0usize; 8]; 379 | let room = world 380 | .get(room_eid) 381 | .unwrap(); 382 | let game_info = room 383 | .get::() 384 | .unwrap(); 385 | let room_info = room 386 | .get::() 387 | .unwrap(); 388 | 389 | for i in 0..8 { 390 | if let Some(member_eid) = room_info.members[i] { 391 | let member = world 392 | .get(&member_eid) 393 | .unwrap(); 394 | let on_room_info = member 395 | .get::() 396 | .unwrap(); 397 | let team_id = on_room_info.team; 398 | let hp = game_info.player_infos[i].hp; 399 | if hp > 0 { 400 | survivors_by_team[team_id as usize] += 1; 401 | } 402 | } 403 | } 404 | 405 | let survived_teams = survivors_by_team 406 | .iter() 407 | .filter(|&&n| n > 0) 408 | .count(); 409 | if survived_teams == 0 { 410 | Some(8) 411 | } else if survived_teams == 1 { 412 | let (team_id, _n) = survivors_by_team 413 | .iter() 414 | .enumerate() 415 | .find(|(_, &n)| n > 0) 416 | .unwrap(); 417 | Some(team_id) 418 | } else { 419 | None 420 | } 421 | } 422 | 423 | fn set_client_states_to_on_room(world: &mut World, room_eid: &EntityId) { 424 | let ids = { 425 | let room = 426 | world 427 | .get(&room_eid) 428 | .unwrap(); 429 | entity::room::get_user_entity_ids(room) 430 | }; 431 | ids 432 | .iter() 433 | .for_each(|id| { 434 | world 435 | .get_mut(&id) 436 | .unwrap() 437 | .push(ClientState::OnRoom); 438 | }); 439 | } 440 | 441 | fn end_game(world: &mut World, room_eid: &EntityId, winner_team: usize) { 442 | // Send packets to end this game. 443 | let pkt = PacketWriter::new(90) 444 | .u8(winner_team as u8) 445 | .u8(0) // TODO: unknown (boolean) 446 | .as_vec(); 447 | send_to_all(world, &room_eid, &pkt); 448 | 449 | // Change clients' states to OnRoom. 450 | set_client_states_to_on_room(world, room_eid); 451 | 452 | { 453 | let room = world 454 | .get_mut(room_eid) 455 | .unwrap(); 456 | let room_info = room 457 | .get_mut::() 458 | .unwrap(); 459 | 460 | // Unset is_playing. 461 | room_info.is_playing = false; 462 | 463 | // Unset players' readiness. 464 | let ids = { 465 | let room = world 466 | .get(&room_eid) 467 | .unwrap(); 468 | entity::room::get_user_entity_ids(room) 469 | }; 470 | ids 471 | .iter() 472 | .for_each(|id| { 473 | world 474 | .get_mut(&id) 475 | .unwrap() 476 | .get_mut::() 477 | .unwrap() 478 | .state_in_room = StateInRoom::NotReady; 479 | }); 480 | } 481 | } 482 | 483 | pub fn loop_main(world: &mut World, room_eid: EntityId) { 484 | // Start a turn. 485 | change_turn(world, &room_eid, true); 486 | 487 | let turn_duration = Duration::from_secs(60 * 2); 488 | let mut turn_instant = Instant::now(); 489 | register_loop!(world, move |server: &mut Server, _timer_eid: &EntityId| -> bool { 490 | let world = &mut server.world; 491 | 492 | let is_turn_finished = check_if_load_finished(world, &room_eid); 493 | let is_turn_timeout = turn_instant.elapsed().ge(&turn_duration); 494 | if is_turn_finished || is_turn_timeout { // Go to the next turn. 495 | if is_turn_timeout { 496 | // TODO: Check who has timed out and kick him off. 497 | todo!("kick the player who caused turn timeout"); 498 | } 499 | 500 | // Check if the game has ended. 501 | if let Some(team) = check_if_game_finished(world, &room_eid) { 502 | end_game(world, &room_eid, team); 503 | return false; 504 | } 505 | 506 | // Start a new turn. 507 | change_turn(&mut server.world, &room_eid, false); 508 | 509 | // Reset the turn timer. 510 | turn_instant = Instant::now(); 511 | } 512 | 513 | return true; 514 | }); 515 | } 516 | 517 | pub fn loop_loading(world: &mut World, room_eid: EntityId) { 518 | let duration = Duration::from_secs(30); // At most 30 secs. 519 | let instant = Instant::now(); 520 | 521 | register_loop!(world, move |server: &mut Server, _timer_eid: &EntityId| -> bool { 522 | let world = &mut server.world; 523 | // Check if all ready, or it is timeout. 524 | let is_all_loaded = check_if_load_finished(world, &room_eid); 525 | let is_timeout = instant.elapsed().ge(&duration); 526 | if is_timeout { 527 | // TODO: Players who are not yet ready for battle must be kicked off. 528 | // It might be due to that (1) he left the server (2) or it is on a low latency. 529 | todo!("Handle players with too long loading time"); 530 | } 531 | if is_all_loaded || is_timeout { 532 | loop_main(world, room_eid); 533 | 534 | return false; 535 | } 536 | 537 | return true; 538 | }); 539 | } 540 | 541 | fn on_frame(server: &mut Server, eid: &EntityId, mut pr: PacketReader) -> Result<()> { 542 | let world = &mut server.world; 543 | 544 | if !check_if_his_turn(world, eid) { 545 | return Ok(()); // TODO: Hack? 546 | } 547 | 548 | pr.seek(4); 549 | let left = pr.left(); 550 | let movement_data = pr.vec(left); 551 | 552 | // Send this information to the others (except himself). 553 | let pkt = { 554 | let mut pw = PacketWriter::new(PACKET_ON_FRAME + 1); 555 | pw.pad_to(27); 556 | pw.vec(&movement_data); 557 | pw.as_vec() 558 | }; 559 | 560 | let room_eid = get_room_eid(world, eid); 561 | send_to_all_except(world, &room_eid, &pkt, *eid); 562 | Ok(()) 563 | } 564 | 565 | fn end_loading(server: &mut Server, eid: &EntityId, mut pr: PacketReader) -> Result<()> { 566 | let world = &server.world; 567 | 568 | // character state values. 569 | // 0: delay 570 | // 1: current hp 571 | // 2: x (right) 572 | // 3: y (down) 573 | let words: Vec = (0..4).map(|_| pr.u16()).collect(); 574 | 575 | let room_eid = get_room_eid(world, eid); 576 | let room = server 577 | .world 578 | .get_mut(&room_eid) 579 | .unwrap(); 580 | let index_in_room = entity::room::get_index_in_room(room, eid).unwrap(); 581 | let game_info = room 582 | .get_mut::() 583 | .unwrap(); 584 | 585 | game_info.load_table[index_in_room] = true; 586 | 587 | game_info.player_infos[index_in_room].delay = words[0]; 588 | game_info.player_infos[index_in_room].hp = words[1]; 589 | game_info.player_infos[index_in_room].x = words[2]; 590 | game_info.player_infos[index_in_room].y = words[3]; 591 | 592 | Ok(()) 593 | } 594 | 595 | fn use_item(server: &mut Server, eid: &EntityId, mut pr: PacketReader) -> Result<()> { 596 | let world = &server.world; 597 | 598 | let left = pr.left(); 599 | let bytes = pr.vec(left); 600 | 601 | let pkt = PacketWriter::new(PACKET_USE_ITEM) 602 | .vec(&bytes) 603 | .as_vec(); 604 | let room_eid = get_room_eid(world, eid); 605 | send_to_all_except(world, &room_eid, &pkt, *eid); 606 | Ok(()) 607 | } 608 | 609 | fn end_turn(server: &mut Server, eid: &EntityId, mut pr: PacketReader) -> Result<()> { 610 | let world = &mut server.world; 611 | 612 | // character state values. 613 | let words: Vec = (0..4).map(|_| pr.u16()).collect(); 614 | 615 | let room_eid = get_room_eid(world, eid); 616 | let room = world 617 | .get_mut(&room_eid) 618 | .unwrap(); 619 | 620 | let index_in_room = entity::room::get_index_in_room(room, eid).unwrap(); 621 | let game_info = room 622 | .get_mut::() 623 | .unwrap(); 624 | 625 | game_info.load_table[index_in_room] = true; 626 | 627 | game_info.player_infos[index_in_room].delay = words[0]; 628 | game_info.player_infos[index_in_room].hp = words[1]; 629 | game_info.player_infos[index_in_room].x = words[2]; 630 | game_info.player_infos[index_in_room].y = words[3]; 631 | 632 | Ok(()) 633 | } 634 | 635 | fn make_on_death_packet(index_in_room: usize) -> Vec { 636 | PacketWriter::new(PACKET_ON_DEATH + 1) 637 | .u8(index_in_room as u8) 638 | .as_vec() 639 | } 640 | 641 | fn handle_on_death(server: &mut Server, eid: &EntityId) -> Result<()> { 642 | let world = &server.world; 643 | let room_eid = get_room_eid(world, eid); 644 | let pkt = { 645 | let room = world.get(&room_eid)?; 646 | let index_in_room = entity::room::get_index_in_room(room, eid).unwrap(); 647 | make_on_death_packet(index_in_room) 648 | }; 649 | send_to_all(world, &room_eid, &pkt); 650 | Ok(()) 651 | } 652 | 653 | fn on_death(server: &mut Server, eid: &EntityId, _pr: PacketReader) -> Result<()> { 654 | handle_on_death(server, eid) 655 | } 656 | 657 | pub fn handle(server: &mut Server, entity_id: &EntityId, pr: PacketReader) -> Result<()> { 658 | match pr.opcode() { 659 | PACKET_CHAT => { 660 | // Re-use it. 661 | on_chat(server, entity_id, pr) 662 | }, 663 | PACKET_ON_FRAME => { 664 | on_frame(server, entity_id, pr) 665 | }, 666 | PACKET_END_LOADING => { 667 | end_loading(server, entity_id, pr) 668 | }, 669 | PACKET_USE_ITEM => { 670 | use_item(server, entity_id, pr) 671 | }, 672 | PACKET_END_TURN => { 673 | end_turn(server, entity_id, pr) 674 | }, 675 | PACKET_ON_DEATH => { 676 | on_death(server, entity_id, pr) 677 | }, 678 | opcode => { 679 | println!("Unknown packet, OnGame: {opcode}"); 680 | Ok(()) 681 | } 682 | } 683 | } -------------------------------------------------------------------------------- /server/src/server/login/handler/on_lobby.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use crate::{prelude::*, server::login::entity::room::TryAcceptClientResult}; 19 | use super::*; 20 | 21 | const PACKET_CHAT: u8 = 34; 22 | const PACKET_ENTER_SHOP: u8 = 50; 23 | const PACKET_GO_BACK: u8 = 54; 24 | const PACKET_REQUEST_USER_LIST: u8 = 55; 25 | const PACKET_REQUEST_ROOM_LIST: u8 = 57; 26 | const PACKET_CREATE_ROOM: u8 = 59; 27 | const PACKET_ENTER_ROOM: u8 = 62; 28 | 29 | fn try_get_new_room_id(world: &mut World) -> Option { 30 | let id_set = { 31 | let mut id_set = HashSet::new(); 32 | world 33 | .values() 34 | .iter() 35 | .for_each(|&entity| { 36 | match entity.get::() { 37 | Ok(info) => { id_set.insert(info.room_uid); }, 38 | _ => {} 39 | } 40 | }); 41 | id_set 42 | }; 43 | 44 | for candidate in MIN_ROOM_ID..=MAX_ROOM_ID { 45 | if !id_set.contains(&candidate) { 46 | return Some(candidate); 47 | } 48 | } 49 | return None; 50 | } 51 | 52 | // TODO: Do not make it public. Instead make an API to deal with on-chat requests. 53 | pub fn on_chat(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 54 | let world = &mut server.world; 55 | 56 | let unknown = pr.u8(); 57 | let chat_kind = pr.u8(); // 0: Normal, 1: Whisper. 58 | let text = pr.string(193); 59 | 60 | let name = world 61 | .get(entity_id)? 62 | .get::()? 63 | .user_schema 64 | .name 65 | .clone(); 66 | 67 | if chat_kind == 1 { // Whisper. 68 | let receiver_name = pr.string(13); 69 | 70 | let users = world 71 | .select(|entity| { 72 | if let Ok(info) = entity.get::() { 73 | info.user_schema.name == receiver_name 74 | } else { 75 | false 76 | } 77 | }); 78 | 79 | if users.len() == 0 { // Could not find the user. 80 | // TODO: Send a packet to show an error message. 81 | let pkt = packet::chat(true, false, "", "존재하지 않는 아이디입니다."); 82 | let entity = world.get(entity_id).unwrap(); 83 | entity::session::send(entity, pkt); 84 | } else { 85 | let receiver = users[0]; 86 | let pkt = packet::chat(false, true, &name, &text); 87 | entity::session::send(receiver, pkt); 88 | } 89 | 90 | return Ok(()); 91 | } 92 | 93 | let &state = world 94 | .get(entity_id)? 95 | .get::()?; 96 | 97 | let pkt = packet::chat(false, false, &name, &text); 98 | 99 | // TODO: Combine all together. 100 | // Just send the packet to different receivers with the same logic. 101 | match state { 102 | ClientState::OnLobby => { 103 | let users = world 104 | .select(|entity| { 105 | if let Ok(ClientState::OnLobby) = entity.get::() { 106 | true 107 | } else { 108 | false 109 | } 110 | }); 111 | 112 | for user in users { 113 | entity::session::send(user, pkt.clone()); 114 | } 115 | }, 116 | ClientState::OnRoom => { 117 | let users = { 118 | let entity = world.get(entity_id)?; 119 | let on_room_info = entity.get::()?; 120 | let room_entity_id = on_room_info.room_entity_id; 121 | let room_entity = world.get(&room_entity_id)?; 122 | let room_info = room_entity.get::()?; 123 | let mut users = vec!(); 124 | room_info 125 | .members 126 | .iter() 127 | .for_each(|member| { 128 | if let Some(member_entity_id) = member { 129 | let member_entity = world 130 | .get(member_entity_id) 131 | .unwrap(); 132 | users.push(member_entity); 133 | } 134 | }); 135 | users 136 | }; 137 | 138 | for user in users { 139 | entity::session::send(user, pkt.clone()); 140 | } 141 | }, 142 | ClientState::OnGame => { 143 | 144 | }, 145 | _ => {} 146 | } 147 | 148 | Ok(()) 149 | } 150 | 151 | fn enter_shop(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 152 | let world = &mut server.world; 153 | 154 | // TODO 155 | 156 | Ok(()) 157 | } 158 | 159 | fn go_back(server: &mut Server, entity_id: &EntityId, pr: PacketReader) -> Result<()> { 160 | server 161 | .world 162 | .get_mut(entity_id)? 163 | .push(ClientState::BeforeLogin); 164 | Ok(()) 165 | } 166 | 167 | fn send_user_list(server: &mut Server, entity_id: &EntityId, pr: PacketReader) -> Result<()> { 168 | let entity = server.world.get(entity_id).unwrap(); 169 | 170 | // Get a info list of users who are on lobby. 171 | let users = server 172 | .world 173 | .select(|entity| { 174 | if let Ok(state) = entity.get::() { 175 | match state { 176 | ClientState::OnLobby => true, 177 | _ => false 178 | } 179 | } else { 180 | false 181 | } 182 | }); 183 | 184 | let user_count = users.len(); 185 | 186 | let pkt = { 187 | let mut pw = PacketWriter::new(PACKET_REQUEST_USER_LIST + 1); 188 | pw 189 | .u16(user_count as u16) 190 | .pad_to(10); 191 | for user in users { 192 | let info = user 193 | .get::()?; 194 | pw 195 | .string(&info.user_schema.name, 13) 196 | .u8(info.user_schema.level) 197 | .pad(4); 198 | } 199 | pw.as_vec() 200 | }; 201 | 202 | entity::session::send(&entity, pkt); 203 | 204 | Ok(()) 205 | } 206 | 207 | fn send_room_list(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 208 | let world = &mut server.world; 209 | 210 | let is_waiting_only = if_else(pr.u8() == 1, true, false); 211 | let mode = pr.u8(); // 0: From the beginning, 1: >= (forward), 2: <= (backward) 212 | let base_room_id = pr.u8(); 213 | 214 | let rooms = entity::room::get_room_list(world, is_waiting_only, mode, base_room_id); 215 | 216 | { 217 | let entity = world.get(entity_id).unwrap(); 218 | let pkt = packet::room_list(world, &rooms); 219 | entity::session::send(&entity, pkt); 220 | } 221 | 222 | Ok(()) 223 | } 224 | 225 | fn create_room(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 226 | let world = &mut server.world; 227 | 228 | let room_id = 229 | match try_get_new_room_id(world) { 230 | Some(id) => id, 231 | None => { // Full of rooms. 232 | // TODO: Find an adequate packet. 233 | let entity = world.get(entity_id).unwrap(); 234 | let pkt = packet::error(packet::ErrorKind::FullOfClientsInRoom); 235 | entity::session::send(entity, pkt); 236 | 237 | return Ok(()); 238 | } 239 | }; 240 | 241 | let name = pr.string(13); 242 | pr.seek(12); 243 | let password = pr.string(13); 244 | let max_users = pr.u8(); 245 | let lev_condition = pr.u8(); 246 | let lev_base = pr.u8(); 247 | let bits = pr.u8(); 248 | let allows_item = { 249 | if bits & (1 << 0) != 0 { 250 | true 251 | } else { 252 | false 253 | } 254 | }; 255 | let allows_evol = { 256 | if bits & (1 << 1) != 0 { 257 | true 258 | } else { 259 | false 260 | } 261 | }; 262 | 263 | // Create a new room into the world. 264 | let info = RoomInfo::new(room_id, &name, &password, *entity_id, allows_item, allows_evol, max_users, lev_condition, lev_base); 265 | let room = entity::room::create(world, info); 266 | 267 | // Insert into a room. 268 | /* 269 | match entity::room::try_accept_client(room, *entity_id) { 270 | TryAcceptClientResult::Ok => {}, 271 | _ => panic!("A new room should be able to accept its master") 272 | } 273 | */ 274 | 275 | let room_entity_id = room.id(); 276 | 277 | let entity = world.get_mut(entity_id).unwrap(); 278 | 279 | // Change entity's state. 280 | entity.push(ClientState::OnRoom); 281 | entity.push(OnRoomInfo::new(room_entity_id)); 282 | 283 | // Send a packet to join the room. 284 | { 285 | let pkt = 286 | PacketWriter::new(PACKET_CREATE_ROOM + 1) 287 | .u16(room_id as u16) 288 | .as_vec(); 289 | entity::session::send(entity, pkt); 290 | } 291 | 292 | Ok(()) 293 | } 294 | 295 | fn enter_room(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 296 | fn try_enter_room(world: &mut World, entity_id: &EntityId, requested_room_id: RoomId) -> Result { 297 | let mut rooms = 298 | world 299 | .select_mut(|entity| { 300 | if let Ok(info) = entity.get::() { 301 | if info.room_uid == requested_room_id { 302 | true 303 | } else { 304 | false 305 | } 306 | } else { 307 | false 308 | } 309 | }); 310 | 311 | if rooms.len() == 0 { // No room. 312 | let entity = 313 | world 314 | .get(entity_id)?; 315 | let pkt = packet::error(packet::ErrorKind::NoRoom); 316 | entity::session::send(entity, pkt); 317 | return Ok(false); 318 | } 319 | 320 | let room = rooms.pop().unwrap(); 321 | 322 | match entity::room::try_accept_client(room, *entity_id) { 323 | TryAcceptClientResult::InGame => { 324 | let entity = 325 | world 326 | .get(entity_id)?; 327 | // TODO: Find a packet to show that it's already playing. 328 | let pkt = packet::error(packet::ErrorKind::NoRoom); 329 | entity::session::send(entity, pkt); 330 | return Ok(false); 331 | }, 332 | TryAcceptClientResult::FullOfClients => { 333 | let entity = 334 | world 335 | .get(entity_id)?; 336 | let pkt = packet::error(packet::ErrorKind::FullOfClientsInRoom); 337 | entity::session::send(entity, pkt); 338 | return Ok(false); 339 | }, 340 | _ => {} 341 | } 342 | 343 | // Add OnRoomInfo to the user. 344 | let on_room_info = component::OnRoomInfo::new(room.id()); 345 | let entity = world.get_mut(entity_id).unwrap(); 346 | entity.push(on_room_info); 347 | 348 | // Change the ClientState. 349 | entity.push(ClientState::OnRoom); 350 | 351 | Ok(true) 352 | } 353 | 354 | let world = &mut server.world; 355 | let requested_room_id = pr.u8(); 356 | 357 | if !try_enter_room(world, entity_id, requested_room_id)? { 358 | return Ok(()); 359 | } 360 | 361 | let entity = world.get(entity_id).unwrap(); 362 | let room_entity_id = entity.get::().unwrap().room_entity_id; 363 | 364 | // Send a packet to make the client enter the room. 365 | let pkt = { 366 | let room_entity = world.get(&room_entity_id).unwrap(); 367 | let room_info = room_entity.get::().unwrap(); 368 | let mut pw = PacketWriter::new(PACKET_ENTER_ROOM + 1); 369 | pw 370 | .u8(room_info.master_index as u8) // 8 (room owner index) 371 | .u8(room_info.map); // 9 (map) 372 | // 10 373 | for i in 0..8 { 374 | if let Some(member_entity_id) = room_info.members[i] { 375 | let member_entity = world.get(&member_entity_id).unwrap(); 376 | let member_info = member_entity.get::().unwrap(); 377 | let name = &member_info.user_schema.name; 378 | let client_uid = member_info.client_uid; 379 | let level = member_info.user_schema.level; 380 | let on_room_info = member_entity.get::().unwrap(); 381 | let character = on_room_info.character; 382 | let state_in_room = on_room_info.state_in_room.clone(); 383 | let team = on_room_info.team; 384 | pw 385 | .u8(1) // an user exists. 386 | .u8(character) // (character) 387 | .u8(state_in_room as u8) // (status, 1: not ready, 2: shopping, 3: ready) 388 | .u8(level) // (level) 389 | .string(&name, 13) 390 | .u8(team) // 17 (team) 391 | .u16(client_uid); // 18 (user uid?) 392 | } else { 393 | pw 394 | .u8(0) // does not exists. 395 | .pad(19); 396 | } 397 | } 398 | pw.as_vec() 399 | }; 400 | entity::session::send(entity, pkt); 401 | 402 | // Notify other members that a newcomer joined. 403 | let room = world.get(&room_entity_id).unwrap(); 404 | let pkt = { 405 | let room_entity = world.get(&room_entity_id).unwrap(); 406 | let room_info = room_entity.get::().unwrap(); 407 | let member_entity = world.get(&entity_id).unwrap(); 408 | let member_info = member_entity.get::().unwrap(); 409 | let name = &member_info.user_schema.name; 410 | let client_uid = member_info.client_uid; 411 | let level = member_info.user_schema.level; 412 | let on_room_info = member_entity.get::().unwrap(); 413 | let team = on_room_info.team; 414 | let index_in_room = 415 | room_info 416 | .members 417 | .iter() 418 | .enumerate() 419 | .find(|(_, member)| { 420 | if let Some(member_entity_id) = member { 421 | *member_entity_id == *entity_id 422 | } else { 423 | false 424 | } 425 | }) 426 | .unwrap() 427 | .0; 428 | let mut pw = PacketWriter::new(PACKET_ENTER_ROOM + 2); 429 | pw 430 | // 8 431 | .u8(index_in_room as u8) // the index of an user who has come newly. 432 | // 13 433 | .pad_to(13) 434 | .u8(level) // level 435 | .string(&name, 13) 436 | // 27 437 | .u8(team) // team 438 | .u16(client_uid); 439 | pw.as_vec() 440 | }; 441 | 442 | for member_id in entity::room::get_user_entity_ids(room) { 443 | let member_entity = world.get(&member_id).unwrap(); 444 | entity::session::send(member_entity, pkt.clone()); 445 | } 446 | 447 | Ok(()) 448 | } 449 | 450 | pub fn handle(server: &mut Server, entity_id: &EntityId, pr: PacketReader) -> Result<()> { 451 | match pr.opcode() { 452 | PACKET_CHAT => { 453 | on_chat(server, entity_id, pr) 454 | }, 455 | PACKET_ENTER_SHOP => { 456 | enter_shop(server, entity_id, pr) 457 | }, 458 | PACKET_GO_BACK => { 459 | go_back(server, entity_id, pr) 460 | }, 461 | PACKET_REQUEST_USER_LIST => { 462 | send_user_list(server, entity_id, pr) 463 | }, 464 | PACKET_REQUEST_ROOM_LIST => { 465 | send_room_list(server, entity_id, pr) 466 | }, 467 | PACKET_CREATE_ROOM => { 468 | create_room(server, entity_id, pr) 469 | }, 470 | PACKET_ENTER_ROOM => { 471 | enter_room(server, entity_id, pr) 472 | }, 473 | opcode => { 474 | println!("Unknown packet, OnLobby: {opcode}"); 475 | Ok(()) 476 | } 477 | } 478 | } -------------------------------------------------------------------------------- /server/src/server/login/handler/on_room.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use rand::prelude::SliceRandom; 19 | 20 | use crate::prelude::*; 21 | use super::*; 22 | 23 | const PACKET_CHAT: u8 = 34; 24 | const PACKET_ENTER_SHOP: u8 = 50; 25 | const PACKET_LEAVE_SHOP: u8 = 52; 26 | const PACKET_CHANGE_MAP: u8 = 66; 27 | const PACKET_CHANGE_CHAR: u8 = 69; 28 | const PACKET_CHANGE_TEAM: u8 = 71; 29 | const PACKET_GET_READY: u8 = 76; 30 | const PACKET_GET_NOT_READY: u8 = 78; 31 | const PACKET_START_GAME: u8 = 80; 32 | const PACKET_EXIT_ROOM: u8 = 95; 33 | const PACKET_KICK: u8 = 98; 34 | 35 | fn enter_shop(server: &mut Server, entity_id: &EntityId, _pr: PacketReader) -> Result<()> { 36 | let world = &mut server.world; 37 | // Change his state. 38 | { 39 | let entity = world.get_mut(entity_id).unwrap(); 40 | let on_room_info = entity.get_mut::().unwrap(); 41 | on_room_info.state_in_room = StateInRoom::Shopping; 42 | } 43 | let entity = world.get(entity_id).unwrap(); 44 | let on_room_info = entity.get::().unwrap(); 45 | let room_entity_id = on_room_info.room_entity_id; 46 | let room_entity = world.get(&room_entity_id).unwrap(); 47 | let room_info = room_entity.get::().unwrap(); 48 | let index_in_room = entity::room::get_index_in_room(room_entity, entity_id).unwrap(); 49 | 50 | // Send packets to notify that it has changed its map. 51 | let pkt = 52 | PacketWriter::new(PACKET_ENTER_SHOP + 1) 53 | .u8(index_in_room as u8) 54 | .as_vec(); 55 | for member in room_info.members { 56 | if let Some(member_entity_id) = member { 57 | let member = world.get(&member_entity_id).unwrap(); 58 | entity::session::send(member, pkt.clone()); 59 | } 60 | } 61 | 62 | Ok(()) 63 | } 64 | 65 | fn leave_shop(server: &mut Server, entity_id: &EntityId, _pr: PacketReader) -> Result<()> { 66 | let world = &mut server.world; 67 | // Change his state. 68 | { 69 | let entity = world.get_mut(entity_id).unwrap(); 70 | let on_room_info = entity.get_mut::().unwrap(); 71 | on_room_info.state_in_room = StateInRoom::NotReady; 72 | } 73 | let entity = world.get(entity_id).unwrap(); 74 | let on_room_info = entity.get::().unwrap(); 75 | let room_entity_id = on_room_info.room_entity_id; 76 | let room_entity = world.get(&room_entity_id).unwrap(); 77 | let room_info = room_entity.get::().unwrap(); 78 | let index_in_room = entity::room::get_index_in_room(room_entity, entity_id).unwrap(); 79 | 80 | // Send packets to notify that it has changed its map. 81 | let pkt = 82 | PacketWriter::new(PACKET_LEAVE_SHOP + 1) 83 | .u8(index_in_room as u8) 84 | .as_vec(); 85 | for member in room_info.members { 86 | if let Some(member_entity_id) = member { 87 | let member = world.get(&member_entity_id).unwrap(); 88 | entity::session::send(member, pkt.clone()); 89 | } 90 | } 91 | 92 | Ok(()) 93 | } 94 | 95 | fn change_map(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 96 | let map_id = pr.u8(); 97 | 98 | let world = &mut server.world; 99 | let entity = world.get(entity_id).unwrap(); 100 | let on_room_info = entity.get::().unwrap(); 101 | let room_entity_id = on_room_info.room_entity_id; 102 | let room_entity = world.get_mut(&room_entity_id).unwrap(); 103 | let room_info = room_entity.get_mut::().unwrap(); 104 | 105 | // Check if it is an owner of a room. 106 | if room_info.members[room_info.master_index].unwrap() != *entity_id { 107 | bail!("It tried to change a map although it is an an owner of a room"); 108 | } 109 | 110 | // Change a map id of the room. 111 | room_info.map = map_id; 112 | 113 | // Send packets to notify that it has changed its map. 114 | let pkt = 115 | PacketWriter::new(PACKET_CHANGE_MAP + 1) 116 | .u8(map_id) 117 | .as_vec(); 118 | for member in room_info.members { 119 | if let Some(member_entity_id) = member { 120 | let member = world.get(&member_entity_id).unwrap(); 121 | entity::session::send(member, pkt.clone()); 122 | } 123 | } 124 | 125 | Ok(()) 126 | } 127 | 128 | fn change_char(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 129 | let char_id = pr.u8(); 130 | 131 | let world = &mut server.world; 132 | // Change the character id. 133 | { 134 | let entity = world.get_mut(entity_id).unwrap(); 135 | let on_room_info = entity.get_mut::().unwrap(); 136 | on_room_info.character = char_id; 137 | } 138 | let entity = world.get(entity_id).unwrap(); 139 | let on_room_info = entity.get::().unwrap(); 140 | let room_entity_id = on_room_info.room_entity_id; 141 | let room_entity = world.get(&room_entity_id).unwrap(); 142 | let room_info = room_entity.get::().unwrap(); 143 | let index_in_room = entity::room::get_index_in_room(room_entity, entity_id).unwrap(); 144 | 145 | // Send packets to notify that it has changed its map. 146 | let pkt = 147 | PacketWriter::new(PACKET_CHANGE_CHAR + 1) 148 | .u8(index_in_room as u8) 149 | .u8(char_id) 150 | .as_vec(); 151 | for member in room_info.members { 152 | if let Some(member_entity_id) = member { 153 | let member = world.get(&member_entity_id).unwrap(); 154 | entity::session::send(member, pkt.clone()); 155 | } 156 | } 157 | 158 | Ok(()) 159 | } 160 | 161 | fn change_team(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 162 | let team_id = pr.u8(); 163 | 164 | let world = &mut server.world; 165 | // Change the team. 166 | { 167 | let entity = world.get_mut(entity_id).unwrap(); 168 | let on_room_info = entity.get_mut::().unwrap(); 169 | on_room_info.team = team_id; 170 | } 171 | let entity = world.get(entity_id).unwrap(); 172 | let on_room_info = entity.get::().unwrap(); 173 | let room_entity_id = on_room_info.room_entity_id; 174 | let room_entity = world.get(&room_entity_id).unwrap(); 175 | let room_info = room_entity.get::().unwrap(); 176 | let index_in_room = entity::room::get_index_in_room(room_entity, entity_id).unwrap(); 177 | 178 | // Send packets to notify that it has changed its map. 179 | let pkt = 180 | PacketWriter::new(PACKET_CHANGE_TEAM + 1) 181 | .u8(index_in_room as u8) 182 | .u8(team_id) 183 | .as_vec(); 184 | for member in room_info.members { 185 | if let Some(member_entity_id) = member { 186 | let member = world.get(&member_entity_id).unwrap(); 187 | entity::session::send(member, pkt.clone()); 188 | } 189 | } 190 | 191 | Ok(()) 192 | } 193 | 194 | fn get_ready(server: &mut Server, entity_id: &EntityId, _pr: PacketReader) -> Result<()> { 195 | let world = &mut server.world; 196 | // Change his readiness. 197 | { 198 | let entity = world.get_mut(entity_id).unwrap(); 199 | let on_room_info = entity.get_mut::().unwrap(); 200 | on_room_info.state_in_room = StateInRoom::Ready; 201 | } 202 | let entity = world.get(entity_id).unwrap(); 203 | let on_room_info = entity.get::().unwrap(); 204 | let room_entity_id = on_room_info.room_entity_id; 205 | let room_entity = world.get(&room_entity_id).unwrap(); 206 | let room_info = room_entity.get::().unwrap(); 207 | let index_in_room = entity::room::get_index_in_room(room_entity, entity_id).unwrap(); 208 | 209 | // Send packets to notify that it has changed its map. 210 | let pkt = 211 | PacketWriter::new(PACKET_GET_READY + 1) 212 | .u8(index_in_room as u8) 213 | .as_vec(); 214 | for member in room_info.members { 215 | if let Some(member_entity_id) = member { 216 | let member = world.get(&member_entity_id).unwrap(); 217 | entity::session::send(member, pkt.clone()); 218 | } 219 | } 220 | 221 | Ok(()) 222 | } 223 | 224 | fn get_not_ready(server: &mut Server, entity_id: &EntityId, _pr: PacketReader) -> Result<()> { 225 | let world = &mut server.world; 226 | // Change his readiness. 227 | { 228 | let entity = world.get_mut(entity_id).unwrap(); 229 | let on_room_info = entity.get_mut::().unwrap(); 230 | on_room_info.state_in_room = StateInRoom::NotReady; 231 | } 232 | let entity = world.get(entity_id).unwrap(); 233 | let on_room_info = entity.get::().unwrap(); 234 | let room_entity_id = on_room_info.room_entity_id; 235 | let room_entity = world.get(&room_entity_id).unwrap(); 236 | let room_info = room_entity.get::().unwrap(); 237 | let index_in_room = entity::room::get_index_in_room(room_entity, entity_id).unwrap(); 238 | 239 | // Send packets to notify that it has changed its map. 240 | let pkt = 241 | PacketWriter::new(PACKET_GET_NOT_READY + 1) 242 | .u8(index_in_room as u8) 243 | .as_vec(); 244 | for member in room_info.members { 245 | if let Some(member_entity_id) = member { 246 | let member = world.get(&member_entity_id).unwrap(); 247 | entity::session::send(member, pkt.clone()); 248 | } 249 | } 250 | 251 | Ok(()) 252 | } 253 | 254 | fn start_game(server: &mut Server, entity_id: &EntityId, _pr: PacketReader) -> Result<()> { 255 | let world = &mut server.world; 256 | 257 | let room_eid = { 258 | let entity = 259 | world 260 | .get(entity_id)?; 261 | let on_room_info = 262 | entity 263 | .get::()?; 264 | on_room_info.room_entity_id 265 | }; 266 | 267 | { 268 | let room_entity_id = room_eid; 269 | let room_info = 270 | world 271 | .get(&room_entity_id)? 272 | .get::()?; 273 | let map_id = match room_info.map { 274 | MAP_RANDOM_ID => rand::random::() % MAX_MAP_ID, 275 | id => id 276 | }; 277 | let initial_positions = { 278 | let mut rng = rand::thread_rng(); 279 | let mut positions: Vec = (0..16).collect(); 280 | positions.shuffle(&mut rng); 281 | positions[0..8].to_vec() 282 | }; 283 | 284 | // TODO 285 | let mut pw = PacketWriter::new(PACKET_START_GAME + 1); 286 | pw 287 | .u8(map_id) 288 | .pad_to(12); 289 | for i in 0..8 { 290 | if let Some(member_entity_id) = room_info.members[i] { 291 | let member = 292 | world 293 | .get(&member_entity_id)?; 294 | let on_room_info = 295 | member 296 | .get::()?; 297 | let char_id = match on_room_info.character { 298 | CHAR_RANDOM_ID => rand::random::() % MAX_CHAR_ID, 299 | id => id 300 | }; 301 | let initial_pos = initial_positions[i]; 302 | pw 303 | .u8(0) // TODO: unknown. 304 | .u8(initial_pos as u8) // starting position ? 305 | .u8(char_id) // character id 306 | .u8(0) // TODO: Unknown, maybe dummy. 307 | .u32(0); // TODO: unknown. 308 | } else { 309 | pw.pad(8); 310 | } 311 | } 312 | 313 | let pkt = pw.as_vec(); 314 | 315 | { 316 | let room = 317 | world 318 | .get(&room_entity_id)?; 319 | entity::room::send_to_all(world, room, &pkt); 320 | } 321 | } 322 | 323 | { 324 | let room = 325 | world 326 | .get(&room_eid)?; 327 | let room_info = 328 | room 329 | .get::()?; 330 | 331 | let mut effective_indices = vec!(); 332 | for i in 0..8 { 333 | if room_info 334 | .members[i] 335 | .is_some() { 336 | effective_indices.push(i); 337 | } 338 | } 339 | 340 | // Add a GameInfo to the Room entity. 341 | let turn_table = { // get_turn_table(); 342 | // The order is randomized since it is the first cycle. 343 | let mut members_by_team: Vec> = (0..8) 344 | .map(|_| vec!()) 345 | .collect(); 346 | // 1. By-Team order. 347 | // 2. In-team order. 348 | for idx in effective_indices { 349 | let member_eid = room_info.members[idx].unwrap(); 350 | let entity = world 351 | .get(&member_eid)?; 352 | let team_id = entity 353 | .get::()? 354 | .team; 355 | members_by_team[team_id as usize].push(idx); 356 | } 357 | 358 | let mut rng = rand::thread_rng(); 359 | // Shuffle by team. 360 | members_by_team.shuffle(&mut rng); 361 | // Shuffle by player in each team, and ignore an empty team. 362 | for players in members_by_team.iter_mut() { 363 | if !players.is_empty() { 364 | players.shuffle(&mut rng); 365 | } 366 | } 367 | 368 | let mut turn_table = vec!(); 369 | 370 | loop { 371 | let mut has_pushed = false; 372 | for team_id in 0..8 { 373 | let members = &mut members_by_team[team_id]; 374 | if let Some(idx_in_room) = members.pop() { 375 | turn_table.push(idx_in_room); 376 | has_pushed = true; 377 | } 378 | } 379 | 380 | if !has_pushed { 381 | break; 382 | } 383 | } 384 | 385 | turn_table 386 | }; 387 | 388 | let game_info = GameInfo { 389 | room_eid: room_eid, 390 | load_table: [false; 8], 391 | turn_table: turn_table, 392 | turn_index: 0, 393 | player_infos: [PlayerInfo::default(); 8] 394 | }; 395 | 396 | let room = 397 | world 398 | .get_mut(&room_eid)?; 399 | 400 | room.push(game_info); 401 | 402 | let room_info = 403 | room 404 | .get_mut::()?; 405 | 406 | // Set room's state to playing. 407 | room_info.is_playing = true; 408 | } 409 | 410 | // Set all players' state to OnGame. 411 | { 412 | let ids = { 413 | let room = world 414 | .get(&room_eid)?; 415 | entity::room::get_user_entity_ids(room) 416 | }; 417 | ids 418 | .iter() 419 | .for_each(|id| { 420 | world 421 | .get_mut(&id) 422 | .unwrap() 423 | .push(ClientState::OnGame); 424 | }); 425 | } 426 | 427 | // Start a loop for loading resources in clients. 428 | on_game::loop_loading(world, room_eid); 429 | 430 | log!("[{}] Start a game", room_eid); 431 | 432 | Ok(()) 433 | } 434 | 435 | /// This can be called generally for handling exit. 436 | /// Note that it can be called even when it's playing a game. 437 | pub fn handle_exit(server: &mut Server, entity_id: &EntityId) { 438 | let room_entity_id = server 439 | .world 440 | .get(entity_id) 441 | .unwrap() 442 | .get::() 443 | .unwrap() 444 | .room_entity_id; 445 | 446 | // Check if it is in-game. 447 | { 448 | let is_playing = server 449 | .world 450 | .get(&room_entity_id) 451 | .unwrap() 452 | .get::() 453 | .unwrap() 454 | .is_playing; 455 | 456 | if is_playing { 457 | on_game::handle_exit(server, entity_id); 458 | } 459 | } 460 | 461 | { 462 | let world = &mut server.world; 463 | let room_entity = world 464 | .get_mut(&room_entity_id) 465 | .unwrap(); 466 | 467 | let found_index = { 468 | let room_info = room_entity.get_mut::().unwrap(); 469 | 470 | let found_index = { 471 | let mut found_index = None; 472 | for (i, place) in room_info.members.iter_mut().enumerate() { 473 | if let Some(user_entity_id) = place { 474 | if *user_entity_id == *entity_id { 475 | found_index = Some(i); 476 | break; 477 | } 478 | } 479 | } 480 | found_index 481 | }; 482 | 483 | if found_index.is_none() { 484 | panic!("A place can not be empty"); 485 | } 486 | 487 | let found_index = found_index.unwrap(); 488 | room_info.members[found_index] = None; 489 | 490 | found_index 491 | }; 492 | 493 | // (1) Check if it is an empty room now. 494 | if entity::room::get_user_entity_ids(room_entity).is_empty() { 495 | // Remove the room. 496 | world.remove(&room_entity_id); 497 | } else { 498 | // (2) Check if he was the owner of the room. 499 | let room_info = room_entity.get_mut::().unwrap(); 500 | let mut master_index = room_info.master_index; 501 | if master_index == found_index { 502 | // If so, then give his ownership to the other member. 503 | for (i, place) in room_info.members.iter().enumerate() { 504 | if place.is_some() { 505 | master_index = i; 506 | } 507 | } 508 | room_info.master_index = master_index; 509 | } 510 | 511 | // (3) Notify the other members that he has left the room. 512 | let leaver = found_index as u8; 513 | let master = master_index as u8; 514 | let pkt = packet::room_notify_leaver_and_master(leaver, master); 515 | for entity_id in entity::room::get_user_entity_ids(room_entity) { 516 | let member = world.get(&entity_id).unwrap(); 517 | entity::session::send(member, pkt.clone()); 518 | } 519 | } 520 | } 521 | 522 | // Change entity's state. 523 | { 524 | let entity = server 525 | .world 526 | .get_mut(entity_id) 527 | .unwrap(); 528 | entity.push(ClientState::OnLobby); 529 | entity.remove::(); // Remove OnRoomInfo. 530 | } 531 | } 532 | 533 | fn kick(server: &mut Server, entity_id: &EntityId, mut pr: PacketReader) -> Result<()> { 534 | let index_in_room_to_kick = pr.u8() as usize; 535 | 536 | let world = &mut server.world; 537 | let entity = world.get(entity_id).unwrap(); 538 | let on_room_info = entity.get::().unwrap(); 539 | let room_entity_id = on_room_info.room_entity_id; 540 | let room_entity = world.get(&room_entity_id).unwrap(); 541 | let room_info = room_entity.get::().unwrap(); 542 | let entity_id_to_kick = room_info.members[index_in_room_to_kick].unwrap(); 543 | 544 | // Make the client leave the room. 545 | handle_exit(server, &entity_id_to_kick); 546 | 547 | // Send an exit packet. 548 | let world = &mut server.world; 549 | let entity = world.get_mut(&entity_id_to_kick).unwrap(); 550 | let pkt = packet::room_leave_to_lobby(); 551 | entity::session::send(entity, pkt); 552 | 553 | Ok(()) 554 | } 555 | 556 | fn exit_room(server: &mut Server, entity_id: &EntityId, _pr: PacketReader) -> Result<()> { 557 | handle_exit(server, entity_id); 558 | 559 | // Send an exit packet. 560 | let world = &mut server.world; 561 | let entity = world.get_mut(entity_id).unwrap(); 562 | let pkt = packet::room_leave_to_lobby(); 563 | entity::session::send(entity, pkt); 564 | 565 | Ok(()) 566 | } 567 | 568 | pub fn handle(server: &mut Server, entity_id: &EntityId, pr: PacketReader) -> Result<()> { 569 | match pr.opcode() { 570 | PACKET_CHAT => { 571 | // Re-use it. 572 | on_lobby::on_chat(server, entity_id, pr) 573 | }, 574 | PACKET_ENTER_SHOP => { 575 | enter_shop(server, entity_id, pr) 576 | }, 577 | PACKET_LEAVE_SHOP => { 578 | leave_shop(server, entity_id, pr) 579 | }, 580 | PACKET_CHANGE_MAP => { 581 | change_map(server, entity_id, pr) 582 | }, 583 | PACKET_CHANGE_CHAR => { 584 | change_char(server, entity_id, pr) 585 | }, 586 | PACKET_CHANGE_TEAM => { 587 | change_team(server, entity_id, pr) 588 | }, 589 | PACKET_GET_READY => { 590 | get_ready(server, entity_id, pr) 591 | }, 592 | PACKET_GET_NOT_READY => { 593 | get_not_ready(server, entity_id, pr) 594 | }, 595 | PACKET_START_GAME => { 596 | start_game(server, entity_id, pr) 597 | }, 598 | PACKET_EXIT_ROOM => { 599 | exit_room(server, entity_id, pr) 600 | }, 601 | PACKET_KICK => { 602 | kick(server, entity_id, pr) 603 | }, 604 | opcode => { 605 | println!("Unknown packet, OnRoom: {opcode}"); 606 | Ok(()) 607 | } 608 | } 609 | } -------------------------------------------------------------------------------- /server/src/server/login/handler/packet.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use crate::{prelude::*}; 19 | use super::*; 20 | 21 | pub enum ErrorKind { 22 | FullOfClients = 5001, 23 | InvalidId = 7001, 24 | NoResponse = 7003, 25 | CantFindUserInfo = 7005, 26 | Banned = 7007, 27 | Online = 7010, 28 | FullOfClientsInRoom = 7011, 29 | FullOfRooms = 7012, 30 | NoRoom = 7013, 31 | NotEnoughLevel = 7014, 32 | UnbalancedTeamNumber = 7016, 33 | BoardNotReady = 7021, 34 | SameNickname = 7027, 35 | NotEnoughTicketToChangeNickname = 7028, 36 | DupNickname = 7029, 37 | InvalidAccInfo = 8003, 38 | } 39 | 40 | pub fn error(err_kind: ErrorKind) -> Vec { 41 | PacketWriter::new(0) 42 | .u32(err_kind as u32) 43 | .as_vec() 44 | } 45 | 46 | pub fn chat(is_admin: bool, is_whisper: bool, name: &str, text: &str) -> Vec { 47 | PacketWriter::new(34) 48 | .u8(if_else(is_admin, 1, 0)) 49 | .u8(if_else(is_whisper, 1, 0)) 50 | .string(text, 222) 51 | .string(name, 13) 52 | .as_vec() 53 | } 54 | 55 | pub fn chat_in_game( 56 | unknown: u8, 57 | msg_kind: u8, 58 | name: &str, 59 | text: &str, 60 | emoticon: Option, // for in-game 61 | index_in_room: Option // for in-game 62 | ) -> Vec { 63 | PacketWriter::new(34) 64 | .u8(unknown) 65 | .u8(msg_kind) // type (1: whisper, 2: team?, 0: all?) 66 | .string(text, 206) 67 | .u32(emoticon.unwrap_or(0xffffffff)) // TODO: 0xff..ff: for in-game chat? / else: emoticon 68 | .pad(12) 69 | .string(name, 13) 70 | .pad(31) 71 | // 276 72 | .u8(index_in_room.unwrap_or_default()) 73 | .as_vec() 74 | } 75 | 76 | pub fn room_list(world: &World, rooms: &Vec<&Entity>) -> Vec { 77 | let mut pw = PacketWriter::new(58); 78 | pw.u8(rooms.len() as u8); 79 | for i in 0..8 { 80 | if i >= rooms.len() { 81 | pw.pad(46 + 14 * 8); // 8: max players 82 | continue; 83 | } 84 | let room = rooms[i]; 85 | let state = entity::room::get_room_state(room).unwrap(); 86 | let info = room.get::().unwrap(); 87 | pw 88 | .u8(state as u8) // 1: waiting, 2: full, else: playing 89 | .string(&info.name, 24+1) 90 | .string(&info.password, 12+1) 91 | .u8(info.max_clients) // 39 - 1 2 4 8 - max players 92 | .u8(info.level_condition) // 40 - (1: same, 2: upper, 3: lower) 93 | .u8(info.level_base) // 41 - base level 94 | .u8(if_else(info.allows_item, 1 << 0, 0) | if_else(info.allows_evol, 1 << 1, 0)) 95 | .u8(info.map) // 43 - map (0~) 96 | .u8(info.room_uid) // 44 - room uid 97 | .u8(0); // unknown 98 | // 46 - players in a room 99 | for member in info.members { 100 | if let Some(k) = member { 101 | if let Ok(entity) = world.get(&k) { 102 | let user_info = entity.get::().unwrap(); 103 | pw 104 | .string(&user_info.user_schema.name, 13) 105 | .u8(user_info.user_schema.level); 106 | } else { 107 | pw.pad(14); 108 | } 109 | } else { 110 | pw.pad(14); 111 | } 112 | } 113 | }; 114 | pw.as_vec() 115 | } 116 | 117 | /// It's used to notify the room members when someone exited. 118 | pub fn room_notify_leaver_and_master(leaver_index: u8, master_index: u8) -> Vec { 119 | PacketWriter::new(96) 120 | .u8(leaver_index) 121 | .u8(master_index) 122 | .as_vec() 123 | } 124 | 125 | /// 126 | pub fn room_leave_to_lobby() -> Vec { 127 | // When a leaver index is -1 (0xff), then the client exits into the lobby. 128 | room_notify_leaver_and_master(u8::MAX, 0) 129 | } -------------------------------------------------------------------------------- /server/src/server/login/mod.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | mod handler; 19 | mod component; 20 | mod conn; 21 | mod entity; 22 | mod timer; 23 | 24 | use crate::{prelude::*, server::login::{entity::EntityKind, conn::MsgToConnSender, handler::packet::ErrorKind}}; 25 | //use legion::serialize::WorldDeserializer; 26 | use serde_json::Value; 27 | //use legion::{storage::IntoComponentSource, world::EntryRef}; 28 | use tokio::{net::{TcpListener, TcpStream}, sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}}; 29 | use std::{net::SocketAddr, collections::{HashSet}, time::{Duration, Instant}, thread, rc::Rc}; 30 | use conn::Conn; 31 | use component::*; 32 | 33 | use self::conn::MsgToConn; 34 | 35 | const LOGIN_PORT: u16 = 9874; 36 | const FPS: u64 = 20; 37 | const MILLI_PER_FRAME: u64 = 1000 / FPS; 38 | 39 | #[derive(Debug)] 40 | pub enum MsgToServer { 41 | /// A new connection. 42 | NewConn(TcpStream, SocketAddr), 43 | /// Received a packet. 44 | OnPacketReceived(EntityId, PacketReader), 45 | /// Disconnection. 46 | OnDisconnected(EntityId), 47 | /// Inter-server message. 48 | /// JSON Value / Request port 49 | InterRequest(Value, UnboundedSender) 50 | } 51 | 52 | pub type MsgToServerSender = UnboundedSender; 53 | 54 | pub struct Server { 55 | pub world: World, 56 | pub db: Connection, 57 | config: Config, 58 | msg_rx: UnboundedReceiver, 59 | msg_tx: UnboundedSender, 60 | } 61 | 62 | impl Server { 63 | pub fn new(config: Config, db: Connection) -> Self { 64 | let (tx, rx) = unbounded_channel(); 65 | let mut server = Self { 66 | world: World::default(), 67 | config: config, 68 | db: db, 69 | msg_rx: rx, 70 | msg_tx: tx, 71 | }; 72 | server.add_timers(); 73 | server 74 | } 75 | 76 | /// Add timers to handle periodic events such as checking disconnections and 77 | /// game-related stuffs. 78 | fn add_timers(&mut self) { 79 | timer::register_timers(&mut self.world); 80 | } 81 | 82 | async fn recv_loop(conn_tx: UnboundedSender) -> anyhow::Result<()> { 83 | let addr = format!("0.0.0.0:{LOGIN_PORT}"); 84 | let listener = TcpListener::bind(addr).await?; 85 | println!("Login server listening on {LOGIN_PORT}"); 86 | loop { 87 | let (stream, sock_addr) = listener.accept().await?; 88 | let new_conn = MsgToServer::NewConn(stream, sock_addr); 89 | conn_tx.send(new_conn)?; 90 | } 91 | } 92 | 93 | async fn start_recv(&self) { 94 | tokio::spawn(Self::recv_loop(self.msg_tx.clone())); 95 | } 96 | 97 | fn register_connection(&mut self, stream: TcpStream, addr: SocketAddr) { 98 | let world = &mut self.world; 99 | let entity = entity::session::create(world, addr); 100 | let entity_id = entity.id(); 101 | let conn = Conn::new(entity_id, stream, self.msg_tx.clone()); 102 | entity.push(conn.get_conn_tx()); 103 | conn.start(); 104 | 105 | log!("[{}] A new connection.", entity_id); 106 | } 107 | 108 | fn handle_packet(&mut self, eid: EntityId, pr: PacketReader) { 109 | log!("[{}] Packet received.", eid); 110 | if self.is_entity_offline(&eid) { // It is already disconnected. 111 | // Ignore the message. 112 | } 113 | else if let Err(err) = handler::handle(self, eid, pr) { 114 | // Disconnect? 115 | // TODO 116 | println!("An error should be resolved: {err}"); 117 | } else { // Ok. 118 | if let Ok(entity) = self.world.get_mut(&eid) { 119 | entity.push(PongInfo(true)); 120 | } 121 | } 122 | } 123 | 124 | /// We handle disconnection here in the end. 125 | /// (Disconnection) -> (Connection) -> (Server: here) 126 | fn handle_disconnection(&mut self, entity_id: EntityId) { 127 | // 1. Check if it is in a room or in-game. 128 | let state = 129 | self 130 | .world 131 | .get(&entity_id) 132 | .unwrap() 133 | .get::() 134 | .unwrap() 135 | .to_owned(); 136 | if state == ClientState::OnRoom || state == ClientState::OnGame { 137 | handler::on_room::handle_exit(self, &entity_id); 138 | } 139 | 140 | // 2. Release every resource attached to the entity. 141 | self.world.remove(&entity_id); 142 | 143 | log!("[{}] Disconnection.", entity_id); 144 | } 145 | 146 | fn is_entity_offline(&self, eid: &EntityId) -> bool { 147 | self 148 | .world 149 | .get(eid) 150 | .is_err() 151 | } 152 | 153 | #[inline] 154 | fn handle_message(&mut self) { 155 | while let Ok(msg) = self.msg_rx.try_recv() { 156 | match msg { 157 | // A new connection. 158 | MsgToServer::NewConn(stream, addr) => { 159 | self.register_connection(stream, addr); 160 | }, 161 | // Packet received. 162 | MsgToServer::OnPacketReceived(eid, pr) => { 163 | self.handle_packet(eid, pr); 164 | }, 165 | // Disconnected. 166 | MsgToServer::OnDisconnected(entity) => { 167 | self.handle_disconnection(entity); 168 | }, 169 | // This message can be sent from the other server such as a web 170 | // server. 171 | MsgToServer::InterRequest(msg, tx) => { 172 | self.handle_inter_request(msg, tx); 173 | } 174 | } 175 | } 176 | } 177 | 178 | #[inline] 179 | fn handle_timer(&mut self) { 180 | let timer_list = { 181 | let mut timer_list = vec!(); 182 | self 183 | .world 184 | .select(|entity| { 185 | let kind = entity 186 | .get::() 187 | .unwrap(); 188 | *kind == entity::EntityKind::Timer 189 | }) 190 | .iter() 191 | .for_each(|timer| { 192 | let timer_eid = timer.id(); 193 | timer_list.push(timer_eid); 194 | }); 195 | timer_list 196 | }; 197 | 198 | for timer_eid in timer_list.iter() { 199 | entity::timer::tick(self, timer_eid); 200 | } 201 | } 202 | 203 | #[inline] 204 | fn sleep(&self, tick: &Duration, time: Instant) { 205 | let duration = time.elapsed(); 206 | if duration >= *tick { 207 | log!("There is too much traffic right now."); 208 | } else { 209 | thread::sleep(tick.saturating_sub(duration)); 210 | } 211 | } 212 | 213 | /// The main loop. 214 | /// It never terminates. 215 | /// TODO: Make priority between callbacks. 216 | fn handle(&mut self) -> Option<()> { 217 | let tick = Duration::from_millis(MILLI_PER_FRAME); 218 | loop { 219 | let time = std::time::Instant::now(); 220 | self.handle_message(); 221 | self.handle_timer(); 222 | self.sleep(&tick, time); 223 | } 224 | } 225 | 226 | pub async fn run(&mut self) { 227 | self.start_recv().await; 228 | self.handle(); 229 | } 230 | 231 | pub fn get_server_tx(&self) -> MsgToServerSender { 232 | self.msg_tx.clone() 233 | } 234 | 235 | fn add_new_account(&self, name: String, id: String, pw: String) -> String { 236 | let db = &self.db; 237 | let user_tbl = db.table(USER_TBL).unwrap(); 238 | let config = &self.config; 239 | 240 | // Check the uniqueness of id & name. 241 | { 242 | let users: Vec = user_tbl 243 | .iter() 244 | .filter(field("id").eq(&id)) 245 | .data(&db) 246 | .unwrap(); 247 | if users.is_empty() == false { 248 | return "id_dup".into(); 249 | } 250 | } 251 | { 252 | let users: Vec = user_tbl 253 | .iter() 254 | .filter(field("name").eq(&name)) 255 | .data(&db) 256 | .unwrap(); 257 | if users.is_empty() == false { 258 | return "name_dup".into(); 259 | } 260 | } 261 | 262 | let user_schema = UserSchema { 263 | id: id.clone(), 264 | pw: hex::encode(hash::from_str(&pw)), 265 | name: name.clone(), 266 | level: config.user.initial_level, 267 | is_female: false, 268 | money: config.user.initial_money, 269 | items: vec![0; ITEM_COUNT], 270 | exps: vec![0; EXP_COUNT], 271 | is_admin: false, 272 | is_muted: false, 273 | is_banned: false, 274 | setting: SettingSchema { 275 | key_binding: 1, 276 | bgm_volume: 49, 277 | bgm_mute: false, 278 | bgm_echo: true, 279 | sound_volume: 49, 280 | sound_mute: false, 281 | macros: vec!["".into(); MACRO_COUNT] 282 | } 283 | }; 284 | user_tbl 285 | .insert(user_schema, &db) 286 | .expect("Failed to insert an account into database"); 287 | 288 | println!("Added an account: {id} ({name})"); 289 | return "".into(); 290 | } 291 | 292 | fn handle_inter_request(&mut self, msg: Value, tx: UnboundedSender) { 293 | //println!("{}", msg.to_string()); 294 | let opcode = msg["opcode"].as_str().unwrap(); 295 | let msg_id = msg["msg_id"].as_u64().unwrap(); 296 | let response_msg = match opcode { 297 | "ping" => { 298 | let mut response_msg = Value::default(); 299 | response_msg["msg_id"] = Value::from(msg_id); 300 | Some(response_msg) 301 | }, 302 | "new_account" => { 303 | let id = msg["id"].as_str().unwrap().into(); 304 | let name = msg["name"].as_str().unwrap().into(); 305 | let pw = msg["pw"].as_str().unwrap().into(); 306 | let response = self.add_new_account(name, id, pw); 307 | let mut response_msg = Value::default(); 308 | response_msg["result"] = Value::from(response); 309 | response_msg["msg_id"] = Value::from(msg_id); 310 | Some(response_msg) 311 | }, 312 | _ => { 313 | println!("Invalid opcode: {opcode}"); 314 | None 315 | } 316 | }; 317 | 318 | if let Some(msg) = response_msg { 319 | tx 320 | .send(msg) 321 | .expect("Failed to send a response message"); 322 | } 323 | } 324 | } -------------------------------------------------------------------------------- /server/src/server/login/timer.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use super::*; 19 | 20 | /// We register server-wide timers here. 21 | /// Currently we use ping-pong checker only. 22 | pub fn register_timers(world: &mut World) { 23 | add_ping_pong_checker(world); 24 | } 25 | 26 | fn add_ping_pong_checker(world: &mut World) { 27 | let duration = Duration::from_secs(60 * 10); // 10 mins. 28 | register_timer!(world, duration, move |server: &mut Server, _timer_eid: &EntityId| -> bool { 29 | for entity in server.world.select_mut(|entity| *entity.get::().unwrap() == EntityKind::Client) { 30 | if let Ok(pong_info) = entity.get::() { 31 | if let PongInfo(false) = pong_info { 32 | // Send a packet to let the client know his leaving. 33 | let pkt = handler::packet::error(ErrorKind::NoResponse); 34 | entity::session::send(entity, pkt); 35 | 36 | // Make the connection shut down. 37 | let _ = entity 38 | .get::() 39 | .unwrap() 40 | .send(MsgToConn::Shutdown); 41 | continue; 42 | } 43 | } 44 | entity.push(PongInfo(false)); // Reset the state. 45 | } 46 | return true; 47 | }); 48 | } -------------------------------------------------------------------------------- /server/src/server/mod.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | pub mod login; 19 | pub mod status; -------------------------------------------------------------------------------- /server/src/server/status/mod.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use std::net::{SocketAddr, IpAddr, Ipv4Addr}; 19 | use std::sync::Arc; 20 | 21 | use tokio::net::UdpSocket; 22 | use tokio::sync::mpsc::unbounded_channel; 23 | 24 | use crate::prelude::*; 25 | use crate::server::login::MsgToServer; 26 | 27 | use super::login::MsgToServerSender; 28 | 29 | const STATUS_PORT: u16 = 9874; 30 | 31 | async fn parse_packet(buf: Vec, n: usize) -> anyhow::Result> { 32 | if n < HEADER_SIZE + TAIL_SIZE { 33 | bail!("Packet too short"); 34 | } 35 | let header = &buf[0 .. HEADER_SIZE]; 36 | let header = crypt::decode(header)?; 37 | let body_size = read_u16(&header, 0) as usize; 38 | let to_read = body_size + TAIL_SIZE; 39 | if n < HEADER_SIZE + to_read { 40 | bail!("The size field is too long"); 41 | } 42 | let body = &buf[HEADER_SIZE .. HEADER_SIZE + to_read ]; 43 | let body = crypt::transfer(body); 44 | Ok(body) 45 | } 46 | 47 | async fn send_to(sock: Arc, addr: SocketAddr, pkt: &[u8]) { 48 | let _ = sock.send_to(pkt, addr).await; 49 | } 50 | 51 | async fn handle_packet(sock: Arc, data: &[u8], addr: SocketAddr) { 52 | let mut pr = PacketReader::new(data); 53 | let opcode = pr.opcode(); 54 | let opcode_to_reply = opcode + 1; 55 | match opcode { 56 | 1 => { // TODO: Support multiple servers. 57 | let ping = pr.u8(); 58 | let pong = ping; 59 | let curr = 0;//global::user::get_user_count().await; 60 | let max = MAX_USERS as u16; 61 | let avail = (MAX_USERS - curr) as u16; 62 | let mut pw = PacketWriter::new(opcode_to_reply); 63 | let pkt = pw 64 | .u8(pong) 65 | .u8(1) // the number of servers 66 | .u16(401) // (1) server uid 67 | .u16(avail) // (2) available 68 | .u16(max) // (3) the max number of clients 69 | .as_vec(); 70 | send_to(sock, addr, &pkt).await; 71 | }, 72 | 46 => { // Right after being authenticated in the login server. 73 | let mut pw = PacketWriter::new(opcode_to_reply); 74 | let ping = pr.u8(); 75 | let pong = ping; 76 | let is_send_permitted = true; 77 | pw 78 | .u8(pong) 79 | .u8(if_else(is_send_permitted, 1, 0)) 80 | .u16(0); // TODO: count 81 | let pkt = pw.as_vec(); 82 | send_to(sock, addr, &pkt).await; 83 | } 84 | _ => { 85 | println!("Invalid status packet: {opcode}"); 86 | } 87 | } 88 | } 89 | 90 | const INTER_MSG_PREFIX: &str = "!INTER_MSG"; 91 | const DELIMITER: u8 = 0xff; 92 | 93 | fn generate_inter_packet(pw: String, msg: String) -> Vec { 94 | let mut ret = vec!(); 95 | 96 | ret.append(&mut INTER_MSG_PREFIX.as_bytes().to_vec()); 97 | ret.push(DELIMITER); 98 | ret.append(&mut pw.as_bytes().to_vec()); 99 | ret.push(DELIMITER); 100 | ret.append(&mut msg.as_bytes().to_vec()); 101 | 102 | ret 103 | } 104 | 105 | // TODO: Do not implement handling of inter messages here. 106 | // Instead, create a new UDP port in the login server. 107 | fn try_get_inter_msg(pw: String, pkt: &Vec) -> Option { 108 | /* 109 | [Inter-message structure] 110 | {{PREFIX}} 111 | {{PASSWORD}} 112 | {{CONTENTS}} 113 | 114 | Note that we use '\n' (0xff) as a delimiter. 115 | */ 116 | let mut it = pkt.split(|&v| v == DELIMITER); 117 | let mut f = || -> Option<(String, String, String)> { 118 | let prefix = match String::from_utf8(it.next()?.to_vec()) { 119 | Ok(s) => s, 120 | _ => return None, 121 | }; 122 | let password = match String::from_utf8(it.next()?.to_vec()) { 123 | Ok(s) => s, 124 | _ => return None, 125 | }; 126 | let contents = match String::from_utf8(it.next()?.to_vec()) { 127 | Ok(s) => s, 128 | _ => return None, 129 | }; 130 | Some((prefix, password, contents)) 131 | }; 132 | if let Some((prefix, password, contents)) = f() { 133 | if &prefix == INTER_MSG_PREFIX && password == pw { 134 | Some(contents) 135 | } else { 136 | None 137 | } 138 | } else { 139 | None 140 | } 141 | } 142 | 143 | async fn handle(config: Config, login_tx: MsgToServerSender) -> anyhow::Result<()> { 144 | let addr = format!("0.0.0.0:{STATUS_PORT}"); 145 | let server_sock = UdpSocket::bind(addr).await?; 146 | let server_sock = Arc::new(server_sock); 147 | let mut buf = [0u8; 4096]; 148 | let password = &config.server.password; 149 | println!("Status server listening on {STATUS_PORT}"); 150 | loop { 151 | let server_sock = server_sock.clone(); 152 | match server_sock.recv_from(&mut buf).await { 153 | Ok((n, addr)) => { 154 | let vec = Vec::from(buf)[..n].to_vec(); 155 | let password = password.clone(); 156 | let login_tx = login_tx.clone(); 157 | tokio::spawn(async move { 158 | if let Some(msg) = try_get_inter_msg(password.clone(), &vec) { 159 | let json: serde_json::Value = serde_json::from_str(&msg).unwrap(); 160 | let (msg_tx, mut msg_rx) = unbounded_channel(); 161 | login_tx 162 | .send(MsgToServer::InterRequest(json, msg_tx)) 163 | .expect("InterRequest failed"); 164 | let response_msg = match msg_rx.recv().await { 165 | Some(response) => response.to_string(), 166 | None => "{}".into() 167 | }; 168 | let response_pkt = generate_inter_packet(password, response_msg); 169 | let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 9875); // hacky. 170 | send_to(server_sock, addr, &response_pkt).await; 171 | } 172 | else { 173 | match parse_packet(vec, n).await { 174 | Ok(data) => { 175 | handle_packet(server_sock, &data, addr).await; 176 | }, 177 | _ => { 178 | // TODO: Check if it's a DoS attack. 179 | } 180 | } 181 | } 182 | }); 183 | }, 184 | Err(error) => { 185 | bail!(error); 186 | } 187 | } 188 | } 189 | } 190 | 191 | pub async fn run(config: Config, login_tx: MsgToServerSender) { 192 | tokio::spawn(handle(config, login_tx)); 193 | } -------------------------------------------------------------------------------- /server/src/util/config.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use crate::prelude::*; 19 | 20 | use rand::distributions::{Alphanumeric, DistString}; 21 | use serde::{Deserialize}; 22 | use ::config; 23 | 24 | #[derive(Clone, Deserialize)] 25 | pub struct Config { 26 | pub server: ConfigServer, 27 | pub user: ConfigUser, 28 | pub message: ConfigMessage, 29 | pub ticker: ConfigTicker, 30 | } 31 | 32 | #[derive(Clone, Deserialize)] 33 | pub struct ConfigServer { 34 | pub password: String, 35 | pub use_auto_account: bool, 36 | pub max_users: usize, 37 | } 38 | 39 | #[derive(Clone, Deserialize)] 40 | pub struct ConfigUser { 41 | pub initial_level: u8, 42 | pub initial_money: u32, 43 | } 44 | 45 | #[derive(Clone, Deserialize)] 46 | pub struct ConfigMessage { 47 | pub notice: String, 48 | } 49 | 50 | #[derive(Clone, Deserialize)] 51 | pub struct ConfigTicker { 52 | pub initial: String, 53 | pub board: String, 54 | pub ranking: String, 55 | pub selection: String, 56 | } 57 | 58 | fn get_random_string(len: usize) -> String { 59 | Alphanumeric.sample_string(&mut rand::thread_rng(), len) 60 | } 61 | 62 | impl Config { 63 | pub fn open(path: &str) -> Result { 64 | let mut ret: Self = config::Config::builder() 65 | .add_source(config::File::with_name(path)) 66 | .build()? 67 | .try_deserialize()?; 68 | let pw = &ret 69 | .server 70 | .password; 71 | if pw.is_empty() { 72 | ret 73 | .server 74 | .password = get_random_string(32); 75 | } 76 | Ok(ret) 77 | } 78 | } -------------------------------------------------------------------------------- /server/src/util/db.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | pub use nosqlite::*; 19 | use serde::{Serialize, Deserialize}; 20 | 21 | pub const USER_TBL: &str = "user"; 22 | pub const POST_TBL: &str = "post"; 23 | 24 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] 25 | pub struct UserSchema { 26 | pub id: String, 27 | pub pw: String, // it should be hashed. 28 | pub name: String, 29 | pub level: u8, 30 | pub is_female: bool, 31 | pub money: u32, 32 | pub items: Vec, // total: 4 33 | pub exps: Vec, // total: 8 34 | pub is_admin: bool, 35 | pub is_muted: bool, 36 | pub is_banned: bool, 37 | 38 | pub setting: SettingSchema 39 | } 40 | 41 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] 42 | pub struct SettingSchema { 43 | pub key_binding: u8, 44 | pub bgm_volume: u8, 45 | pub bgm_mute: bool, 46 | pub bgm_echo: bool, 47 | pub sound_volume: u8, 48 | pub sound_mute: bool, 49 | pub macros: Vec, // total: 8 50 | } 51 | 52 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] 53 | pub struct DateTimeSchema { 54 | pub year: i32, 55 | pub month: u32, 56 | pub day: u32, 57 | pub hour: u32, 58 | pub min: u32, 59 | } 60 | 61 | impl DateTimeSchema { 62 | pub fn now() -> Self { 63 | use chrono::prelude::*; 64 | 65 | let local: DateTime = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00` 66 | let date = local.date(); 67 | let time = local.time(); 68 | 69 | Self { 70 | year: date.year(), 71 | month: date.month(), 72 | day: date.day(), 73 | hour: time.hour(), 74 | min: time.minute() 75 | } 76 | } 77 | } 78 | 79 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] 80 | pub struct PostSchema { 81 | pub post_id: u32, 82 | pub parent_post_id: Option, 83 | pub writer_id: String, 84 | pub title: String, 85 | pub text: String, 86 | pub datetime: DateTimeSchema, 87 | pub is_deleted: bool, 88 | pub is_notice: bool, 89 | pub views: u16, 90 | } 91 | 92 | /* 93 | 94 | pub struct Db { 95 | conn: Connection 96 | } 97 | 98 | impl Db { 99 | pub fn open(path: &str) -> Result { 100 | let conn = Connection::open(path)?; 101 | Ok(Self { 102 | conn: conn 103 | }) 104 | } 105 | 106 | pub fn insert(&self, coll_name: &str, data: T) { 107 | let conn = &self.conn; 108 | let coll = conn.table(coll_name).unwrap(); 109 | coll.insert(data, conn); 110 | } 111 | 112 | pub fn select(&self, coll_name: &str, filter: A) -> Iterator { 113 | let conn = &self.conn; 114 | let coll = conn.table(coll_name).unwrap(); 115 | coll 116 | .iter() 117 | .filter(field) 118 | .set(field, value, connection) 119 | } 120 | } 121 | */ 122 | 123 | //use hotpot_db::*; 124 | 125 | // Just export pub uses. 126 | /* 127 | pub use rusqlite::{Connection}; 128 | 129 | pub const TBL_USER: &str = "user"; 130 | pub const TBL_BOARD: &str = "board"; 131 | 132 | 133 | pub struct Db { 134 | conn: Connection 135 | } 136 | 137 | impl Db { 138 | pub fn open(path: &str) -> Result { 139 | let conn = Connection::open(path)?; 140 | 141 | 142 | Ok(Self { 143 | conn: conn 144 | }) 145 | } 146 | } 147 | */ 148 | 149 | /* 150 | use std::io::BufRead; 151 | 152 | use rocksdb::{DB, Options}; 153 | 154 | pub struct Db { 155 | db: rocksdb::DBWithThreadMode, 156 | } 157 | 158 | const STRING_DELIMITER: u8 = 0xff; 159 | 160 | impl Db { 161 | pub fn open(path: &str) -> Result { 162 | let conn = DB::open_default(path)?; 163 | let db = Db { 164 | db: conn, 165 | }; 166 | Ok(db) 167 | } 168 | 169 | pub fn test(&self) { 170 | let db = &self.db; 171 | let mut it = db.iterator(rocksdb::IteratorMode::Start); 172 | while let Some((k, v)) = it.next() { 173 | print!("{} ", String::from_utf8(k.to_vec()).unwrap()); 174 | println!("{:?} {:?}", k, v); 175 | } 176 | } 177 | 178 | pub fn get_string(&self, key: &str) -> Result { 179 | let db = &self.db; 180 | let val = match db.get(key)? { 181 | Some(val) => val, 182 | _ => bail!("Not exists") 183 | }; 184 | let str = String::from_utf8(val).unwrap(); 185 | Ok(str) 186 | } 187 | 188 | pub fn get_i64(&self, key: &str) -> Result { 189 | let db = &self.db; 190 | let val = match db.get_pinned(key)? { 191 | Some(val) => val, 192 | _ => bail!("Not exists") 193 | }; 194 | let mut fixed_val: [u8; 8] = Default::default(); 195 | for (i, elem) in val.iter().enumerate() { 196 | fixed_val[i] = *elem; 197 | } 198 | let ret = i64::from_le_bytes(fixed_val); 199 | Ok(ret) 200 | } 201 | 202 | pub fn get_i64_vec(&self, key: &str) -> Result> { 203 | let db = &self.db; 204 | let val = match db.get_pinned(key)? { 205 | Some(val) => val, 206 | _ => bail!("Not exists") 207 | }; 208 | let len = val.len(); 209 | assert_eq!(len % 8, 0); 210 | let count = len / 8; 211 | let ret = (0..count) 212 | .map(|i| { 213 | let mut fixed_val: [u8; 8] = Default::default(); 214 | let s = i * 8; 215 | let curr_val = &val[s..s+8]; 216 | for (j, elem) in curr_val.iter().enumerate() { 217 | fixed_val[i] = *elem; 218 | } 219 | let word = i64::from_le_bytes(fixed_val); 220 | word 221 | }) 222 | .collect(); 223 | Ok(ret) 224 | } 225 | 226 | pub fn get_string_vec(&self, key: &str) -> Result> { 227 | let db = &self.db; 228 | let val = match db.get(key)? { 229 | Some(val) => val, 230 | _ => bail!("Not exists") 231 | }; 232 | let ret = val 233 | .split(|ch| *ch == STRING_DELIMITER) 234 | .map(|bytes| String::from_utf8(Vec::from(bytes)).unwrap()) 235 | .collect(); 236 | Ok(ret) 237 | } 238 | 239 | pub fn get_bool(&self, key: &str) -> Result { 240 | let db = &self.db; 241 | let val = match db.get_pinned(key)? { 242 | Some(val) => val, 243 | _ => bail!("Not exists") 244 | }; 245 | let ret = match val[0] { 246 | 1 => true, 247 | _ => false, 248 | }; 249 | Ok(ret) 250 | } 251 | 252 | /// TODO: It always induces memory copy for returning the value. 253 | pub fn get_bytes(&self, key: &str) -> Result> { 254 | let db = &self.db; 255 | let val = match db.get(key)? { 256 | Some(val) => val, 257 | _ => bail!("Not exists") 258 | }; 259 | Ok(val) 260 | } 261 | 262 | pub fn put_string(&self, key: &str, val: &str) { 263 | let db = &self.db; 264 | let _ = db.put(key, val); 265 | } 266 | 267 | pub fn put_i64(&self, key: &str, val: i64) { 268 | let db = &self.db; 269 | let fixed_val: [u8; 8] = val.to_le_bytes(); 270 | let _ = db.put(key, fixed_val); 271 | } 272 | 273 | pub fn put_bool(&self, key: &str, val: bool) { 274 | let db = &self.db; 275 | let mut fixed_val: [u8; 1] = Default::default(); 276 | if val { 277 | fixed_val[0] = 1; 278 | } else { 279 | fixed_val[0] = 0; 280 | } 281 | let _ = db.put(key, fixed_val); 282 | } 283 | 284 | // TODO: Reduce overhead. 285 | pub fn put_i64_vec(&self, key: &str, val: &Vec) { 286 | let db = &self.db; 287 | let mut acc = vec!(); 288 | val 289 | .iter() 290 | .for_each(|elem| { 291 | let mut v = elem.to_le_bytes().to_vec(); 292 | acc.append(&mut v); 293 | }); 294 | let _ = db.put(key, acc); 295 | } 296 | 297 | pub fn put_string_vec(&self, key: &str, val: &Vec) { 298 | let db = &self.db; 299 | let mut acc = vec!(); 300 | val 301 | .iter() 302 | .enumerate() 303 | .for_each(|(i, elem)| { 304 | let mut v = elem.as_bytes().to_vec(); 305 | if i != 0 { 306 | acc.push(STRING_DELIMITER); 307 | } 308 | acc.append(&mut v); 309 | }); 310 | let _ = db.put(key, acc); 311 | } 312 | 313 | pub fn put_bytes(&self, key: &str, bytes: Vec) { 314 | let db = &self.db; 315 | let _ = db.put(key, &bytes); 316 | } 317 | } 318 | */ -------------------------------------------------------------------------------- /server/src/util/hash.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use sha3::{Sha3_256, Digest}; 19 | 20 | /// We use SHA-3. 21 | pub fn from_str(s: &str) -> Vec { 22 | let mut hasher = Sha3_256::default(); 23 | hasher.update(s); 24 | hasher 25 | .finalize() 26 | .to_vec() 27 | } -------------------------------------------------------------------------------- /server/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | mod preader; 19 | mod pwriter; 20 | mod object; 21 | pub mod db; 22 | pub mod config; 23 | pub mod hash; 24 | 25 | pub use object::*; 26 | pub use db::*; 27 | pub use crate::util::config::Config; 28 | 29 | #[derive(Debug)] 30 | pub struct PacketReader { 31 | opcode: u8, 32 | data: Vec, 33 | offset: usize 34 | } 35 | 36 | pub struct PacketWriter { 37 | opcode: u8, 38 | data: Vec, 39 | } 40 | -------------------------------------------------------------------------------- /server/src/util/object.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use crate::prelude::*; 19 | 20 | use std::{sync::{Arc}, any::{TypeId, Any}, collections::{HashMap}}; 21 | 22 | pub type EntityId = u64; 23 | 24 | 25 | //pub type EntityRef<'a> = RefMut<'a, Entity>; 26 | 27 | /* 28 | impl EntityRef { 29 | pub fn new(entity: Entity) -> Self { 30 | Self { 31 | inner: Rc::new(RefCell::new(entity)) 32 | } 33 | } 34 | 35 | pub fn push(&mut self, v: T) -> bool 36 | where T: 37 | 'static + Sync + Send { 38 | self 39 | .inner 40 | .borrow_mut() 41 | .push(v) 42 | } 43 | 44 | pub fn get(&self) -> Option<&T> 45 | where T: 46 | 'static + Sync + Send { 47 | None 48 | } 49 | 50 | pub fn get_mut(&self) -> Option<&mut T> 51 | where T: 52 | 'static + Sync + Send { 53 | self 54 | .inner 55 | .borrow_mut() 56 | .get_mut() 57 | } 58 | 59 | pub fn id(&self) -> EntityId { 60 | self.borrow().id() 61 | } 62 | }*/ 63 | 64 | 65 | pub struct Ptr { 66 | inner: Arc>, 67 | } 68 | 69 | pub struct World { 70 | entities: HashMap, 71 | entity_id_counter: EntityId, 72 | } 73 | 74 | impl World { 75 | pub fn default() -> Self { 76 | Self { 77 | entities: HashMap::default(), 78 | entity_id_counter: EntityId::min_value(), 79 | } 80 | } 81 | 82 | fn get_new_id(&mut self) -> EntityId { 83 | let id = self.entity_id_counter; 84 | self.entity_id_counter += 1; 85 | id 86 | } 87 | 88 | pub fn push(&mut self) -> EntityId { 89 | let k = self.get_new_id(); 90 | let v = Entity::new(k); 91 | self.entities.insert(k, v); 92 | k 93 | } 94 | 95 | pub fn get(&self, k: &EntityId) -> Result<&Entity> { 96 | match self.entities.get(k) { 97 | Some(v) => Ok(v), 98 | None => bail!("An entity does not exist") 99 | } 100 | } 101 | 102 | pub fn get_mut(&mut self, k: &EntityId) -> Result<&mut Entity> { 103 | match self.entities.get_mut(k) { 104 | Some(v) => Ok(v), 105 | None => bail!("An entity does not exist") 106 | } 107 | } 108 | 109 | pub fn remove(&mut self, k: &EntityId) -> bool { 110 | self.entities 111 | .remove(k) 112 | .is_some() 113 | } 114 | 115 | pub fn values(&self) -> Vec<&Entity> { 116 | self 117 | .entities 118 | .values() 119 | .collect() 120 | } 121 | 122 | pub fn values_mut(&mut self) -> Vec<&mut Entity> { 123 | self 124 | .entities 125 | .values_mut() 126 | .collect() 127 | } 128 | 129 | pub fn select(&self, f: T) -> Vec<&Entity> where T: Fn(&Entity) -> bool { 130 | self 131 | .entities 132 | .values() 133 | .filter(|&entity| f(entity)) 134 | .collect() 135 | } 136 | 137 | pub fn select_mut(&mut self, f: T) -> Vec<&mut Entity> where T: Fn(&Entity) -> bool { 138 | self 139 | .entities 140 | .values_mut() 141 | .filter(|entity| f(entity as &Entity)) 142 | .collect() 143 | } 144 | 145 | } 146 | 147 | pub struct Entity { 148 | entity_id: EntityId, 149 | inner: HashMap>, 150 | } 151 | 152 | impl Entity { 153 | fn new(id: EntityId) -> Self { 154 | Self { 155 | inner: HashMap::default(), 156 | entity_id: id, 157 | } 158 | } 159 | 160 | pub fn id(&self) -> EntityId { 161 | self.entity_id 162 | } 163 | 164 | /// Push a new value into the map. 165 | /// Returns false if the map already has the key. 166 | pub fn push(&mut self, v: T) -> bool 167 | where T: 168 | 'static { 169 | let k = TypeId::of::(); 170 | self.inner 171 | .insert(k, Box::new(v)) 172 | .is_none() 173 | } 174 | 175 | pub fn get(&self) -> Result<&T> 176 | where T: 177 | 'static { 178 | let type_id = TypeId::of::(); 179 | match self.inner.get(&type_id) { 180 | None => bail!("No component named {} in {}", std::any::type_name::(), self.entity_id), 181 | Some(v) => { 182 | let v: Option<&T> = v.downcast_ref(); 183 | match v { 184 | Some(v) => Ok(v), 185 | None => bail!("Internal error: could not downcast a component.") 186 | } 187 | } 188 | } 189 | } 190 | 191 | pub fn get_mut(&mut self) -> Result<&mut T> 192 | where T: 193 | 'static + Send { 194 | let type_id = TypeId::of::(); 195 | match self.inner.get_mut(&type_id) { 196 | None => bail!("No component named {} in {}", std::any::type_name::(), self.entity_id), 197 | Some(v) => { 198 | let v: Option<&mut T> = v.downcast_mut(); 199 | match v { 200 | Some(v) => Ok(v), 201 | None => bail!("Internal error: could not downcast a component.") 202 | } 203 | } 204 | } 205 | } 206 | 207 | pub fn remove(&mut self) -> bool { 208 | let type_id = TypeId::of::(); 209 | self.inner 210 | .remove(&type_id) 211 | .is_some() 212 | } 213 | } 214 | 215 | impl Ptr { 216 | pub fn new(base: T) -> Self { 217 | Self { 218 | inner: Arc::new(RwLock::new(base)), 219 | } 220 | } 221 | 222 | pub fn clone(&self) -> Self { 223 | Self { 224 | inner:self.inner.clone() 225 | } 226 | } 227 | 228 | pub async fn read(&self) -> RwLockReadGuard<'_, T> { 229 | self.inner.read().await 230 | } 231 | 232 | pub async fn lock(&self) -> RwLockWriteGuard<'_, T> { 233 | self.inner.write().await 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /server/src/util/preader.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use crate::prelude::*; 19 | use super::*; 20 | 21 | use encoding::{all, Encoding, DecoderTrap}; 22 | 23 | impl PacketReader { 24 | pub fn new(body: &[u8]) -> Self { 25 | let opcode = body[0]; 26 | let data = Vec::from(body); 27 | PacketReader { 28 | opcode: opcode, 29 | data: data, 30 | offset: 8 /* Skip the header part in 'body'. */ 31 | } 32 | } 33 | 34 | pub fn left(&self) -> usize { 35 | self.len() - self.offset 36 | } 37 | 38 | pub fn len(&self) -> usize { 39 | self.data.len() 40 | } 41 | 42 | pub fn opcode(&self) -> u8 { 43 | self.opcode 44 | } 45 | 46 | pub fn seek(&mut self, delta: usize) { 47 | self.offset = self.offset + delta; 48 | } 49 | 50 | pub fn u8(&mut self) -> u8 { 51 | let ret = self.data[self.offset]; 52 | self.seek(1); 53 | ret 54 | } 55 | 56 | pub fn u16(&mut self) -> u16 { 57 | let ret = read_u16(&self.data, self.offset); 58 | self.seek(2); 59 | ret 60 | } 61 | 62 | pub fn u32(&mut self) -> u32 { 63 | let ret = read_u32(&self.data, self.offset); 64 | self.seek(4); 65 | ret 66 | } 67 | 68 | pub fn string(&mut self, len: usize) -> String { 69 | let euc_kr = all::WINDOWS_949; 70 | let data = self.vec(len); 71 | let actual_len = { 72 | let mut l = len; 73 | for i in 0..len { 74 | if data[i] == 0 { 75 | l = i; 76 | break; 77 | } 78 | } 79 | l 80 | }; 81 | euc_kr.decode(&data[..actual_len], DecoderTrap::Ignore).unwrap() 82 | } 83 | 84 | /// It's used when (1) it contains a string with a null terminator and 85 | /// (2) its actual length is not specified. 86 | pub fn string_with_null(&mut self) -> Result { 87 | // Get the actual length until null. 88 | let mut off = self.offset; 89 | let data_len = self.data.len(); 90 | loop { 91 | if off >= data_len { 92 | bail!("Invalid input: no terminator"); 93 | } else { 94 | if self.data[off] == 0 { 95 | break; 96 | } else { 97 | off += 1; 98 | } 99 | } 100 | } 101 | Ok(self.string(off + 1)) // Including an null terminator. 102 | } 103 | 104 | pub fn vec(&mut self, len: usize) -> Vec { 105 | let ret = Vec::from(&self.data[self.offset .. self.offset + len]); 106 | self.seek(len); 107 | ret 108 | } 109 | 110 | pub fn to_str(&self) -> String { 111 | format!("{:x?}", self.data) 112 | } 113 | } -------------------------------------------------------------------------------- /server/src/util/pwriter.rs: -------------------------------------------------------------------------------- 1 | // RustyDO 2 | // 3 | // Copyright 2022. JungHyun Kim (jidoc01). 4 | // 5 | // This program is free software: you can redistribute it and/or modify it under 6 | // the terms of the GNU Affero General Public License as published by the Free 7 | // Software Foundation, either version 3 of the License, or (at your option) any 8 | // later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, but WITHOUT 11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 13 | // details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | use crate::prelude::*; 19 | use super::*; 20 | 21 | use byteorder::WriteBytesExt; 22 | use byteorder::LittleEndian; 23 | use encoding::{all, Encoding, EncoderTrap}; 24 | 25 | const BODY_MAGIC_STAMP: u32 = 0x12345678; 26 | 27 | /// A module for writing a body part of a packet. 28 | impl PacketWriter { 29 | pub fn new(opcode: u8) -> Self { 30 | let mut pw = PacketWriter { 31 | opcode: opcode, 32 | data: vec!(), 33 | }; 34 | pw 35 | .u8(opcode) 36 | .u8(0) // Dummy 37 | .u16(0) // Dummy 38 | .u32(BODY_MAGIC_STAMP); 39 | pw 40 | } 41 | 42 | pub fn len(&mut self) -> usize { 43 | self.data.len() 44 | } 45 | 46 | pub fn opcode(&self) -> u8 { 47 | self.opcode 48 | } 49 | 50 | pub fn u8(&mut self, v: u8) -> &mut Self { 51 | let _ = self.data.write_u8(v); 52 | self 53 | } 54 | 55 | pub fn u16(&mut self, v: u16) -> &mut Self { 56 | let _ = self.data.write_u16::(v); 57 | self 58 | } 59 | 60 | pub fn u32(&mut self, v: u32) -> &mut Self { 61 | let _ = self.data.write_u32::(v); 62 | self 63 | } 64 | 65 | pub fn i32(&mut self, v: i32) -> &mut Self { 66 | let _ = self.data.write_i32::(v); 67 | self 68 | } 69 | 70 | pub fn string(&mut self, msg: &str, size: usize) -> &mut Self { 71 | let euc_kr = all::WINDOWS_949; 72 | match euc_kr.encode(msg, EncoderTrap::Ignore) { 73 | Ok(data) => { 74 | let len = data.len(); 75 | if len > size { 76 | self.u8s(&data[..size]) 77 | } 78 | else { 79 | let shortage = size - len; 80 | self.u8s(&data); 81 | self.vec(&vec![0u8; shortage]) 82 | } 83 | }, 84 | _ => { 85 | println!("Invalid input for encoding: {msg}"); 86 | self.vec(&vec![0u8; size]) // Just fill with null terminators. 87 | } 88 | } 89 | } 90 | 91 | pub fn string_with_null(&mut self, msg: &str) -> &mut Self { 92 | let euc_kr = all::WINDOWS_949; 93 | match euc_kr.encode(msg, EncoderTrap::Ignore) { 94 | Ok(data) => { 95 | self.u8s(&data); 96 | }, 97 | _ => { 98 | println!("Invalid input for encoding: {msg}"); 99 | } 100 | } 101 | self.u8(0) 102 | } 103 | 104 | pub fn vec(&mut self, vec: &Vec) -> &mut Self { 105 | self.u8s(vec.as_ref()) 106 | } 107 | 108 | pub fn u8s(&mut self, bytes: &[u8]) -> &mut Self { 109 | let data = &mut self.data; 110 | let len = data.len(); 111 | let vec_len = bytes.len(); 112 | let new_len = len + vec_len; 113 | data.resize(new_len, 0u8); // Extend its length. 114 | for i in 0..vec_len { // Append the vector. 115 | data[len + i] = bytes[i]; 116 | } 117 | self 118 | } 119 | 120 | pub fn pad(&mut self, n: usize) -> &mut Self { 121 | self.vec(&vec![0u8; n]) 122 | } 123 | 124 | /// Pad null bytes until its length reaches the given length. 125 | pub fn pad_to(&mut self, len: usize) -> &mut Self { 126 | let curr_len = self.data.len(); 127 | assert!(curr_len <= len); 128 | self.pad(len - curr_len) 129 | } 130 | 131 | fn create_header(body_len: usize) -> Vec { 132 | let mut bytes = vec!(); 133 | let random_seed = rand::random::() % 6 + 2; 134 | let _ = bytes.write_u16::(body_len as u16); 135 | let _ = bytes.write_u16::(0xb9); 136 | let _ = bytes.write_u16::(0x08); 137 | let _ = bytes.write_u16::(0x09); 138 | let _ = bytes.write_u8(random_seed); 139 | bytes 140 | } 141 | 142 | /// Encrypt and return the packet. 143 | /// NOTE: Do not call this frequently for the same packet. 144 | pub fn as_vec(&mut self) -> Vec { 145 | let body_len = self.len(); 146 | let header = Self::create_header(body_len); 147 | let header_enc = crypt::encode(&header).unwrap(); 148 | let body = &self.data; 149 | let body_enc = crypt::transfer(body); 150 | let tail = vec![0u8; TAIL_SIZE]; 151 | let list = [ &header_enc, &body_enc, &tail ]; 152 | let pkt = concat_list_of_vec(&list); 153 | pkt 154 | } 155 | } --------------------------------------------------------------------------------