diff --git a/auth-service/src/grpc.rs b/auth-service/src/grpc.rs index 4b9e627..1d98074 100644 --- a/auth-service/src/grpc.rs +++ b/auth-service/src/grpc.rs @@ -36,7 +36,7 @@ impl AuthService for MyAuthService { &req.username, &req.password, ) - .await + .await { let user_id = user.user_id.to_string(); let session_id = uuid::Uuid::new_v4().to_string(); @@ -146,12 +146,13 @@ impl AuthService for MyAuthService { match response { Ok(res) => { - debug!("Session valid: {:?}", res.into_inner()); - Ok(Response::new(ValidateSessionResponse { valid: true })) + let res = res.into_inner(); + debug!("Session valid: {:?}", res); + Ok(Response::new(ValidateSessionResponse { valid: true, session_id: res.session_id.to_string(), user_id: res.user_id.to_string() })) } Err(_) => { debug!("Session invalid or not found"); - Ok(Response::new(ValidateSessionResponse { valid: false })) + Ok(Response::new(ValidateSessionResponse { valid: false, session_id: "".to_string(), user_id: "".to_string() })) } } } @@ -172,12 +173,13 @@ impl AuthService for MyAuthService { match response { Ok(res) => { - debug!("Session valid: {:?}", res.into_inner()); - Ok(Response::new(ValidateSessionResponse { valid: true })) + let res = res.into_inner(); + debug!("Session valid: {:?}", res); + Ok(Response::new(ValidateSessionResponse { valid: true, session_id: res.session_id.to_string(), user_id: res.user_id.to_string() })) } Err(_) => { debug!("Session invalid or not found"); - Ok(Response::new(ValidateSessionResponse { valid: false })) + Ok(Response::new(ValidateSessionResponse { valid: false, session_id: "".to_string(), user_id: "".to_string() })) } } } diff --git a/database-service/src/characters.rs b/database-service/src/characters.rs index 9d1eec2..b9047f2 100644 --- a/database-service/src/characters.rs +++ b/database-service/src/characters.rs @@ -2,7 +2,8 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, Row}; use std::sync::Arc; use tokio::sync::Mutex; -use utils::redis_cache::{Cache, RedisCache}; // Import RedisCache +use utils::redis_cache::{Cache, RedisCache}; +// Import RedisCache #[derive(Debug, FromRow, Serialize, Deserialize)] pub struct Character { @@ -74,14 +75,16 @@ impl CharacterRepository { looks: serde_json::Value, position: serde_json::Value, ) -> Result { + let default_skills = "[{\"id\": 11, \"level\": 1}, {\"id\": 12, \"level\": 1}, {\"id\": 16, \"level\": 1}, {\"id\": 19, \"level\": 1}, {\"id\": 20, \"level\": 1}, {\"id\": 21, \"level\": 1}, {\"id\": 26, \"level\": 1}, {\"id\": 41, \"level\": 1}, {\"id\": 42, \"level\": 1}, {\"id\": 43, \"level\": 1}, {\"id\": 46, \"level\": 1}, {\"id\": 47, \"level\": 1}, {\"id\": 48, \"level\": 1}, {\"id\": 49, \"level\": 1}, {\"id\": 50, \"level\": 1}]"; let result = sqlx::query( - "INSERT INTO characters (user_id, name, inventory, stats, looks, position, created_at, updated_at, is_active) \ - VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW(), true) RETURNING id", + "INSERT INTO characters (user_id, name, inventory, stats, skills, looks, position, created_at, updated_at, is_active) \ + VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW(), true) RETURNING id", ) .bind(user_id) .bind(name) .bind(inventory) .bind(stats) + .bind(default_skills) .bind(looks) .bind(position) .fetch_one(&self.pool) @@ -150,7 +153,7 @@ impl CharacterRepository { // Fetch from database let characters = sqlx::query_as::<_, Character>( - "SELECT id, user_id, name, inventory, stats, looks, position, created_at, updated_at, extract(epoch from (deleted_at - now()))::BIGINT as deleted_at, is_active FROM characters WHERE user_id = $1 AND is_active = true", + "SELECT id, user_id, name, inventory, stats, skills, looks, position, created_at, updated_at, extract(epoch from (deleted_at - now()))::BIGINT as deleted_at, is_active FROM characters WHERE user_id = $1 AND is_active = true", ) .bind(user_id) .fetch_all(&self.pool) diff --git a/frontend/src/components/LoginPage.js b/frontend/src/components/LoginPage.js index 8a08f4c..9c30b2d 100644 --- a/frontend/src/components/LoginPage.js +++ b/frontend/src/components/LoginPage.js @@ -1,7 +1,7 @@ import React, {useEffect, useState} from "react"; import axios from "axios"; import {getServiceAddress} from "../utils/consul"; -import { Link } from "react-router-dom"; +import {Link} from "react-router-dom"; const LoginPage = () => { const [apiUrl, setApiUrl] = useState(null); @@ -13,7 +13,7 @@ const LoginPage = () => { // Fetch the API address from Consul const fetchApiUrl = async () => { try { - const { ServiceAddress, ServicePort } = await getServiceAddress("api-service"); + const {ServiceAddress, ServicePort} = await getServiceAddress("api-service"); setApiUrl(`http://${ServiceAddress}:${ServicePort}/api/login`); } catch (error) { setMessage("Failed to retrieve API information."); @@ -31,15 +31,15 @@ const LoginPage = () => { } try { - const response = await axios.post(apiUrl, { username, password }); + const response = await axios.post(apiUrl, {username, password}); // Extract token and server info from response - const { token, session_id } = response.data; + const {token, session_id} = response.data; setMessage("Login successful! Launching game..."); - const { ServiceAddress, ServicePort } = await getServiceAddress("packet-service"); - window.location.href = `osirose-launcher://launch?otp=${encodeURIComponent(token)}&session=${encodeURIComponent(session_id)}&ip=${encodeURIComponent(ServiceAddress)}&port=${encodeURIComponent(ServicePort)}&username=${encodeURIComponent(username)}`; + const {ServiceAddress, ServicePort} = await getServiceAddress("packet-service"); + window.location.href = `osirose-launcher://launch?session=${encodeURIComponent(session_id)}&ip=${encodeURIComponent(ServiceAddress)}&port=${encodeURIComponent(ServicePort)}&username=${encodeURIComponent(username)}`; } catch (error) { setMessage("Login failed: " + error.response?.data?.error || error.message); } diff --git a/launcher/src/launcher.rs b/launcher/src/launcher.rs index 80b7239..338a063 100644 --- a/launcher/src/launcher.rs +++ b/launcher/src/launcher.rs @@ -1,5 +1,8 @@ use crate::format_shell_command; +use crate::wait_for_keypress; use std::borrow::Cow; +use std::env; +use std::process::exit; use std::process::{Command, Stdio}; use tracing::{debug, error, info, warn}; use url::Url; @@ -15,7 +18,7 @@ fn create_command() -> Command { exit(1); } - let mut command = Command::new("./TRose.exe"); + let command = Command::new("./TRose.exe"); command } @@ -56,7 +59,8 @@ pub(crate) fn launch_game(url: String) { command.arg("_direct").arg("_otp").arg(value.to_string()); } Cow::Borrowed("session") => { - command.arg("_session").arg(value.to_string()); + is_direct = true; + command.arg("_direct").arg("_session").arg(value.to_string()); } Cow::Borrowed("username") => { command.arg("_userid").arg(value.to_string()); diff --git a/packet-service/Dockerfile b/packet-service/Dockerfile index 9d0a0f6..50762d7 100644 --- a/packet-service/Dockerfile +++ b/packet-service/Dockerfile @@ -20,6 +20,6 @@ RUN apk add --no-cache libssl3 libgcc COPY --from=builder /usr/src/packet-service/target/release/packet-service /usr/local/bin/packet-service -EXPOSE 4000 +EXPOSE 29000 CMD ["packet-service"] \ No newline at end of file diff --git a/packet-service/src/enums.rs b/packet-service/src/enums.rs index f85f4ad..899f8f8 100644 --- a/packet-service/src/enums.rs +++ b/packet-service/src/enums.rs @@ -119,6 +119,29 @@ pub(crate) enum ItemType { Zuly = 0x1F, } +impl ItemType { + pub(crate) fn from_i32(value: i32) -> Option { + match value { + 1 => Some(ItemType::ItemGoggles), + 2 => Some(ItemType::ItemHelmet), + 3 => Some(ItemType::ItemArmor), + 4 => Some(ItemType::ItemGauntlet), + 5 => Some(ItemType::ItemBoots), + 6 => Some(ItemType::ItemBackpack), + 7 => Some(ItemType::ItemRing), + 8 => Some(ItemType::ItemWeaponR), + 9 => Some(ItemType::ItemWeaponL), + 10 => Some(ItemType::ItemConsumable), + 11 => Some(ItemType::ItemEtcGem), + 12 => Some(ItemType::ItemEtc), + 13 => Some(ItemType::ItemEtc2), + 14 => Some(ItemType::ItemRiding), + 0x1F => Some(ItemType::Zuly), + _ => None, + } + } +} + mod party_req { #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/packet-service/src/handlers/auth.rs b/packet-service/src/handlers/auth.rs index 237ab48..9394690 100644 --- a/packet-service/src/handlers/auth.rs +++ b/packet-service/src/handlers/auth.rs @@ -140,7 +140,7 @@ pub(crate) async fn handle_login_req( debug!("{:?}", data); let mut auth_client = auth_client.lock().await; - match auth_client.login_token(&data.token.0).await { + match auth_client.validate_session(&data.token.0).await { Ok(response) => { if response.valid == false { info!("Login failed: Invalid credentials"); @@ -158,7 +158,7 @@ pub(crate) async fn handle_login_req( if let Some(mut state) = connection_service.get_connection_mut(&connection_id) { state.user_id = Some(response.user_id.parse().unwrap()); - state.session_id = Some(response.session_id); + state.session_id = Some(response.session_id.parse().unwrap()); } let consul_url = diff --git a/packet-service/src/handlers/character.rs b/packet-service/src/handlers/character.rs index b02b7dc..3999861 100644 --- a/packet-service/src/handlers/character.rs +++ b/packet-service/src/handlers/character.rs @@ -3,6 +3,7 @@ use crate::character_client::CharacterClient; use crate::connection_service::ConnectionService; use crate::dataconsts::*; use crate::enums; +use crate::enums::ItemType; use crate::packet::{send_packet, Packet, PacketPayload}; use crate::packet_type::PacketType; use crate::packets::*; @@ -16,7 +17,7 @@ use tonic::{Code, Status}; use tracing::{debug, error, info, warn}; use utils::null_string::NullTerminatedString; -pub(crate) fn convert_slot(slot: i32) -> srv_char_list_reply::EquippedPosition { +pub(crate) fn convert_slot_to_equip_slot(slot: i32) -> srv_char_list_reply::EquippedPosition { match enums::EquippedPosition::from_i32(slot) { Some(enums::EquippedPosition::Goggles) => srv_char_list_reply::EquippedPosition::Googles, Some(enums::EquippedPosition::Helmet) => srv_char_list_reply::EquippedPosition::Helmet, @@ -30,6 +31,34 @@ pub(crate) fn convert_slot(slot: i32) -> srv_char_list_reply::EquippedPosition { } } +pub(crate) fn convert_slot_to_body_part(slot: i32) -> usize { + match enums::EquippedPosition::from_i32(slot) { + Some(enums::EquippedPosition::Goggles) => 6, + Some(enums::EquippedPosition::Helmet) => 2, + Some(enums::EquippedPosition::Armor) => 3, + Some(enums::EquippedPosition::Backpack) => 7, + Some(enums::EquippedPosition::Gauntlet) => 4, + Some(enums::EquippedPosition::Boots) => 5, + Some(enums::EquippedPosition::WeaponR) => 8, + Some(enums::EquippedPosition::WeaponL) => 9, + _ => 12, + } +} + +pub(crate) fn convert_type_to_body_part(slot: i32) -> ItemType { + match enums::EquippedPosition::from_i32(slot) { + Some(enums::EquippedPosition::Goggles) => ItemType::ItemGoggles, + Some(enums::EquippedPosition::Helmet) => ItemType::ItemHelmet, + Some(enums::EquippedPosition::Armor) => ItemType::ItemArmor, + Some(enums::EquippedPosition::Backpack) => ItemType::ItemBackpack, + Some(enums::EquippedPosition::Gauntlet) => ItemType::ItemGauntlet, + Some(enums::EquippedPosition::Boots) => ItemType::ItemBoots, + Some(enums::EquippedPosition::WeaponR) => ItemType::ItemWeaponR, + Some(enums::EquippedPosition::WeaponL) => ItemType::ItemWeaponL, + _ => ItemType::None, + } +} + pub(crate) async fn handle_char_list_req( stream: &mut TcpStream, packet: Packet, @@ -64,7 +93,7 @@ pub(crate) async fn handle_char_list_req( for item in character.items { if item.slot < MAX_VISIBLE_ITEMS as i32 { - let slot = convert_slot(item.slot) as usize; + let slot = convert_slot_to_equip_slot(item.slot) as usize; item_list[slot] = EquippedItem { id: item.item_id as u16, gem_opt: item.gem_option as u16, @@ -244,6 +273,7 @@ pub(crate) async fn handle_select_char_req( let character = character_data.character.unwrap_or_default(); + let name = NullTerminatedString(character.name.clone()); let looks = character.looks.unwrap(); let position = character.position.unwrap(); let stats = character.stats.unwrap(); @@ -253,22 +283,23 @@ pub(crate) async fn handle_select_char_req( core::array::from_fn(|i| EquippedItem::default()); let mut inventory: [srv_inventory_data::Item; (MAX_ITEMS as usize)] = core::array::from_fn(|i| srv_inventory_data::Item::default()); - let mut skill_list: [u16; (MAX_SKILL_COUNT as usize)] = core::array::from_fn(|i| { - if i < skills.len() { - return skills[i] as u16; - } - 0 - }); + let mut skill_list: [u16; (MAX_SKILL_COUNT as usize)] = [0u16; MAX_SKILL_COUNT as usize]; + + for index in 0..skills.len() { + skill_list[index] = skills[index].id as u16; + } for item in items { if item.slot < MAX_VISIBLE_ITEMS as i32 { - let slot = convert_slot(item.slot) as usize; - equipped_item_list[slot] = EquippedItem { - id: item.item_id as u16, - gem_opt: item.gem_option as u16, - socket: item.socket as i8, - grade: item.grade as u8, - }; + let slot = convert_type_to_body_part(item.slot) as usize - 2; + if slot >= 0 { + equipped_item_list[slot] = EquippedItem { + id: item.item_id as u16, + gem_opt: item.gem_option as u16, + socket: item.socket as i8, + grade: item.grade as u8, + }; + } } inventory[item.slot as usize] = srv_inventory_data::Item { @@ -337,8 +368,9 @@ pub(crate) async fn handle_select_char_req( skills: skill_list, hotbar: hotbar_list, tag: user_id as u32, - name: request.name, + name, }; + debug!("{:?}", data); let response_packet = Packet::new(PacketType::PakwcSelectCharReply, &data)?; send_packet(stream, &response_packet).await?; diff --git a/packet-service/src/packet.rs b/packet-service/src/packet.rs index a6704e3..29abc82 100644 --- a/packet-service/src/packet.rs +++ b/packet-service/src/packet.rs @@ -14,14 +14,18 @@ pub struct Packet { } pub trait PacketPayload { - fn encode(&self) -> Result, Box> where Self: Encode { + fn encode(&self) -> Result, Box> + where + Self: Encode, + { let config = bincode::config::standard().with_fixed_int_encoding(); Ok(bincode::encode_to_vec(self, config)?) } fn decode(data: &[u8]) -> Result> where - Self: Sized, Self: Decode<()> + Self: Sized, + Self: Decode<()>, { let config = bincode::config::standard().with_fixed_int_encoding(); Ok(bincode::decode_from_slice(data, config)?.0) @@ -107,7 +111,7 @@ impl Packet { pub async fn send_packet(stream: &mut TcpStream, packet: &Packet) -> Result<(), std::io::Error> { let data = packet.to_raw(); - debug!("Sending data: {:?}", data); + debug!("Sending '{:#X}' bytes of data. {:?}", data.len(), data); stream.write_all(&data).await?; Ok(()) } diff --git a/packet-service/src/types.rs b/packet-service/src/types.rs index 7fe0a88..ab5d5fe 100644 --- a/packet-service/src/types.rs +++ b/packet-service/src/types.rs @@ -1,5 +1,4 @@ use bincode::{Decode, Encode}; -use std::time::Duration; // `HotbarItem` structure converted to Rust. #[derive(Debug, Clone, Copy, Encode, Decode, Default)] @@ -18,8 +17,7 @@ pub(crate) struct Skill { // `StatusEffect` structure converted to Rust. #[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, Default)] pub(crate) struct StatusEffect { - expired: Duration, + expired: u32, value: u16, unknown: u16, - dt: Duration, } diff --git a/proto/auth.proto b/proto/auth.proto index 59c2a34..3f45b4e 100644 --- a/proto/auth.proto +++ b/proto/auth.proto @@ -5,74 +5,76 @@ package auth; import "common.proto"; service AuthService { - rpc Login(LoginRequest) returns (LoginResponse); - rpc Logout(LogoutRequest) returns (common.Empty); - rpc ValidateToken(ValidateTokenRequest) returns (ValidateTokenResponse); - rpc ValidateSession(ValidateSessionRequest) returns (ValidateSessionResponse); - rpc RefreshSession(ValidateSessionRequest) returns (ValidateSessionResponse); - rpc Register (RegisterRequest) returns (RegisterResponse); - rpc RequestPasswordReset (PasswordResetRequest) returns (PasswordResetResponse); - rpc ResetPassword (ResetPasswordRequest) returns (ResetPasswordResponse); + rpc Login(LoginRequest) returns (LoginResponse); + rpc Logout(LogoutRequest) returns (common.Empty); + rpc ValidateToken(ValidateTokenRequest) returns (ValidateTokenResponse); + rpc ValidateSession(ValidateSessionRequest) returns (ValidateSessionResponse); + rpc RefreshSession(ValidateSessionRequest) returns (ValidateSessionResponse); + rpc Register (RegisterRequest) returns (RegisterResponse); + rpc RequestPasswordReset (PasswordResetRequest) returns (PasswordResetResponse); + rpc ResetPassword (ResetPasswordRequest) returns (ResetPasswordResponse); } message LoginRequest { - string username = 1; - string password = 2; - string ip_address = 3; + string username = 1; + string password = 2; + string ip_address = 3; } message LoginResponse { - string token = 1; - string user_id = 2; - string session_id = 3; + string token = 1; + string user_id = 2; + string session_id = 3; } message LogoutRequest { - string session_id = 1; + string session_id = 1; } message ValidateTokenRequest { - string token = 1; + string token = 1; } message ValidateTokenResponse { - bool valid = 1; - string user_id = 2; - string session_id = 3; + bool valid = 1; + string user_id = 2; + string session_id = 3; } message ValidateSessionRequest { - string session_id = 1; + string session_id = 1; } message ValidateSessionResponse { - bool valid = 1; + bool valid = 1; + string session_id = 2; + string user_id = 3; } message RegisterRequest { - string username = 1; - string email = 2; - string password = 3; + string username = 1; + string email = 2; + string password = 3; } message RegisterResponse { - int32 user_id = 1; - string message = 2; + int32 user_id = 1; + string message = 2; } message PasswordResetRequest { - string email = 1; + string email = 1; } message PasswordResetResponse { - string message = 1; + string message = 1; } message ResetPasswordRequest { - string reset_token = 1; - string new_password = 2; + string reset_token = 1; + string new_password = 2; } message ResetPasswordResponse { - string message = 1; + string message = 1; } diff --git a/proto/character_common.proto b/proto/character_common.proto index 1589d21..44bdd27 100644 --- a/proto/character_common.proto +++ b/proto/character_common.proto @@ -62,6 +62,10 @@ message Location { int32 spawn_id = 4; } +message Skill { + int32 id = 1; +} + message Character { string character_id = 1; // Unique ID for the character string name = 2; // Name of the character @@ -78,6 +82,6 @@ message CharacterFull { Location position = 3; // Character's position Looks looks = 4; // Character's Looks Stats stats = 5; // Character's stats - repeated int32 skills = 6; // Character's skills + repeated Skill skills = 6; // Character's skills repeated Item items = 7; // Character inventory } \ No newline at end of file diff --git a/sql/schema.sql b/sql/schema.sql index c6bb3d8..43f7198 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -37,11 +37,11 @@ create table characters user_id integer not null references users on delete cascade, - is_active boolean default true, + is_active boolean default true, name varchar(50) not null, - inventory jsonb default '{}'::jsonb, + inventory jsonb default '[]'::jsonb, stats jsonb default '{}'::jsonb, - skills jsonb default '{}'::jsonb, + skills jsonb default '[]'::jsonb, looks jsonb default '{}'::jsonb, position jsonb default '{}'::jsonb, created_at timestamp default CURRENT_TIMESTAMP,